@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,807 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kortix-session-search
|
|
3
|
+
description: "Complete session search and management for OpenCode. Covers API endpoints, on-disk JSON storage, deterministic file queries (grep, ripgrep, jq, date filtering), semantic search via LSS, session lifecycle, and data schemas. Use whenever you need to find, inspect, query, filter, or manage OpenCode sessions — by title, content, date, cost, agent, status, or meaning."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Session Search & Handling
|
|
7
|
+
|
|
8
|
+
You have **full access** to OpenCode's session data — both through the REST API and directly on disk. This skill teaches you every method for finding, querying, filtering, and managing sessions.
|
|
9
|
+
|
|
10
|
+
## Architecture Overview
|
|
11
|
+
|
|
12
|
+
OpenCode stores all session data as **individual JSON files** in a structured directory tree under:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
/workspace/.local/share/opencode/storage/
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The REST API at `http://localhost:3111` provides higher-level access to the same data. Both methods have tradeoffs — the API is simpler for basic CRUD, but direct file access lets you do powerful queries (grep, jq, date filters, regex) that the API can't.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 1. On-Disk Storage Layout
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
/workspace/.local/share/opencode/storage/
|
|
26
|
+
├── migration # Version marker (currently "2")
|
|
27
|
+
├── project/
|
|
28
|
+
│ └── global.json # Project metadata
|
|
29
|
+
├── session/
|
|
30
|
+
│ └── global/
|
|
31
|
+
│ └── ses_{id}.json # Session metadata (one file per session)
|
|
32
|
+
├── session_diff/
|
|
33
|
+
│ └── ses_{id}.json # File diffs per session (usually [])
|
|
34
|
+
├── message/
|
|
35
|
+
│ └── ses_{id}/ # Directory per session
|
|
36
|
+
│ └── msg_{id}.json # Message metadata (one file per message)
|
|
37
|
+
├── part/
|
|
38
|
+
│ └── msg_{id}/ # Directory per message
|
|
39
|
+
│ └── prt_{id}.json # Content parts (text, tools, reasoning)
|
|
40
|
+
├── todo/
|
|
41
|
+
│ └── ses_{id}.json # Todo lists per session
|
|
42
|
+
└── session_share/ # Shared session data (usually empty)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Other important locations:**
|
|
46
|
+
|
|
47
|
+
| Path | Contents |
|
|
48
|
+
|------|----------|
|
|
49
|
+
| `/workspace/.local/share/opencode/tool-output/tool_{id}` | Large tool outputs (plain text, 60-230KB each) |
|
|
50
|
+
| `/workspace/.local/share/opencode/log/` | Timestamped structured logs |
|
|
51
|
+
| `/workspace/.local/share/opencode/snapshot/global/` | Bare git repo for file snapshots |
|
|
52
|
+
| `/workspace/.local/share/opencode/bin/rg` | Bundled ripgrep binary |
|
|
53
|
+
| `/workspace/.local/state/opencode/prompt-history.jsonl` | JSONL of all prompts ever entered |
|
|
54
|
+
|
|
55
|
+
### ID Format
|
|
56
|
+
|
|
57
|
+
All IDs follow: `{prefix}_{hex-timestamp}{random-suffix}`
|
|
58
|
+
|
|
59
|
+
| Entity | Prefix | Example |
|
|
60
|
+
|--------|--------|---------|
|
|
61
|
+
| Session | `ses_` | `ses_3c077220affeH63Puu4UvaAgJm` |
|
|
62
|
+
| Message | `msg_` | `msg_c3f88de24001K9micndkF0GLsp` |
|
|
63
|
+
| Part | `prt_` | `prt_c3f88de25001CLJ8yDleyTypqP` |
|
|
64
|
+
| Tool output | `tool_` | `tool_c3aca2b70001xLkTb5ypHFDn8H` |
|
|
65
|
+
|
|
66
|
+
The hex portion encodes a creation timestamp — IDs sort chronologically.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## 2. Data Schemas
|
|
71
|
+
|
|
72
|
+
### 2a. Session File (`storage/session/global/ses_{id}.json`)
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"id": "ses_3c077220affeH63Puu4UvaAgJm",
|
|
77
|
+
"slug": "witty-engine",
|
|
78
|
+
"version": "1.1.53",
|
|
79
|
+
"projectID": "global",
|
|
80
|
+
"directory": "/workspace",
|
|
81
|
+
"title": "Voice Proxy",
|
|
82
|
+
"parentID": "ses_...", // OPTIONAL — present for subagent sessions
|
|
83
|
+
"permission": [ // OPTIONAL — permission overrides for subagents
|
|
84
|
+
{ "permission": "todowrite", "action": "deny", "pattern": "*" }
|
|
85
|
+
],
|
|
86
|
+
"time": {
|
|
87
|
+
"created": 1770592460277, // Unix milliseconds
|
|
88
|
+
"updated": 1770592476844
|
|
89
|
+
},
|
|
90
|
+
"summary": {
|
|
91
|
+
"additions": 0,
|
|
92
|
+
"deletions": 0,
|
|
93
|
+
"files": 0
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 2b. Message File (`storage/message/ses_{id}/msg_{id}.json`)
|
|
99
|
+
|
|
100
|
+
**User message:**
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"id": "msg_...",
|
|
104
|
+
"sessionID": "ses_...",
|
|
105
|
+
"role": "user",
|
|
106
|
+
"time": { "created": 1770592460324 },
|
|
107
|
+
"summary": { "title": "Current sessions running inquiry", "diffs": [] },
|
|
108
|
+
"agent": "kortix-proxy",
|
|
109
|
+
"model": { "providerID": "openai", "modelID": "gpt-5.2-chat-latest" }
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Assistant message:**
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"id": "msg_...",
|
|
117
|
+
"sessionID": "ses_...",
|
|
118
|
+
"role": "assistant",
|
|
119
|
+
"time": { "created": 1770592460404, "completed": 1770592463202 },
|
|
120
|
+
"parentID": "msg_...", // Links to the user message it answers
|
|
121
|
+
"modelID": "gpt-5.2-chat-latest",
|
|
122
|
+
"providerID": "openai",
|
|
123
|
+
"mode": "kortix-proxy",
|
|
124
|
+
"agent": "kortix-proxy",
|
|
125
|
+
"path": { "cwd": "/workspace", "root": "/" },
|
|
126
|
+
"cost": 0.00323715, // USD cost
|
|
127
|
+
"tokens": {
|
|
128
|
+
"input": 93,
|
|
129
|
+
"output": 26,
|
|
130
|
+
"reasoning": 0,
|
|
131
|
+
"cache": { "read": 15488, "write": 0 }
|
|
132
|
+
},
|
|
133
|
+
"finish": "stop" // "stop" or "tool-calls"
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 2c. Part File (`storage/part/msg_{id}/prt_{id}.json`)
|
|
138
|
+
|
|
139
|
+
Parts hold the actual content. Each message has 1+ parts.
|
|
140
|
+
|
|
141
|
+
**Text part:**
|
|
142
|
+
```json
|
|
143
|
+
{
|
|
144
|
+
"id": "prt_...", "sessionID": "ses_...", "messageID": "msg_...",
|
|
145
|
+
"type": "text",
|
|
146
|
+
"text": "The actual text content here",
|
|
147
|
+
"time": { "start": 1770592465053, "end": 1770592465053 },
|
|
148
|
+
"metadata": { "openai": { "itemId": "msg_..." } }
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Tool part:**
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"id": "prt_...", "sessionID": "ses_...", "messageID": "msg_...",
|
|
156
|
+
"type": "tool",
|
|
157
|
+
"callID": "call_GfLOPqNzhoo...",
|
|
158
|
+
"tool": "session-list",
|
|
159
|
+
"state": {
|
|
160
|
+
"status": "completed",
|
|
161
|
+
"input": { "limit": 15, "filter": "all" },
|
|
162
|
+
"output": "{...}",
|
|
163
|
+
"title": "",
|
|
164
|
+
"metadata": { "truncated": false },
|
|
165
|
+
"time": { "start": 1770592461000, "end": 1770592462000 }
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Other part types:** `step-start`, `step-finish` (with cost/tokens), `reasoning` (with encrypted content)
|
|
171
|
+
|
|
172
|
+
### 2d. Todo File (`storage/todo/ses_{id}.json`)
|
|
173
|
+
|
|
174
|
+
```json
|
|
175
|
+
[
|
|
176
|
+
{ "id": "1", "content": "Phase 1: Brand Discovery", "status": "completed", "priority": "high" },
|
|
177
|
+
{ "id": "2", "content": "Generate symbol variations", "status": "pending", "priority": "medium" }
|
|
178
|
+
]
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Status values: `completed`, `pending`, `cancelled`
|
|
182
|
+
Priority values: `high`, `medium`, `low`
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## 3. REST API Reference
|
|
187
|
+
|
|
188
|
+
Base URL: `http://localhost:3111`
|
|
189
|
+
|
|
190
|
+
### Session Endpoints
|
|
191
|
+
|
|
192
|
+
| Method | Endpoint | Description |
|
|
193
|
+
|--------|----------|-------------|
|
|
194
|
+
| `GET` | `/session` | List all sessions (newest first) |
|
|
195
|
+
| `POST` | `/session` | Create session. Body: `{"title":"..."}` |
|
|
196
|
+
| `GET` | `/session/{id}` | Get single session |
|
|
197
|
+
| `PATCH` | `/session/{id}` | Update session (e.g. title). Body: `{"title":"..."}` |
|
|
198
|
+
| `DELETE` | `/session/{id}` | Delete session permanently |
|
|
199
|
+
| `GET` | `/session/status` | Global map of busy sessions `{ses_id: {...}}` |
|
|
200
|
+
| `GET` | `/session/{id}/message` | All messages with parts for a session |
|
|
201
|
+
| `POST` | `/session/{id}/message` | Send message (synchronous — blocks until response) |
|
|
202
|
+
| `POST` | `/session/{id}/prompt_async` | Send prompt (fire-and-forget, returns 204 immediately) |
|
|
203
|
+
| `GET` | `/session/{id}/children` | List child/subagent sessions |
|
|
204
|
+
| `POST` | `/session/{id}/abort` | Abort running session |
|
|
205
|
+
| `POST` | `/session/{id}/share` | Create share link |
|
|
206
|
+
|
|
207
|
+
### Other Endpoints
|
|
208
|
+
|
|
209
|
+
| Method | Endpoint | Description |
|
|
210
|
+
|--------|----------|-------------|
|
|
211
|
+
| `GET` | `/config` | Full config (agents, commands, keybinds, MCP) |
|
|
212
|
+
| `GET` | `/agent` | List all agents with descriptions and prompts |
|
|
213
|
+
| `GET` | `/provider` | Provider configs, models, costs |
|
|
214
|
+
| `GET` | `/skill` | All skills with full content |
|
|
215
|
+
| `GET` | `/permission` | Pending permission requests |
|
|
216
|
+
| `GET` | `/project` | Project definitions |
|
|
217
|
+
| `GET` | `/event` | SSE stream of real-time events |
|
|
218
|
+
| `GET` | `/experimental/tool/ids` | List all tool IDs |
|
|
219
|
+
| `GET` | `/experimental/tool?provider=X&model=Y` | Full tool definitions |
|
|
220
|
+
|
|
221
|
+
### API Response Format
|
|
222
|
+
|
|
223
|
+
Messages from `GET /session/{id}/message` return:
|
|
224
|
+
```json
|
|
225
|
+
[
|
|
226
|
+
{
|
|
227
|
+
"info": { "id": "msg_...", "role": "user", "time": {...}, ... },
|
|
228
|
+
"parts": [
|
|
229
|
+
{ "type": "text", "text": "..." },
|
|
230
|
+
{ "type": "tool", "tool": "bash", "state": { "status": "completed", "output": "..." } }
|
|
231
|
+
]
|
|
232
|
+
}
|
|
233
|
+
]
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### SSE Event Stream (`GET /event`)
|
|
237
|
+
|
|
238
|
+
Events are `data:` lines (no `event:` prefix). Parse the JSON to get the type:
|
|
239
|
+
|
|
240
|
+
```
|
|
241
|
+
data: {"type":"session.status","properties":{"sessionID":"ses_...","status":{"type":"busy"}}}
|
|
242
|
+
data: {"type":"message.updated","properties":{"info":{"id":"msg_...","role":"assistant",...}}}
|
|
243
|
+
data: {"type":"message.part.updated","properties":{"part":{"type":"text",...},"delta":"word "}}
|
|
244
|
+
data: {"type":"session.idle","properties":{"sessionID":"ses_..."}}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Event types: `server.connected`, `session.status`, `session.idle`, `session.updated`, `message.updated`, `message.part.updated`, `session.diff`, `file.edited`, `command.executed`
|
|
248
|
+
|
|
249
|
+
The `sessionID` can appear in 3 places depending on event type:
|
|
250
|
+
- `properties.sessionID`
|
|
251
|
+
- `properties.part.sessionID`
|
|
252
|
+
- `properties.info.sessionID`
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## 4. Deterministic Query Methods
|
|
257
|
+
|
|
258
|
+
### 4a. Find Sessions by Title (grep/ripgrep)
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
# Find all sessions with "research" in the title
|
|
262
|
+
rg -l '"title".*research' /workspace/.local/share/opencode/storage/session/global/ -i
|
|
263
|
+
|
|
264
|
+
# Find sessions by exact title
|
|
265
|
+
rg '"title":"Voice Proxy"' /workspace/.local/share/opencode/storage/session/global/
|
|
266
|
+
|
|
267
|
+
# Find sessions by agent/mode
|
|
268
|
+
rg '"agent":"kortix-research"' /workspace/.local/share/opencode/storage/message/ -r -l
|
|
269
|
+
|
|
270
|
+
# Find sessions that used a specific tool
|
|
271
|
+
rg '"tool":"web-search"' /workspace/.local/share/opencode/storage/part/ -r -l
|
|
272
|
+
|
|
273
|
+
# Find sessions with subagents (have parentID)
|
|
274
|
+
rg '"parentID"' /workspace/.local/share/opencode/storage/session/global/
|
|
275
|
+
|
|
276
|
+
# Find child sessions of a specific parent
|
|
277
|
+
rg '"parentID":"ses_PARENT_ID_HERE"' /workspace/.local/share/opencode/storage/session/global/
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### 4b. Find Sessions by Date
|
|
281
|
+
|
|
282
|
+
Session timestamps are Unix milliseconds in the JSON. Convert dates for filtering:
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
# Get current timestamp in ms
|
|
286
|
+
date +%s000
|
|
287
|
+
|
|
288
|
+
# Sessions created today (compare time.created)
|
|
289
|
+
TODAY_START=$(date -d "today 00:00:00" +%s000 2>/dev/null || date -j -f "%Y-%m-%d" "$(date +%Y-%m-%d)" +%s000)
|
|
290
|
+
for f in /workspace/.local/share/opencode/storage/session/global/ses_*.json; do
|
|
291
|
+
created=$(cat "$f" | python3 -c "import json,sys; print(json.load(sys.stdin)['time']['created'])" 2>/dev/null)
|
|
292
|
+
if [ "$created" -gt "$TODAY_START" ] 2>/dev/null; then
|
|
293
|
+
echo "$f: created=$created"
|
|
294
|
+
cat "$f" | python3 -c "import json,sys; d=json.load(sys.stdin); print(f' {d[\"id\"]} | {d[\"title\"]}')"
|
|
295
|
+
fi
|
|
296
|
+
done
|
|
297
|
+
|
|
298
|
+
# Sessions created in the last N hours
|
|
299
|
+
python3 -c "
|
|
300
|
+
import json, glob, time, sys
|
|
301
|
+
hours = int(sys.argv[1]) if len(sys.argv) > 1 else 24
|
|
302
|
+
cutoff = (time.time() - hours * 3600) * 1000
|
|
303
|
+
for f in sorted(glob.glob('/workspace/.local/share/opencode/storage/session/global/ses_*.json')):
|
|
304
|
+
with open(f) as fh:
|
|
305
|
+
d = json.load(fh)
|
|
306
|
+
if d['time']['created'] > cutoff:
|
|
307
|
+
age_h = (time.time()*1000 - d['time']['created']) / 3600000
|
|
308
|
+
print(f'{d[\"id\"]} | {d[\"title\"]:50s} | {age_h:.1f}h ago')
|
|
309
|
+
" 24
|
|
310
|
+
|
|
311
|
+
# Sessions updated in the last hour (recently active)
|
|
312
|
+
python3 -c "
|
|
313
|
+
import json, glob, time
|
|
314
|
+
cutoff = (time.time() - 3600) * 1000
|
|
315
|
+
for f in sorted(glob.glob('/workspace/.local/share/opencode/storage/session/global/ses_*.json')):
|
|
316
|
+
with open(f) as fh:
|
|
317
|
+
d = json.load(fh)
|
|
318
|
+
if d['time']['updated'] > cutoff:
|
|
319
|
+
print(f'{d[\"id\"]} | {d[\"title\"]}')
|
|
320
|
+
"
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### 4c. Find Sessions by Cost
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
# Sum total cost across all messages in a session
|
|
327
|
+
python3 -c "
|
|
328
|
+
import json, glob, sys
|
|
329
|
+
session_id = sys.argv[1]
|
|
330
|
+
total = 0
|
|
331
|
+
for f in glob.glob(f'/workspace/.local/share/opencode/storage/message/{session_id}/msg_*.json'):
|
|
332
|
+
with open(f) as fh:
|
|
333
|
+
d = json.load(fh)
|
|
334
|
+
total += d.get('cost', 0)
|
|
335
|
+
print(f'Total cost for {session_id}: \${total:.4f}')
|
|
336
|
+
" ses_SESSION_ID_HERE
|
|
337
|
+
|
|
338
|
+
# Top 10 most expensive sessions
|
|
339
|
+
python3 -c "
|
|
340
|
+
import json, glob, os
|
|
341
|
+
sessions = {}
|
|
342
|
+
for f in glob.glob('/workspace/.local/share/opencode/storage/message/ses_*/msg_*.json'):
|
|
343
|
+
with open(f) as fh:
|
|
344
|
+
d = json.load(fh)
|
|
345
|
+
sid = d.get('sessionID', '')
|
|
346
|
+
sessions[sid] = sessions.get(sid, 0) + d.get('cost', 0)
|
|
347
|
+
|
|
348
|
+
# Get titles
|
|
349
|
+
for sid in sessions:
|
|
350
|
+
sf = f'/workspace/.local/share/opencode/storage/session/global/{sid}.json'
|
|
351
|
+
if os.path.exists(sf):
|
|
352
|
+
with open(sf) as fh:
|
|
353
|
+
sessions[sid] = (sessions[sid], json.load(fh).get('title', ''))
|
|
354
|
+
else:
|
|
355
|
+
sessions[sid] = (sessions[sid], '???')
|
|
356
|
+
|
|
357
|
+
for sid, (cost, title) in sorted(sessions.items(), key=lambda x: -x[1][0])[:10]:
|
|
358
|
+
print(f'\${cost:.4f} | {title:50s} | {sid}')
|
|
359
|
+
"
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### 4d. Find Sessions by Content (what the agent actually said/did)
|
|
363
|
+
|
|
364
|
+
```bash
|
|
365
|
+
# Search across all text parts for a keyword
|
|
366
|
+
rg "landing page" /workspace/.local/share/opencode/storage/part/ -r -l --type json
|
|
367
|
+
|
|
368
|
+
# Find which session a part belongs to (trace back)
|
|
369
|
+
python3 -c "
|
|
370
|
+
import json
|
|
371
|
+
# Given a part file, trace back to its session
|
|
372
|
+
with open('/workspace/.local/share/opencode/storage/part/msg_XXX/prt_YYY.json') as f:
|
|
373
|
+
part = json.load(f)
|
|
374
|
+
print(f'Session: {part[\"sessionID\"]}')
|
|
375
|
+
print(f'Message: {part[\"messageID\"]}')
|
|
376
|
+
"
|
|
377
|
+
|
|
378
|
+
# Search tool outputs for specific content
|
|
379
|
+
rg "error\|failed\|exception" /workspace/.local/share/opencode/storage/tool-output/ -i -l
|
|
380
|
+
|
|
381
|
+
# Find all bash commands executed in a session
|
|
382
|
+
python3 -c "
|
|
383
|
+
import json, glob, sys
|
|
384
|
+
session_id = sys.argv[1]
|
|
385
|
+
# Find all messages in this session
|
|
386
|
+
for mf in sorted(glob.glob(f'/workspace/.local/share/opencode/storage/message/{session_id}/msg_*.json')):
|
|
387
|
+
with open(mf) as fh:
|
|
388
|
+
msg = json.load(fh)
|
|
389
|
+
msg_id = msg['id']
|
|
390
|
+
# Find tool parts for this message
|
|
391
|
+
for pf in sorted(glob.glob(f'/workspace/.local/share/opencode/storage/part/{msg_id}/prt_*.json')):
|
|
392
|
+
with open(pf) as fh:
|
|
393
|
+
part = json.load(fh)
|
|
394
|
+
if part.get('type') == 'tool' and part.get('tool') == 'bash':
|
|
395
|
+
state = part.get('state', {})
|
|
396
|
+
inp = state.get('input', {})
|
|
397
|
+
cmd = inp.get('command', '')
|
|
398
|
+
print(f'$ {cmd}')
|
|
399
|
+
" ses_SESSION_ID_HERE
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### 4e. Find Messages by Token Usage
|
|
403
|
+
|
|
404
|
+
```bash
|
|
405
|
+
# Find the most token-heavy messages across all sessions
|
|
406
|
+
python3 -c "
|
|
407
|
+
import json, glob
|
|
408
|
+
results = []
|
|
409
|
+
for f in glob.glob('/workspace/.local/share/opencode/storage/message/ses_*/msg_*.json'):
|
|
410
|
+
with open(f) as fh:
|
|
411
|
+
d = json.load(fh)
|
|
412
|
+
tokens = d.get('tokens', {})
|
|
413
|
+
total = tokens.get('input', 0) + tokens.get('output', 0) + tokens.get('reasoning', 0)
|
|
414
|
+
if total > 0:
|
|
415
|
+
results.append((total, d.get('sessionID', ''), d.get('id', ''), d.get('agent', '')))
|
|
416
|
+
for total, sid, mid, agent in sorted(results, reverse=True)[:15]:
|
|
417
|
+
print(f'{total:8d} tokens | {agent:20s} | {sid} | {mid}')
|
|
418
|
+
"
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### 4f. List All Tools Used in a Session
|
|
422
|
+
|
|
423
|
+
```bash
|
|
424
|
+
python3 -c "
|
|
425
|
+
import json, glob, sys
|
|
426
|
+
from collections import Counter
|
|
427
|
+
session_id = sys.argv[1]
|
|
428
|
+
tools = Counter()
|
|
429
|
+
for mf in glob.glob(f'/workspace/.local/share/opencode/storage/message/{session_id}/msg_*.json'):
|
|
430
|
+
with open(mf) as fh:
|
|
431
|
+
msg = json.load(fh)
|
|
432
|
+
for pf in glob.glob(f'/workspace/.local/share/opencode/storage/part/{msg[\"id\"]}/prt_*.json'):
|
|
433
|
+
with open(pf) as fh:
|
|
434
|
+
part = json.load(fh)
|
|
435
|
+
if part.get('type') == 'tool':
|
|
436
|
+
tools[part.get('tool', 'unknown')] += 1
|
|
437
|
+
for tool, count in tools.most_common():
|
|
438
|
+
print(f'{count:4d}x {tool}')
|
|
439
|
+
" ses_SESSION_ID_HERE
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
## 5. Semantic Search with LSS
|
|
445
|
+
|
|
446
|
+
For meaning-based queries — when you don't know the exact keywords — use Local Semantic Search (`lss`). It combines BM25 full-text + OpenAI embedding similarity.
|
|
447
|
+
|
|
448
|
+
### Search Session Content Semantically
|
|
449
|
+
|
|
450
|
+
Session JSON files aren't directly indexed by LSS (it indexes Desktop files and memory). But you can use LSS on any outputs that were saved to disk:
|
|
451
|
+
|
|
452
|
+
```bash
|
|
453
|
+
# Search Desktop for session outputs (agents write results here)
|
|
454
|
+
lss "landing page with dark theme" -p /workspace -k 10 --json
|
|
455
|
+
|
|
456
|
+
# Search agent memory for session-related knowledge
|
|
457
|
+
lss "what sessions were created for research tasks" -p /workspace/.kortix/ -k 5 --json
|
|
458
|
+
|
|
459
|
+
# Search a specific project directory an agent built
|
|
460
|
+
lss "authentication middleware" -p /workspace/myproject/ -k 5 --json
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
### Index Session Data for Semantic Search
|
|
464
|
+
|
|
465
|
+
To make session content searchable by meaning, you can index it:
|
|
466
|
+
|
|
467
|
+
```bash
|
|
468
|
+
# Index all session titles (create a searchable index file)
|
|
469
|
+
python3 -c "
|
|
470
|
+
import json, glob
|
|
471
|
+
with open('/tmp/session-index.txt', 'w') as out:
|
|
472
|
+
for f in sorted(glob.glob('/workspace/.local/share/opencode/storage/session/global/ses_*.json')):
|
|
473
|
+
with open(f) as fh:
|
|
474
|
+
d = json.load(fh)
|
|
475
|
+
out.write(f'{d[\"id\"]} | {d[\"title\"]}\n')
|
|
476
|
+
print('Written to /tmp/session-index.txt')
|
|
477
|
+
"
|
|
478
|
+
lss index /tmp/session-index.txt
|
|
479
|
+
lss "voice interface development" -p /tmp -k 5 --json
|
|
480
|
+
|
|
481
|
+
# Index all assistant text outputs for a session
|
|
482
|
+
python3 -c "
|
|
483
|
+
import json, glob, sys
|
|
484
|
+
session_id = sys.argv[1]
|
|
485
|
+
with open(f'/tmp/{session_id}-texts.txt', 'w') as out:
|
|
486
|
+
for mf in sorted(glob.glob(f'/workspace/.local/share/opencode/storage/message/{session_id}/msg_*.json')):
|
|
487
|
+
with open(mf) as fh:
|
|
488
|
+
msg = json.load(fh)
|
|
489
|
+
if msg.get('role') != 'assistant':
|
|
490
|
+
continue
|
|
491
|
+
for pf in sorted(glob.glob(f'/workspace/.local/share/opencode/storage/part/{msg[\"id\"]}/prt_*.json')):
|
|
492
|
+
with open(pf) as fh:
|
|
493
|
+
part = json.load(fh)
|
|
494
|
+
if part.get('type') == 'text' and part.get('text'):
|
|
495
|
+
out.write(part['text'] + '\n---\n')
|
|
496
|
+
print(f'Written to /tmp/{session_id}-texts.txt')
|
|
497
|
+
" ses_SESSION_ID_HERE
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### Combine Semantic + Deterministic
|
|
501
|
+
|
|
502
|
+
Best results come from combining both:
|
|
503
|
+
|
|
504
|
+
```bash
|
|
505
|
+
# Step 1: Semantic search to find relevant files/content
|
|
506
|
+
lss "database migration strategy" -p /workspace -k 5 --json
|
|
507
|
+
|
|
508
|
+
# Step 2: Deterministic grep for exact matches in those files
|
|
509
|
+
rg "migration" /workspace/project/src/ -l
|
|
510
|
+
|
|
511
|
+
# Step 3: Check which sessions created those files
|
|
512
|
+
rg "project/src" /workspace/.local/share/opencode/storage/part/ -r -l --type json
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
## 6. Comprehensive Session Query Script
|
|
518
|
+
|
|
519
|
+
This all-in-one Python script handles the most common query patterns:
|
|
520
|
+
|
|
521
|
+
```bash
|
|
522
|
+
# Save as /tmp/session-query.py and run with: python3 /tmp/session-query.py <command> [args]
|
|
523
|
+
|
|
524
|
+
python3 << 'PYEOF'
|
|
525
|
+
import json, glob, os, sys, time
|
|
526
|
+
from collections import Counter
|
|
527
|
+
from datetime import datetime
|
|
528
|
+
|
|
529
|
+
STORAGE = "/workspace/.local/share/opencode/storage"
|
|
530
|
+
SESSION_DIR = f"{STORAGE}/session/global"
|
|
531
|
+
MESSAGE_DIR = f"{STORAGE}/message"
|
|
532
|
+
PART_DIR = f"{STORAGE}/part"
|
|
533
|
+
TODO_DIR = f"{STORAGE}/todo"
|
|
534
|
+
|
|
535
|
+
def load_json(path):
|
|
536
|
+
with open(path) as f:
|
|
537
|
+
return json.load(f)
|
|
538
|
+
|
|
539
|
+
def all_sessions():
|
|
540
|
+
results = []
|
|
541
|
+
for f in glob.glob(f"{SESSION_DIR}/ses_*.json"):
|
|
542
|
+
results.append(load_json(f))
|
|
543
|
+
return sorted(results, key=lambda s: s["time"]["created"], reverse=True)
|
|
544
|
+
|
|
545
|
+
def fmt_age(ms):
|
|
546
|
+
secs = (time.time() * 1000 - ms) / 1000
|
|
547
|
+
if secs < 60: return f"{int(secs)}s"
|
|
548
|
+
if secs < 3600: return f"{int(secs/60)}m"
|
|
549
|
+
if secs < 86400: return f"{int(secs/3600)}h"
|
|
550
|
+
return f"{int(secs/86400)}d"
|
|
551
|
+
|
|
552
|
+
def cmd_list(args):
|
|
553
|
+
"""List all sessions with optional title filter"""
|
|
554
|
+
query = " ".join(args).lower() if args else ""
|
|
555
|
+
for s in all_sessions():
|
|
556
|
+
if query and query not in s["title"].lower():
|
|
557
|
+
continue
|
|
558
|
+
parent = " [child]" if s.get("parentID") else ""
|
|
559
|
+
print(f'{s["id"]} | {s["title"]:50s} | {fmt_age(s["time"]["created"])} ago{parent}')
|
|
560
|
+
|
|
561
|
+
def cmd_recent(args):
|
|
562
|
+
"""Sessions from the last N hours (default 24)"""
|
|
563
|
+
hours = int(args[0]) if args else 24
|
|
564
|
+
cutoff = (time.time() - hours * 3600) * 1000
|
|
565
|
+
for s in all_sessions():
|
|
566
|
+
if s["time"]["created"] > cutoff:
|
|
567
|
+
print(f'{s["id"]} | {s["title"]:50s} | {fmt_age(s["time"]["created"])} ago')
|
|
568
|
+
|
|
569
|
+
def cmd_cost(args):
|
|
570
|
+
"""Top sessions by cost"""
|
|
571
|
+
limit = int(args[0]) if args else 10
|
|
572
|
+
costs = {}
|
|
573
|
+
for f in glob.glob(f"{MESSAGE_DIR}/ses_*/msg_*.json"):
|
|
574
|
+
d = load_json(f)
|
|
575
|
+
sid = d.get("sessionID", "")
|
|
576
|
+
costs[sid] = costs.get(sid, 0) + d.get("cost", 0)
|
|
577
|
+
titles = {}
|
|
578
|
+
for s in all_sessions():
|
|
579
|
+
titles[s["id"]] = s["title"]
|
|
580
|
+
for sid, cost in sorted(costs.items(), key=lambda x: -x[1])[:limit]:
|
|
581
|
+
print(f'${cost:.4f} | {titles.get(sid, "???"):50s} | {sid}')
|
|
582
|
+
|
|
583
|
+
def cmd_tools(args):
|
|
584
|
+
"""Tools used in a session"""
|
|
585
|
+
if not args:
|
|
586
|
+
print("Usage: tools <session_id>"); return
|
|
587
|
+
sid = args[0]
|
|
588
|
+
tools = Counter()
|
|
589
|
+
for mf in glob.glob(f"{MESSAGE_DIR}/{sid}/msg_*.json"):
|
|
590
|
+
msg = load_json(mf)
|
|
591
|
+
for pf in glob.glob(f"{PART_DIR}/{msg['id']}/prt_*.json"):
|
|
592
|
+
part = load_json(pf)
|
|
593
|
+
if part.get("type") == "tool":
|
|
594
|
+
tools[part.get("tool", "?")] += 1
|
|
595
|
+
for tool, count in tools.most_common():
|
|
596
|
+
print(f"{count:4d}x {tool}")
|
|
597
|
+
|
|
598
|
+
def cmd_text(args):
|
|
599
|
+
"""All text output from a session"""
|
|
600
|
+
if not args:
|
|
601
|
+
print("Usage: text <session_id>"); return
|
|
602
|
+
sid = args[0]
|
|
603
|
+
for mf in sorted(glob.glob(f"{MESSAGE_DIR}/{sid}/msg_*.json")):
|
|
604
|
+
msg = load_json(mf)
|
|
605
|
+
role = msg.get("role", "?")
|
|
606
|
+
for pf in sorted(glob.glob(f"{PART_DIR}/{msg['id']}/prt_*.json")):
|
|
607
|
+
part = load_json(pf)
|
|
608
|
+
if part.get("type") == "text" and part.get("text"):
|
|
609
|
+
print(f"\n--- [{role}] ---")
|
|
610
|
+
print(part["text"][:2000])
|
|
611
|
+
|
|
612
|
+
def cmd_children(args):
|
|
613
|
+
"""Find all child sessions of a parent"""
|
|
614
|
+
if not args:
|
|
615
|
+
print("Usage: children <session_id>"); return
|
|
616
|
+
pid = args[0]
|
|
617
|
+
for s in all_sessions():
|
|
618
|
+
if s.get("parentID") == pid:
|
|
619
|
+
print(f'{s["id"]} | {s["title"]:50s} | {fmt_age(s["time"]["created"])} ago')
|
|
620
|
+
|
|
621
|
+
def cmd_agents(args):
|
|
622
|
+
"""Count sessions by agent"""
|
|
623
|
+
agents = Counter()
|
|
624
|
+
for f in glob.glob(f"{MESSAGE_DIR}/ses_*/msg_*.json"):
|
|
625
|
+
d = load_json(f)
|
|
626
|
+
if d.get("role") == "assistant":
|
|
627
|
+
agents[d.get("agent", "unknown")] += 1
|
|
628
|
+
for agent, count in agents.most_common():
|
|
629
|
+
print(f"{count:4d} messages | {agent}")
|
|
630
|
+
|
|
631
|
+
def cmd_search(args):
|
|
632
|
+
"""Search text parts for a keyword"""
|
|
633
|
+
if not args:
|
|
634
|
+
print("Usage: search <keyword>"); return
|
|
635
|
+
keyword = " ".join(args).lower()
|
|
636
|
+
seen_sessions = set()
|
|
637
|
+
for pf in glob.glob(f"{PART_DIR}/msg_*/prt_*.json"):
|
|
638
|
+
part = load_json(pf)
|
|
639
|
+
if part.get("type") == "text" and keyword in (part.get("text", "")).lower():
|
|
640
|
+
sid = part.get("sessionID", "")
|
|
641
|
+
if sid not in seen_sessions:
|
|
642
|
+
seen_sessions.add(sid)
|
|
643
|
+
sf = f"{SESSION_DIR}/{sid}.json"
|
|
644
|
+
title = load_json(sf)["title"] if os.path.exists(sf) else "???"
|
|
645
|
+
snippet = part["text"][:200].replace("\n", " ")
|
|
646
|
+
print(f'{sid} | {title}')
|
|
647
|
+
print(f' ...{snippet}...')
|
|
648
|
+
|
|
649
|
+
def cmd_stats(args):
|
|
650
|
+
"""Overall statistics"""
|
|
651
|
+
sessions = all_sessions()
|
|
652
|
+
msg_count = len(glob.glob(f"{MESSAGE_DIR}/ses_*/msg_*.json"))
|
|
653
|
+
part_count = len(glob.glob(f"{PART_DIR}/msg_*/prt_*.json"))
|
|
654
|
+
total_cost = sum(
|
|
655
|
+
load_json(f).get("cost", 0)
|
|
656
|
+
for f in glob.glob(f"{MESSAGE_DIR}/ses_*/msg_*.json")
|
|
657
|
+
)
|
|
658
|
+
children = sum(1 for s in sessions if s.get("parentID"))
|
|
659
|
+
print(f"Sessions: {len(sessions)} ({children} children)")
|
|
660
|
+
print(f"Messages: {msg_count}")
|
|
661
|
+
print(f"Parts: {part_count}")
|
|
662
|
+
print(f"Total cost: ${total_cost:.2f}")
|
|
663
|
+
if sessions:
|
|
664
|
+
oldest = min(s["time"]["created"] for s in sessions)
|
|
665
|
+
print(f"Oldest: {datetime.fromtimestamp(oldest/1000).isoformat()}")
|
|
666
|
+
newest = max(s["time"]["created"] for s in sessions)
|
|
667
|
+
print(f"Newest: {datetime.fromtimestamp(newest/1000).isoformat()}")
|
|
668
|
+
|
|
669
|
+
commands = {
|
|
670
|
+
"list": cmd_list, "recent": cmd_recent, "cost": cmd_cost,
|
|
671
|
+
"tools": cmd_tools, "text": cmd_text, "children": cmd_children,
|
|
672
|
+
"agents": cmd_agents, "search": cmd_search, "stats": cmd_stats,
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
if len(sys.argv) < 2 or sys.argv[1] not in commands:
|
|
676
|
+
print("Commands: " + ", ".join(commands.keys()))
|
|
677
|
+
sys.exit(1)
|
|
678
|
+
|
|
679
|
+
commands[sys.argv[1]](sys.argv[2:])
|
|
680
|
+
PYEOF
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
---
|
|
684
|
+
|
|
685
|
+
## 7. Quick Reference — Common Tasks
|
|
686
|
+
|
|
687
|
+
### "What's running right now?"
|
|
688
|
+
```bash
|
|
689
|
+
curl -s http://localhost:3111/session/status | python3 -m json.tool
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
### "List recent sessions"
|
|
693
|
+
```bash
|
|
694
|
+
curl -s http://localhost:3111/session | python3 -c "
|
|
695
|
+
import json,sys
|
|
696
|
+
for s in json.load(sys.stdin)[:15]:
|
|
697
|
+
print(f'{s[\"id\"]} | {s[\"title\"]}')"
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
### "What did session X do?"
|
|
701
|
+
```bash
|
|
702
|
+
curl -s "http://localhost:3111/session/SESSION_ID/message" | python3 -c "
|
|
703
|
+
import json,sys
|
|
704
|
+
for m in json.load(sys.stdin):
|
|
705
|
+
role = m['info']['role']
|
|
706
|
+
for p in m['parts']:
|
|
707
|
+
if p['type'] == 'text':
|
|
708
|
+
print(f'[{role}] {p[\"text\"][:300]}')"
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
### "Find sessions about topic X"
|
|
712
|
+
```bash
|
|
713
|
+
# Deterministic (exact match)
|
|
714
|
+
rg -i "topic" /workspace/.local/share/opencode/storage/session/global/ -l
|
|
715
|
+
|
|
716
|
+
# Semantic (meaning match)
|
|
717
|
+
lss "topic description" -p /workspace -k 10 --json
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
### "How much did I spend today?"
|
|
721
|
+
```bash
|
|
722
|
+
python3 -c "
|
|
723
|
+
import json, glob, time
|
|
724
|
+
cutoff = (time.time() - 86400) * 1000
|
|
725
|
+
total = 0
|
|
726
|
+
for f in glob.glob('/workspace/.local/share/opencode/storage/message/ses_*/msg_*.json'):
|
|
727
|
+
with open(f) as fh:
|
|
728
|
+
d = json.load(fh)
|
|
729
|
+
if d['time']['created'] > cutoff:
|
|
730
|
+
total += d.get('cost', 0)
|
|
731
|
+
print(f'Last 24h cost: \${total:.4f}')
|
|
732
|
+
"
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
### "Delete old sessions"
|
|
736
|
+
```bash
|
|
737
|
+
# Via API (safe — also cleans up messages/parts)
|
|
738
|
+
curl -s -X DELETE "http://localhost:3111/session/SESSION_ID"
|
|
739
|
+
|
|
740
|
+
# Bulk delete sessions older than 7 days
|
|
741
|
+
python3 -c "
|
|
742
|
+
import json, glob, time, subprocess
|
|
743
|
+
cutoff = (time.time() - 7 * 86400) * 1000
|
|
744
|
+
for f in glob.glob('/workspace/.local/share/opencode/storage/session/global/ses_*.json'):
|
|
745
|
+
with open(f) as fh:
|
|
746
|
+
d = json.load(fh)
|
|
747
|
+
if d['time']['created'] < cutoff:
|
|
748
|
+
sid = d['id']
|
|
749
|
+
r = subprocess.run(['curl', '-s', '-X', 'DELETE', f'http://localhost:3111/session/{sid}'])
|
|
750
|
+
print(f'Deleted {sid} | {d[\"title\"]}')
|
|
751
|
+
"
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
---
|
|
755
|
+
|
|
756
|
+
## 8. Session Lifecycle
|
|
757
|
+
|
|
758
|
+
```
|
|
759
|
+
CREATE (POST /session)
|
|
760
|
+
│
|
|
761
|
+
├── PROMPT (POST /session/{id}/prompt_async) ← fire-and-forget
|
|
762
|
+
│ │
|
|
763
|
+
│ ├── status: busy (GET /session/status shows it)
|
|
764
|
+
│ │ │
|
|
765
|
+
│ │ ├── Tool calls happen (parts with type:"tool")
|
|
766
|
+
│ │ ├── Text generated (parts with type:"text")
|
|
767
|
+
│ │ ├── Subagents may spawn (children with parentID)
|
|
768
|
+
│ │ │
|
|
769
|
+
│ │ └── finish: "stop" or "tool-calls"
|
|
770
|
+
│ │
|
|
771
|
+
│ └── status: idle (session.idle SSE event)
|
|
772
|
+
│
|
|
773
|
+
├── PROMPT again (same session, new conversation turn)
|
|
774
|
+
│
|
|
775
|
+
├── ABORT (POST /session/{id}/abort) ← stops current work
|
|
776
|
+
│
|
|
777
|
+
└── DELETE (DELETE /session/{id}) ← permanent removal
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
### Status Transitions
|
|
781
|
+
|
|
782
|
+
- **idle** → `prompt_async` → **busy** → completes → **idle**
|
|
783
|
+
- **busy** → `abort` → **idle** (aborted)
|
|
784
|
+
- Any state → `DELETE` → gone
|
|
785
|
+
|
|
786
|
+
### Subagent Spawning
|
|
787
|
+
|
|
788
|
+
When an agent uses the `task` tool, OpenCode creates a child session:
|
|
789
|
+
- Child session has `parentID` pointing to parent
|
|
790
|
+
- Child may have restricted `permission` (e.g. deny `todowrite`)
|
|
791
|
+
- Use `GET /session/{id}/children` or grep `parentID` in session files
|
|
792
|
+
- Children are independent sessions — they can be queried/aborted separately
|
|
793
|
+
|
|
794
|
+
---
|
|
795
|
+
|
|
796
|
+
## 9. Tips & Gotchas
|
|
797
|
+
|
|
798
|
+
1. **API has no search/filter params** — `GET /session` returns ALL sessions. Filter client-side or use file grep.
|
|
799
|
+
2. **Messages endpoint returns all messages** — No pagination. For large sessions, reading files directly is faster.
|
|
800
|
+
3. **Part files are the largest data** — 58MB+ across all sessions. The `text` and `tool.state.output` fields hold the bulk.
|
|
801
|
+
4. **Tool outputs over ~50KB** are stored separately in `tool-output/tool_{id}` as plain text, referenced by ID.
|
|
802
|
+
5. **Timestamps are Unix milliseconds** — divide by 1000 for Python `time.time()` comparisons.
|
|
803
|
+
6. **Session IDs sort chronologically** — the hex portion of the ID encodes creation time.
|
|
804
|
+
7. **The bundled ripgrep is at** `/workspace/.local/share/opencode/bin/rg` — use it for fast searches if system `rg` isn't available.
|
|
805
|
+
8. **Prompt history** (all prompts ever entered) is at `/workspace/.local/state/opencode/prompt-history.jsonl`.
|
|
806
|
+
9. **The `POST /session/{id}/message` endpoint is synchronous** — it blocks until the full response is generated. Use `prompt_async` for fire-and-forget.
|
|
807
|
+
10. **CORS is enabled** — the API accepts requests from any origin with GET/POST/PUT/PATCH/DELETE.
|