@kortix/sandbox 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. package/config/customize.sh +143 -0
  2. package/config/kortix-env-setup.sh +25 -0
  3. package/kortix-master/package.json +22 -0
  4. package/kortix-master/src/config.ts +22 -0
  5. package/kortix-master/src/index.ts +44 -0
  6. package/kortix-master/src/routes/env.ts +65 -0
  7. package/kortix-master/src/routes/proxy.ts +108 -0
  8. package/kortix-master/src/routes/update.ts +185 -0
  9. package/kortix-master/src/services/proxy.ts +43 -0
  10. package/kortix-master/src/services/secret-store.ts +156 -0
  11. package/kortix-master/tsconfig.json +14 -0
  12. package/opencode/agents/kortix-browser.md +142 -0
  13. package/opencode/agents/kortix-build.md +62 -0
  14. package/opencode/agents/kortix-explore.md +66 -0
  15. package/opencode/agents/kortix-image-gen.md +33 -0
  16. package/opencode/agents/kortix-main.md +450 -0
  17. package/opencode/agents/kortix-plan.md +100 -0
  18. package/opencode/agents/kortix-research.md +84 -0
  19. package/opencode/agents/kortix-sheets.md +61 -0
  20. package/opencode/agents/kortix-slides.md +64 -0
  21. package/opencode/agents/kortix-web-dev.md +572 -0
  22. package/opencode/commands/email.md +36 -0
  23. package/opencode/commands/init.md +43 -0
  24. package/opencode/commands/journal.md +44 -0
  25. package/opencode/commands/memory-init.md +81 -0
  26. package/opencode/commands/memory-search.md +50 -0
  27. package/opencode/commands/memory-status.md +56 -0
  28. package/opencode/commands/research.md +36 -0
  29. package/opencode/commands/search.md +38 -0
  30. package/opencode/commands/slides.md +32 -0
  31. package/opencode/commands/spreadsheet.md +30 -0
  32. package/opencode/memory.json +37 -0
  33. package/opencode/ocx.jsonc +10 -0
  34. package/opencode/opencode.jsonc +103 -0
  35. package/opencode/package.json +25 -0
  36. package/opencode/patches/apply.sh +19 -0
  37. package/opencode/patches/opencode-pty-spawn.txt +49 -0
  38. package/opencode/plugin/background-agents.ts.disabled +483 -0
  39. package/opencode/plugin/kdco-primitives/get-project-id.ts +172 -0
  40. package/opencode/plugin/kdco-primitives/index.ts +26 -0
  41. package/opencode/plugin/kdco-primitives/log-warn.ts +51 -0
  42. package/opencode/plugin/kdco-primitives/mutex.ts +122 -0
  43. package/opencode/plugin/kdco-primitives/shell.ts +138 -0
  44. package/opencode/plugin/kdco-primitives/temp.ts +36 -0
  45. package/opencode/plugin/kdco-primitives/terminal-detect.ts +34 -0
  46. package/opencode/plugin/kdco-primitives/types.ts +13 -0
  47. package/opencode/plugin/kdco-primitives/with-timeout.ts +84 -0
  48. package/opencode/plugin/memory.ts +306 -0
  49. package/opencode/plugin/worktree/state.ts +412 -0
  50. package/opencode/plugin/worktree/terminal.ts +1002 -0
  51. package/opencode/plugin/worktree.ts +861 -0
  52. package/opencode/skills/KORTIX-browser/SKILL.md +478 -0
  53. package/opencode/skills/KORTIX-cron-triggers/SKILL.md +173 -0
  54. package/opencode/skills/KORTIX-deep-research/SKILL.md +278 -0
  55. package/opencode/skills/KORTIX-docx/SKILL.md +398 -0
  56. package/opencode/skills/KORTIX-docx/scripts/__init__.py +1 -0
  57. package/opencode/skills/KORTIX-docx/scripts/accept_changes.py +104 -0
  58. package/opencode/skills/KORTIX-docx/scripts/comment.py +244 -0
  59. package/opencode/skills/KORTIX-docx/scripts/office/helpers/__init__.py +0 -0
  60. package/opencode/skills/KORTIX-docx/scripts/office/helpers/merge_runs.py +199 -0
  61. package/opencode/skills/KORTIX-docx/scripts/office/helpers/simplify_redlines.py +197 -0
  62. package/opencode/skills/KORTIX-docx/scripts/office/pack.py +159 -0
  63. package/opencode/skills/KORTIX-docx/scripts/office/soffice.py +183 -0
  64. package/opencode/skills/KORTIX-docx/scripts/office/unpack.py +132 -0
  65. package/opencode/skills/KORTIX-docx/scripts/office/validate.py +111 -0
  66. package/opencode/skills/KORTIX-docx/scripts/office/validators/__init__.py +15 -0
  67. package/opencode/skills/KORTIX-docx/scripts/office/validators/base.py +847 -0
  68. package/opencode/skills/KORTIX-docx/scripts/office/validators/docx.py +446 -0
  69. package/opencode/skills/KORTIX-docx/scripts/office/validators/pptx.py +275 -0
  70. package/opencode/skills/KORTIX-docx/scripts/office/validators/redlining.py +247 -0
  71. package/opencode/skills/KORTIX-docx/scripts/render_docx.py +179 -0
  72. package/opencode/skills/KORTIX-docx/scripts/templates/comments.xml +3 -0
  73. package/opencode/skills/KORTIX-docx/scripts/templates/commentsExtended.xml +3 -0
  74. package/opencode/skills/KORTIX-docx/scripts/templates/commentsExtensible.xml +3 -0
  75. package/opencode/skills/KORTIX-docx/scripts/templates/commentsIds.xml +3 -0
  76. package/opencode/skills/KORTIX-docx/scripts/templates/people.xml +3 -0
  77. package/opencode/skills/KORTIX-domain-research/SKILL.md +96 -0
  78. package/opencode/skills/KORTIX-domain-research/scripts/domain-lookup.py +810 -0
  79. package/opencode/skills/KORTIX-elevenlabs/SKILL.md +230 -0
  80. package/opencode/skills/KORTIX-elevenlabs/scripts/tts.py +389 -0
  81. package/opencode/skills/KORTIX-email/SKILL.md +145 -0
  82. package/opencode/skills/KORTIX-legal-writer/SKILL.md +409 -0
  83. package/opencode/skills/KORTIX-legal-writer/references/bluebook.md +152 -0
  84. package/opencode/skills/KORTIX-legal-writer/references/document-types.md +416 -0
  85. package/opencode/skills/KORTIX-legal-writer/scripts/courtlistener.py +291 -0
  86. package/opencode/skills/KORTIX-legal-writer/scripts/ecfr_lookup.py +299 -0
  87. package/opencode/skills/KORTIX-legal-writer/scripts/verify-legal.py +507 -0
  88. package/opencode/skills/KORTIX-logo-creator/SKILL.md +293 -0
  89. package/opencode/skills/KORTIX-logo-creator/references/prompt-patterns.md +134 -0
  90. package/opencode/skills/KORTIX-logo-creator/scripts/compose_logo.py +406 -0
  91. package/opencode/skills/KORTIX-logo-creator/scripts/create_logo_sheet.py +258 -0
  92. package/opencode/skills/KORTIX-logo-creator/scripts/remove_bg.py +96 -0
  93. package/opencode/skills/KORTIX-memory/SKILL.md +261 -0
  94. package/opencode/skills/KORTIX-memory/scripts/export-sessions.py +409 -0
  95. package/opencode/skills/KORTIX-paper-creator/SKILL.md +549 -0
  96. package/opencode/skills/KORTIX-paper-creator/assets/template.tex +101 -0
  97. package/opencode/skills/KORTIX-paper-creator/scripts/compile.sh +177 -0
  98. package/opencode/skills/KORTIX-paper-creator/scripts/openalex_to_bibtex.py +220 -0
  99. package/opencode/skills/KORTIX-paper-creator/scripts/verify.sh +354 -0
  100. package/opencode/skills/KORTIX-paper-search/SKILL.md +418 -0
  101. package/opencode/skills/KORTIX-pdf/SKILL.md +232 -0
  102. package/opencode/skills/KORTIX-pdf/forms.md +36 -0
  103. package/opencode/skills/KORTIX-pdf/reference.md +105 -0
  104. package/opencode/skills/KORTIX-pdf/scripts/check_bounding_boxes.py +65 -0
  105. package/opencode/skills/KORTIX-pdf/scripts/check_fillable_fields.py +11 -0
  106. package/opencode/skills/KORTIX-pdf/scripts/convert_pdf_to_images.py +33 -0
  107. package/opencode/skills/KORTIX-pdf/scripts/create_validation_image.py +37 -0
  108. package/opencode/skills/KORTIX-pdf/scripts/extract_form_field_info.py +122 -0
  109. package/opencode/skills/KORTIX-pdf/scripts/extract_form_structure.py +115 -0
  110. package/opencode/skills/KORTIX-pdf/scripts/fill_fillable_fields.py +98 -0
  111. package/opencode/skills/KORTIX-pdf/scripts/fill_pdf_form_with_annotations.py +107 -0
  112. package/opencode/skills/KORTIX-plan/SKILL.md +228 -0
  113. package/opencode/skills/KORTIX-presentation-viewer/SKILL.md +87 -0
  114. package/opencode/skills/KORTIX-presentation-viewer/serve.ts +136 -0
  115. package/opencode/skills/KORTIX-presentation-viewer/viewer.html +559 -0
  116. package/opencode/skills/KORTIX-presentations/SKILL.md +344 -0
  117. package/opencode/skills/KORTIX-remotion/SKILL.md +56 -0
  118. package/opencode/skills/KORTIX-remotion/rules/3d.md +86 -0
  119. package/opencode/skills/KORTIX-remotion/rules/animations.md +29 -0
  120. package/opencode/skills/KORTIX-remotion/rules/assets.md +78 -0
  121. package/opencode/skills/KORTIX-remotion/rules/audio-visualization.md +198 -0
  122. package/opencode/skills/KORTIX-remotion/rules/audio.md +169 -0
  123. package/opencode/skills/KORTIX-remotion/rules/calculate-metadata.md +104 -0
  124. package/opencode/skills/KORTIX-remotion/rules/can-decode.md +75 -0
  125. package/opencode/skills/KORTIX-remotion/rules/charts.md +120 -0
  126. package/opencode/skills/KORTIX-remotion/rules/compositions.md +141 -0
  127. package/opencode/skills/KORTIX-remotion/rules/display-captions.md +184 -0
  128. package/opencode/skills/KORTIX-remotion/rules/extract-frames.md +229 -0
  129. package/opencode/skills/KORTIX-remotion/rules/ffmpeg.md +38 -0
  130. package/opencode/skills/KORTIX-remotion/rules/fonts.md +152 -0
  131. package/opencode/skills/KORTIX-remotion/rules/get-audio-duration.md +58 -0
  132. package/opencode/skills/KORTIX-remotion/rules/get-video-dimensions.md +68 -0
  133. package/opencode/skills/KORTIX-remotion/rules/get-video-duration.md +58 -0
  134. package/opencode/skills/KORTIX-remotion/rules/gifs.md +141 -0
  135. package/opencode/skills/KORTIX-remotion/rules/images.md +130 -0
  136. package/opencode/skills/KORTIX-remotion/rules/import-srt-captions.md +69 -0
  137. package/opencode/skills/KORTIX-remotion/rules/light-leaks.md +73 -0
  138. package/opencode/skills/KORTIX-remotion/rules/lottie.md +68 -0
  139. package/opencode/skills/KORTIX-remotion/rules/maps.md +401 -0
  140. package/opencode/skills/KORTIX-remotion/rules/measuring-dom-nodes.md +35 -0
  141. package/opencode/skills/KORTIX-remotion/rules/measuring-text.md +143 -0
  142. package/opencode/skills/KORTIX-remotion/rules/parameters.md +98 -0
  143. package/opencode/skills/KORTIX-remotion/rules/sequencing.md +118 -0
  144. package/opencode/skills/KORTIX-remotion/rules/subtitles.md +36 -0
  145. package/opencode/skills/KORTIX-remotion/rules/tailwind.md +11 -0
  146. package/opencode/skills/KORTIX-remotion/rules/text-animations.md +20 -0
  147. package/opencode/skills/KORTIX-remotion/rules/timing.md +179 -0
  148. package/opencode/skills/KORTIX-remotion/rules/transcribe-captions.md +70 -0
  149. package/opencode/skills/KORTIX-remotion/rules/transitions.md +197 -0
  150. package/opencode/skills/KORTIX-remotion/rules/transparent-videos.md +106 -0
  151. package/opencode/skills/KORTIX-remotion/rules/trimming.md +53 -0
  152. package/opencode/skills/KORTIX-remotion/rules/videos.md +171 -0
  153. package/opencode/skills/KORTIX-secrets/SKILL.md +280 -0
  154. package/opencode/skills/KORTIX-semantic-search/SKILL.md +213 -0
  155. package/opencode/skills/KORTIX-session-search/SKILL.md +807 -0
  156. package/opencode/skills/KORTIX-session-search/Untitled +1 -0
  157. package/opencode/skills/KORTIX-skill-creator/SKILL.md +163 -0
  158. package/opencode/skills/KORTIX-web-research/SKILL.md +69 -0
  159. package/opencode/skills/KORTIX-xlsx/LICENSE.txt +30 -0
  160. package/opencode/skills/KORTIX-xlsx/SKILL.md +549 -0
  161. package/opencode/skills/KORTIX-xlsx/scripts/office/helpers/__init__.py +0 -0
  162. package/opencode/skills/KORTIX-xlsx/scripts/office/helpers/merge_runs.py +199 -0
  163. package/opencode/skills/KORTIX-xlsx/scripts/office/helpers/simplify_redlines.py +197 -0
  164. package/opencode/skills/KORTIX-xlsx/scripts/office/pack.py +159 -0
  165. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  166. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  167. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  168. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  169. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  170. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  171. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  172. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  173. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  174. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  175. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  176. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  177. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  178. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  179. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  180. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  181. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  182. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  183. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  184. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  185. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  186. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  187. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  188. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  189. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  190. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  191. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  192. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  193. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  194. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  195. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  196. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/mce/mc.xsd +75 -0
  197. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
  198. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
  199. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
  200. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
  201. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
  202. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  203. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
  204. package/opencode/skills/KORTIX-xlsx/scripts/office/soffice.py +183 -0
  205. package/opencode/skills/KORTIX-xlsx/scripts/office/unpack.py +132 -0
  206. package/opencode/skills/KORTIX-xlsx/scripts/office/validate.py +111 -0
  207. package/opencode/skills/KORTIX-xlsx/scripts/office/validators/__init__.py +15 -0
  208. package/opencode/skills/KORTIX-xlsx/scripts/office/validators/base.py +847 -0
  209. package/opencode/skills/KORTIX-xlsx/scripts/office/validators/docx.py +446 -0
  210. package/opencode/skills/KORTIX-xlsx/scripts/office/validators/pptx.py +275 -0
  211. package/opencode/skills/KORTIX-xlsx/scripts/office/validators/redlining.py +247 -0
  212. package/opencode/skills/KORTIX-xlsx/scripts/recalc.py +184 -0
  213. package/opencode/tools/image-gen.ts +342 -0
  214. package/opencode/tools/image-search.ts +190 -0
  215. package/opencode/tools/memory-get.ts +168 -0
  216. package/opencode/tools/memory-search.ts +247 -0
  217. package/opencode/tools/presentation-gen.ts +723 -0
  218. package/opencode/tools/scrape-webpage.ts +115 -0
  219. package/opencode/tools/scripts/.python-version +1 -0
  220. package/opencode/tools/scripts/convert_pdf.py +184 -0
  221. package/opencode/tools/scripts/convert_pptx.py +562 -0
  222. package/opencode/tools/scripts/pyproject.toml +11 -0
  223. package/opencode/tools/scripts/uv.lock +287 -0
  224. package/opencode/tools/scripts/validate_slide.py +74 -0
  225. package/opencode/tools/show-user.ts +217 -0
  226. package/opencode/tools/tests/e2e-presentation-fix.ts +277 -0
  227. package/opencode/tools/tests/image-gen.test.ts +215 -0
  228. package/opencode/tools/tests/image-search.test.ts +125 -0
  229. package/opencode/tools/tests/memory-system-benchmark.ts +1076 -0
  230. package/opencode/tools/tests/presentation-gen.test.ts +389 -0
  231. package/opencode/tools/tests/scrape-webpage.test.ts +74 -0
  232. package/opencode/tools/tests/show-user.test.ts +241 -0
  233. package/opencode/tools/tests/video-gen.test.ts +110 -0
  234. package/opencode/tools/tests/web-search.test.ts +106 -0
  235. package/opencode/tools/video-gen.ts +200 -0
  236. package/opencode/tools/web-search.ts +153 -0
  237. package/opencode/tsconfig.json +29 -0
  238. package/package.json +36 -0
  239. package/patch-agent-browser.js +100 -0
  240. package/postinstall.sh +88 -0
  241. package/services/KORTIX-presentation-viewer/run +37 -0
  242. package/services/agent-browser-viewer/run +48 -0
  243. package/services/kortix-master/run +16 -0
  244. package/services/lss-sync/run +22 -0
  245. package/services/opencode-serve/run +25 -0
  246. package/services/opencode-web/run +21 -0
@@ -0,0 +1,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.