@scrylog/cli 0.3.0 → 0.3.2

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 (195) hide show
  1. package/dist/assets/highlights-eq9cgrbb.scm +604 -0
  2. package/dist/assets/highlights-ghv9g403.scm +205 -0
  3. package/dist/assets/highlights-hk7bwhj4.scm +284 -0
  4. package/dist/assets/highlights-r812a2qc.scm +150 -0
  5. package/dist/assets/highlights-x6tmsnaa.scm +115 -0
  6. package/dist/assets/injections-73j83es3.scm +27 -0
  7. package/dist/assets/parser.worker-xyhf83rf.js +4563 -0
  8. package/dist/assets/tree-sitter-3jzf13jk.wasm +0 -0
  9. package/dist/assets/tree-sitter-javascript-nd0q4pe9.wasm +0 -0
  10. package/dist/assets/tree-sitter-markdown-411r6y9b.wasm +0 -0
  11. package/dist/assets/tree-sitter-markdown_inline-j5349f42.wasm +0 -0
  12. package/dist/assets/tree-sitter-typescript-zxjzwt75.wasm +0 -0
  13. package/dist/assets/tree-sitter-zig-e78zbjpm.wasm +0 -0
  14. package/dist/cli/index.js +499 -0
  15. package/dist/cli/index.js.map +61 -0
  16. package/dist/daemon/index.js +670 -0
  17. package/dist/daemon/index.js.map +1475 -0
  18. package/dist/hooks/claude.js +6 -0
  19. package/dist/hooks/claude.js.map +11 -0
  20. package/dist/hooks/codex.js +10 -0
  21. package/dist/hooks/codex.js.map +11 -0
  22. package/dist/mcp/index.js +100 -0
  23. package/dist/mcp/index.js.map +231 -0
  24. package/dist/oc/index.js +21 -0
  25. package/dist/oc/index.js.map +10 -0
  26. package/package.json +16 -13
  27. package/src/api/docs/sessions.keys.ts +0 -6
  28. package/src/api/docs/sessions.ts +0 -82
  29. package/src/api/docs/system.keys.ts +0 -5
  30. package/src/api/docs/system.ts +0 -69
  31. package/src/api/docs/usage.keys.ts +0 -3
  32. package/src/api/docs/usage.ts +0 -24
  33. package/src/api/docs/webhooks.keys.ts +0 -5
  34. package/src/api/docs/webhooks.ts +0 -78
  35. package/src/api/index.ts +0 -18
  36. package/src/api/lib/auth.ts +0 -43
  37. package/src/api/lib/errors.ts +0 -28
  38. package/src/api/lib/route.ts +0 -18
  39. package/src/api/models/claude.ts +0 -4
  40. package/src/api/models/codex.ts +0 -4
  41. package/src/api/models/opencode.ts +0 -11
  42. package/src/api/routes/sessions.ts +0 -51
  43. package/src/api/routes/system.ts +0 -106
  44. package/src/api/routes/usage.ts +0 -15
  45. package/src/api/routes/webhooks/claude.ts +0 -149
  46. package/src/api/routes/webhooks/codex.ts +0 -175
  47. package/src/api/routes/webhooks/opencode.ts +0 -112
  48. package/src/api/routes.ts +0 -93
  49. package/src/api/schemas/common.ts +0 -29
  50. package/src/api/schemas/sessions.ts +0 -86
  51. package/src/api/schemas/usage.ts +0 -51
  52. package/src/api/schemas/webhooks/claude.ts +0 -32
  53. package/src/api/schemas/webhooks/codex.ts +0 -34
  54. package/src/api/schemas/webhooks/opencode.ts +0 -29
  55. package/src/api/services/claude-session.ts +0 -9
  56. package/src/api/services/codex-session.ts +0 -8
  57. package/src/api/services/opencode-session.ts +0 -73
  58. package/src/classifier.ts +0 -206
  59. package/src/cli/commands.ts +0 -714
  60. package/src/cli/index.ts +0 -18
  61. package/src/cli/program.ts +0 -175
  62. package/src/cli/prompt.ts +0 -44
  63. package/src/daemon/cli.ts +0 -27
  64. package/src/daemon/collectors/claude/README.md +0 -108
  65. package/src/daemon/collectors/claude/collector.ts +0 -423
  66. package/src/daemon/collectors/claude/discovery.ts +0 -127
  67. package/src/daemon/collectors/claude/hooks.ts +0 -181
  68. package/src/daemon/collectors/claude/index.ts +0 -8
  69. package/src/daemon/collectors/claude/jsonl.ts +0 -85
  70. package/src/daemon/collectors/claude/transcript.ts +0 -246
  71. package/src/daemon/collectors/codex/README.md +0 -147
  72. package/src/daemon/collectors/codex/collector.ts +0 -661
  73. package/src/daemon/collectors/codex/discovery.ts +0 -104
  74. package/src/daemon/collectors/codex/hooks.ts +0 -179
  75. package/src/daemon/collectors/codex/index.ts +0 -7
  76. package/src/daemon/collectors/codex/jsonl.ts +0 -108
  77. package/src/daemon/collectors/codex/transcript.ts +0 -299
  78. package/src/daemon/collectors/git.ts +0 -353
  79. package/src/daemon/collectors/herdr/collector.ts +0 -128
  80. package/src/daemon/collectors/herdr/index.ts +0 -2
  81. package/src/daemon/collectors/herdr/normalization.ts +0 -111
  82. package/src/daemon/collectors/opencode/README.md +0 -106
  83. package/src/daemon/collectors/opencode/collector.ts +0 -545
  84. package/src/daemon/collectors/opencode/discovery.ts +0 -257
  85. package/src/daemon/collectors/opencode/index.ts +0 -5
  86. package/src/daemon/collectors/opencode/session-builder.ts +0 -59
  87. package/src/daemon/collectors/opencode-db.ts +0 -294
  88. package/src/daemon/collectors/process.ts +0 -201
  89. package/src/daemon/collectors/tmux/README.md +0 -128
  90. package/src/daemon/collectors/tmux/collector.ts +0 -492
  91. package/src/daemon/collectors/tmux/enrichment.ts +0 -242
  92. package/src/daemon/collectors/tmux/index.ts +0 -4
  93. package/src/daemon/collectors/tmux/parse.ts +0 -128
  94. package/src/daemon/collectors/usage.ts +0 -259
  95. package/src/daemon/index.ts +0 -209
  96. package/src/daemon/scheduler.ts +0 -86
  97. package/src/daemon/store.ts +0 -630
  98. package/src/detect.ts +0 -143
  99. package/src/hooks/claude.ts +0 -596
  100. package/src/hooks/codex.ts +0 -517
  101. package/src/legacy/classifier.ts +0 -215
  102. package/src/legacy/detect.ts +0 -143
  103. package/src/mcp/client-server.ts +0 -21
  104. package/src/mcp/http-handler.ts +0 -126
  105. package/src/mcp/index.ts +0 -61
  106. package/src/mcp/resources/config-current.ts +0 -19
  107. package/src/mcp/resources/index.ts +0 -11
  108. package/src/mcp/resources/sessions-active.ts +0 -19
  109. package/src/mcp/resources/sessions-all.ts +0 -19
  110. package/src/mcp/schemas/sessions.ts +0 -3
  111. package/src/mcp/server.ts +0 -17
  112. package/src/mcp/services/api-adapter.ts +0 -48
  113. package/src/mcp/services/data-source.ts +0 -22
  114. package/src/mcp/services/store-adapter.ts +0 -40
  115. package/src/mcp/tools/get-active-sessions.ts +0 -16
  116. package/src/mcp/tools/get-session-children.ts +0 -24
  117. package/src/mcp/tools/get-session-messages.ts +0 -24
  118. package/src/mcp/tools/get-session.ts +0 -23
  119. package/src/mcp/tools/index.ts +0 -15
  120. package/src/mcp/tools/list-sessions.ts +0 -16
  121. package/src/oc/index.ts +0 -302
  122. package/src/service/manager.ts +0 -284
  123. package/src/shared/config.ts +0 -511
  124. package/src/shared/ipc.ts +0 -80
  125. package/src/shared/logger.ts +0 -248
  126. package/src/shared/paths.ts +0 -73
  127. package/src/shared/platform.ts +0 -110
  128. package/src/shared/session-exclusions.ts +0 -5
  129. package/src/shared/session-view.ts +0 -302
  130. package/src/shared/terminal-capabilities.ts +0 -130
  131. package/src/shared/types.ts +0 -330
  132. package/src/shared/utils.ts +0 -339
  133. package/src/shared/version.ts +0 -44
  134. package/src/tui/App.tsx +0 -240
  135. package/src/tui/client.ts +0 -163
  136. package/src/tui/components/BindingsModal.tsx +0 -82
  137. package/src/tui/components/DashboardHeader.tsx +0 -256
  138. package/src/tui/components/KeyBar.tsx +0 -67
  139. package/src/tui/components/SessionDetailModal.tsx +0 -169
  140. package/src/tui/components/SessionsView.tsx +0 -184
  141. package/src/tui/components/TabBar.tsx +0 -40
  142. package/src/tui/components/UsageSummaryPane.tsx +0 -272
  143. package/src/tui/components/UsageView.tsx +0 -242
  144. package/src/tui/components/tool-icons.ts +0 -24
  145. package/src/tui/index.tsx +0 -106
  146. package/src/tui/keybindings.ts +0 -49
  147. package/src/tui/model.ts +0 -295
  148. package/src/tui/state.ts +0 -77
  149. package/src/tui/theme.ts +0 -110
  150. package/src/types.ts +0 -113
  151. package/src/usage/README.md +0 -323
  152. package/src/usage/api.ts +0 -99
  153. package/src/usage/client.ts +0 -3
  154. package/src/usage/clients/anthropic-oauth-usage.ts +0 -258
  155. package/src/usage/clients/anthropic-web-usage.ts +0 -329
  156. package/src/usage/clients/claude-cli-probe.ts +0 -513
  157. package/src/usage/clients/codex-app-server.ts +0 -354
  158. package/src/usage/clients/codex-oauth-usage.ts +0 -240
  159. package/src/usage/clients/opencode-go-api.ts +0 -100
  160. package/src/usage/clients/opencode-go-usage.ts +0 -577
  161. package/src/usage/errors.ts +0 -33
  162. package/src/usage/index.ts +0 -20
  163. package/src/usage/local/opencode-go-local.ts +0 -194
  164. package/src/usage/mappers.ts +0 -282
  165. package/src/usage/planner.ts +0 -118
  166. package/src/usage/providers/claude.ts +0 -233
  167. package/src/usage/providers/codex.ts +0 -115
  168. package/src/usage/providers/opencode-go.ts +0 -115
  169. package/src/usage/resolver.ts +0 -4
  170. package/src/usage/resolvers/browser-cookies.ts +0 -808
  171. package/src/usage/resolvers/claude-auth.ts +0 -208
  172. package/src/usage/resolvers/claude-cli.ts +0 -24
  173. package/src/usage/resolvers/claude-desktop-auth.ts +0 -170
  174. package/src/usage/resolvers/claude-web.ts +0 -82
  175. package/src/usage/resolvers/codex-auth.ts +0 -174
  176. package/src/usage/resolvers/keychain-coordinator.ts +0 -397
  177. package/src/usage/resolvers/opencode-go-web.ts +0 -55
  178. package/src/usage/types.ts +0 -225
  179. package/src/usage/web-session-cache.ts +0 -37
  180. /package/{src/tui → dist/cli}/assets/providers/SOURCES.md +0 -0
  181. /package/{src/tui → dist/cli}/assets/providers/claude-terminal.png +0 -0
  182. /package/{src/tui → dist/cli}/assets/providers/claude-terminal.svg +0 -0
  183. /package/{src/tui → dist/cli}/assets/providers/claude.png +0 -0
  184. /package/{src/tui → dist/cli}/assets/providers/copilot.png +0 -0
  185. /package/{src/tui → dist/cli}/assets/providers/copilot.svg +0 -0
  186. /package/{src/tui → dist/cli}/assets/providers/openai-terminal.png +0 -0
  187. /package/{src/tui → dist/cli}/assets/providers/openai-terminal.svg +0 -0
  188. /package/{src/tui → dist/cli}/assets/providers/openai.png +0 -0
  189. /package/{src/tui → dist/cli}/assets/providers/opencode-terminal.png +0 -0
  190. /package/{src/tui → dist/cli}/assets/providers/opencode-terminal.svg +0 -0
  191. /package/{src/tui → dist/cli}/assets/providers/opencode.png +0 -0
  192. /package/{src/tui → dist/cli}/assets/providers/shell.png +0 -0
  193. /package/{src/tui → dist/cli}/assets/providers/shell.svg +0 -0
  194. /package/{src/tui → dist/cli}/assets/providers/unknown.png +0 -0
  195. /package/{src/tui → dist/cli}/assets/providers/unknown.svg +0 -0
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+ var D=Object.create;var{getPrototypeOf:I,defineProperty:Q,getOwnPropertyNames:$}=Object;var V=Object.prototype.hasOwnProperty;function X(q){return this[q]}var h=(q,y,z)=>{var E=$(y);for(let B of E)if(!V.call(q,B)&&B!=="default")Q(q,B,{get:X.bind(y,B),enumerable:!0});if(z){for(let B of E)if(!V.call(z,B)&&B!=="default")Q(z,B,{get:X.bind(y,B),enumerable:!0});return z}},T,U,A=(q,y,z)=>{var E=q!=null&&typeof q==="object";if(E){var B=y?T??=new WeakMap:U??=new WeakMap,G=B.get(q);if(G)return G}z=q!=null?D(I(q)):{};let K=y||!q||!q.__esModule?Q(z,"default",{value:q,enumerable:!0}):z;for(let J of $(q))if(!V.call(K,J))Q(K,J,{get:X.bind(q,J),enumerable:!0});if(E)B.set(q,K);return K};var g=(q,y)=>()=>(y||q((y={exports:{}}).exports,y),y.exports);var k=(q)=>q;function S(q,y){this[q]=k.bind(null,y)}var p=(q,y)=>{for(var z in y)Q(q,z,{get:y[z],enumerable:!0,configurable:!0,set:S.bind(y,z)})};var l=(q,y)=>()=>(q&&(y=q(q=0)),y);var u=(q)=>Promise.all(q),m=import.meta.require;function W(){process.stdout.write(`oc
4
+
5
+ Usage:
6
+ oc [opencode args...]
7
+
8
+ Environment variables:
9
+ OPENCODE_LOCAL_PORT Force a specific port
10
+ OPENCODE_HOST Force a specific host
11
+
12
+ Behavior:
13
+ Auto-discovers running OpenCode instances and attaches when possible.
14
+ Launches a new instance on port 4096 when none is available.
15
+ `)}function F(){let q=Bun.spawnSync(["ps","-ax","-o","pid=,command="],{stdout:"pipe",stderr:"pipe"});if(q.exitCode!==0)return[];let y=Buffer.from(q.stdout).toString("utf8");return M(y,/^\s*(\d+)\s+(.*)$/)}function L(){let q=Bun.spawnSync(["wmic","process","get","ProcessId,CommandLine","/format:csv"],{stdout:"pipe",stderr:"pipe"});if(q.exitCode!==0)return[];let y=Buffer.from(q.stdout).toString("utf8"),z=[];for(let E of y.split(/\r?\n/).filter(Boolean)){let B=E.split(",");if(B.length<3)continue;let G=B.slice(1,-1).join(","),K=B[B.length-1]??"",J=Number.parseInt(K.trim(),10);if(Number.isNaN(J))continue;let N=j(G,J);if(N)z.push(N)}return z}function M(q,y){let z=[];for(let E of q.split(/\r?\n/).filter(Boolean)){let B=E.match(y);if(!B)continue;let G=Number.parseInt(B[1]??"",10),K=B[2]??"",J=j(K,G);if(J)z.push(J)}return z}function j(q,y){let z=/\bopencode\b.*\bserve\b/i.test(q),E=/\bopencode\b/i.test(q)&&/--port[=\s]+([1-9]\d*)/.test(q)&&!/\bserve\b/i.test(q);if(!z&&!E)return null;let B=q.match(/--port[=\s]+(\d+)/),G=q.match(/--hostname[=\s]+(\S+)/),K=B?Number.parseInt(B[1]??"",10):4096,J=G?G[1]??"127.0.0.1":"127.0.0.1";return{url:`http://${J==="0.0.0.0"?"127.0.0.1":J}:${K}`,pid:y}}function f(){let q=process.platform==="win32"?L():F(),y=new Set,z=q.filter((E)=>{if(y.has(E.url))return!1;return y.add(E.url),!0});if(!y.has("http://127.0.0.1:4096"))z.push({url:"http://127.0.0.1:4096"});return z}async function Z(q){try{return(await fetch(`${q}/global/health`,{signal:AbortSignal.timeout(800)})).ok}catch{return!1}}async function H(q){return(await Promise.all(q.map(async(z)=>({candidate:z,alive:await Z(z.url)})))).filter((z)=>z.alive).map((z)=>z.candidate)}async function b(q){let y=[`Found ${q.length} running OpenCode instances:`,...q.map((z,E)=>` [${E+1}] ${z.url}${z.pid!==void 0?` (pid ${z.pid})`:""}`),`Select [1-${q.length}]: `];process.stderr.write(y.join(`
16
+ `));for(let z=0;z<3;z++){let E=await C(),B=Number.parseInt(E.trim(),10);if(!Number.isNaN(B)&&B>=1&&B<=q.length)return q[B-1];if(z<2)process.stderr.write(`Invalid choice. Select [1-${q.length}]: `)}process.stderr.write(`
17
+ Invalid selection. Exiting.
18
+ `),process.exit(1)}function C(){return new Promise((q)=>{let y="";process.stdin.setEncoding("utf8"),process.stdin.resume(),process.stdin.once("data",(z)=>{process.stdin.pause(),y+=z,q(y.split(`
19
+ `)[0]??"")})})}async function O(q){let z=await Bun.spawn(q,{stdio:["inherit","inherit","inherit"]}).exited;process.exit(z??0)}function P(q){return q.includes("--dir")||q.some((y)=>y.startsWith("--dir="))}function w(q){let y=q[0];return y!==void 0&&!y.startsWith("-")}function R(q,y,z){return P(y)?["opencode","attach",q,...y]:["opencode","attach",q,"--dir",z,...y]}function Y(q,y,z,E){let G=["opencode",...w(q)?[...q]:[y,...q]];if(z!==void 0)G.push("--port",z);if(E!==void 0)G.push("--hostname",E);return G}async function v(){let q=process.argv.slice(2);if(q.includes("--help")||q.includes("-h"))W(),process.exit(0);let y=process.cwd(),z=process.env.OPENCODE_LOCAL_PORT,E=process.env.OPENCODE_HOST;if(z!==void 0&&E!==void 0){let J=`http://${E}:${z}`;if(await Z(J))await O(R(J,q,y));else await O(Y(q,y,z,E));return}if(z!==void 0||E!==void 0){let J=z??String(4096),N=E??"127.0.0.1",_=`http://${N}:${J}`;if(await Z(_))await O(R(_,q,y));else await O(Y(q,y,J,N));return}let B=f(),G=await H(B);if(G.length===0){await O(Y(q,y,String(4096)));return}if(G.length===1){await O(R(G[0].url,q,y));return}let K=await b(G);await O(R(K.url,q,y))}if(import.meta.main)await v();export{Y as buildLaunchCommand,R as buildAttachCommand};
20
+
21
+ //# debugId=CE8E69F7FD96D16564756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/oc/index.ts"],
4
+ "sourcesContent": [
5
+ "#!/usr/bin/env bun\n/**\n * oc — OpenCode launcher / attach helper\n *\n * Usage:\n * oc [args...] Auto-discover running instance → attach or launch\n * oc [args...] All extra args are forwarded to the opencode command\n *\n * Environment variables:\n * OPENCODE_LOCAL_PORT Force a specific port (skips discovery)\n * OPENCODE_HOST Force a specific host (skips discovery, default 127.0.0.1)\n *\n * When both env vars are absent, oc discovers running opencode processes via\n * process scanning (ps on Unix, wmic on Windows), probes their health endpoints,\n * and either attaches to a live instance or launches a new one on port 4096.\n *\n * If multiple live instances are found an interactive numbered selector is shown.\n */\n\nconst DEFAULT_PORT = 4096;\nconst DEFAULT_HOST = \"127.0.0.1\";\nconst HEALTH_TIMEOUT_MS = 800;\n\nfunction printHelp() {\n process.stdout.write(`oc\n\nUsage:\n oc [opencode args...]\n\nEnvironment variables:\n OPENCODE_LOCAL_PORT Force a specific port\n OPENCODE_HOST Force a specific host\n\nBehavior:\n Auto-discovers running OpenCode instances and attaches when possible.\n Launches a new instance on port 4096 when none is available.\n`);\n}\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\ninterface Candidate {\n url: string;\n pid?: number;\n}\n\n// ── Process scanning ──────────────────────────────────────────────────────────\n\nfunction scanProcessesUnix(): Candidate[] {\n const proc = Bun.spawnSync([\"ps\", \"-ax\", \"-o\", \"pid=,command=\"], {\n stdout: \"pipe\",\n stderr: \"pipe\",\n });\n if (proc.exitCode !== 0) return [];\n\n const output = Buffer.from(proc.stdout).toString(\"utf8\");\n return parseProcessLines(output, /^\\s*(\\d+)\\s+(.*)$/);\n}\n\nfunction scanProcessesWindows(): Candidate[] {\n const proc = Bun.spawnSync(\n [\"wmic\", \"process\", \"get\", \"ProcessId,CommandLine\", \"/format:csv\"],\n { stdout: \"pipe\", stderr: \"pipe\" },\n );\n if (proc.exitCode !== 0) return [];\n\n const output = Buffer.from(proc.stdout).toString(\"utf8\");\n const candidates: Candidate[] = [];\n\n for (const line of output.split(/\\r?\\n/).filter(Boolean)) {\n // CSV columns: Node,CommandLine,ProcessId\n const cols = line.split(\",\");\n if (cols.length < 3) continue;\n const command = cols.slice(1, -1).join(\",\"); // CommandLine may contain commas\n const pidStr = cols[cols.length - 1] ?? \"\";\n const pid = Number.parseInt(pidStr.trim(), 10);\n if (Number.isNaN(pid)) continue;\n\n const candidate = extractFromCommand(command, pid);\n if (candidate) candidates.push(candidate);\n }\n\n return candidates;\n}\n\nfunction parseProcessLines(\n output: string,\n linePattern: RegExp,\n): Candidate[] {\n const candidates: Candidate[] = [];\n\n for (const line of output.split(/\\r?\\n/).filter(Boolean)) {\n const match = line.match(linePattern);\n if (!match) continue;\n const pid = Number.parseInt(match[1] ?? \"\", 10);\n const command = match[2] ?? \"\";\n const candidate = extractFromCommand(command, pid);\n if (candidate) candidates.push(candidate);\n }\n\n return candidates;\n}\n\nfunction extractFromCommand(command: string, pid: number): Candidate | null {\n const isServe = /\\bopencode\\b.*\\bserve\\b/i.test(command);\n const isTuiWithPort =\n /\\bopencode\\b/i.test(command) &&\n /--port[=\\s]+([1-9]\\d*)/.test(command) &&\n !/\\bserve\\b/i.test(command);\n\n if (!isServe && !isTuiWithPort) return null;\n\n const portMatch = command.match(/--port[=\\s]+(\\d+)/);\n const hostMatch = command.match(/--hostname[=\\s]+(\\S+)/);\n const port = portMatch ? Number.parseInt(portMatch[1] ?? \"\", 10) : DEFAULT_PORT;\n const rawHost = hostMatch ? (hostMatch[1] ?? DEFAULT_HOST) : DEFAULT_HOST;\n // 0.0.0.0 listens on all interfaces — connect via loopback\n const host = rawHost === \"0.0.0.0\" ? \"127.0.0.1\" : rawHost;\n\n return { url: `http://${host}:${port}`, pid };\n}\n\nfunction discoverCandidates(): Candidate[] {\n const raw =\n process.platform === \"win32\" ? scanProcessesWindows() : scanProcessesUnix();\n\n // Deduplicate by URL\n const seen = new Set<string>();\n const deduped = raw.filter((c) => {\n if (seen.has(c.url)) return false;\n seen.add(c.url);\n return true;\n });\n\n // Always include the default endpoint as a fallback\n if (!seen.has(`http://${DEFAULT_HOST}:${DEFAULT_PORT}`)) {\n deduped.push({ url: `http://${DEFAULT_HOST}:${DEFAULT_PORT}` });\n }\n\n return deduped;\n}\n\n// ── Health probe ──────────────────────────────────────────────────────────────\n\nasync function isAlive(url: string): Promise<boolean> {\n try {\n const res = await fetch(`${url}/global/health`, {\n signal: AbortSignal.timeout(HEALTH_TIMEOUT_MS),\n });\n return res.ok;\n } catch {\n return false;\n }\n}\n\nasync function filterAlive(candidates: Candidate[]): Promise<Candidate[]> {\n const results = await Promise.all(\n candidates.map(async (c) => ({ candidate: c, alive: await isAlive(c.url) })),\n );\n return results.filter((r) => r.alive).map((r) => r.candidate);\n}\n\n// ── Interactive selector ──────────────────────────────────────────────────────\n\nasync function selectInstance(candidates: Candidate[]): Promise<Candidate> {\n const lines = [\n `Found ${candidates.length} running OpenCode instances:`,\n ...candidates.map(\n (c, i) =>\n ` [${i + 1}] ${c.url}${c.pid !== undefined ? ` (pid ${c.pid})` : \"\"}`,\n ),\n `Select [1-${candidates.length}]: `,\n ];\n\n process.stderr.write(lines.join(\"\\n\"));\n\n // Read a line from stdin\n for (let attempt = 0; attempt < 3; attempt++) {\n const raw = await readLine();\n const n = Number.parseInt(raw.trim(), 10);\n if (!Number.isNaN(n) && n >= 1 && n <= candidates.length) {\n return candidates[n - 1]!;\n }\n if (attempt < 2) {\n process.stderr.write(`Invalid choice. Select [1-${candidates.length}]: `);\n }\n }\n\n process.stderr.write(\"\\nInvalid selection. Exiting.\\n\");\n process.exit(1);\n}\n\nfunction readLine(): Promise<string> {\n return new Promise((resolve) => {\n let buffer = \"\";\n process.stdin.setEncoding(\"utf8\");\n process.stdin.resume();\n process.stdin.once(\"data\", (chunk: string) => {\n process.stdin.pause();\n buffer += chunk;\n resolve(buffer.split(\"\\n\")[0] ?? \"\");\n });\n });\n}\n\n// ── Spawn helpers ─────────────────────────────────────────────────────────────\n\nasync function run(cmd: string[]): Promise<void> {\n const proc = Bun.spawn(cmd, {\n stdio: [\"inherit\", \"inherit\", \"inherit\"],\n });\n const code = await proc.exited;\n process.exit(code ?? 0);\n}\n\nfunction hasDirFlag(args: string[]): boolean {\n return args.includes(\"--dir\") || args.some((arg) => arg.startsWith(\"--dir=\"));\n}\n\nfunction hasProjectArg(args: string[]): boolean {\n const first = args[0];\n return first !== undefined && !first.startsWith(\"-\");\n}\n\nexport function buildAttachCommand(url: string, extraArgs: string[], cwd: string): string[] {\n return hasDirFlag(extraArgs)\n ? [\"opencode\", \"attach\", url, ...extraArgs]\n : [\"opencode\", \"attach\", url, \"--dir\", cwd, ...extraArgs];\n}\n\nexport function buildLaunchCommand(extraArgs: string[], cwd: string, port?: string, host?: string): string[] {\n const args = hasProjectArg(extraArgs) ? [...extraArgs] : [cwd, ...extraArgs];\n const command = [\"opencode\", ...args];\n if (port !== undefined) command.push(\"--port\", port);\n if (host !== undefined) command.push(\"--hostname\", host);\n return command;\n}\n\n// ── Main ──────────────────────────────────────────────────────────────────────\n\nasync function main(): Promise<void> {\n const extraArgs = process.argv.slice(2);\n if (extraArgs.includes(\"--help\") || extraArgs.includes(\"-h\")) {\n printHelp();\n process.exit(0);\n }\n const cwd = process.cwd();\n\n const envPort = process.env[\"OPENCODE_LOCAL_PORT\"];\n const envHost = process.env[\"OPENCODE_HOST\"];\n\n // ── Explicit mode: both env vars set ───────────────────────────────────────\n if (envPort !== undefined && envHost !== undefined) {\n const url = `http://${envHost}:${envPort}`;\n const alive = await isAlive(url);\n if (alive) {\n await run(buildAttachCommand(url, extraArgs, cwd));\n } else {\n await run(buildLaunchCommand(extraArgs, cwd, envPort, envHost));\n }\n return;\n }\n\n // Partial override: only port or only host specified\n if (envPort !== undefined || envHost !== undefined) {\n const port = envPort ?? String(DEFAULT_PORT);\n const host = envHost ?? DEFAULT_HOST;\n const url = `http://${host}:${port}`;\n const alive = await isAlive(url);\n if (alive) {\n await run(buildAttachCommand(url, extraArgs, cwd));\n } else {\n await run(buildLaunchCommand(extraArgs, cwd, port, host));\n }\n return;\n }\n\n // ── Auto-discovery mode ────────────────────────────────────────────────────\n const candidates = discoverCandidates();\n const alive = await filterAlive(candidates);\n\n if (alive.length === 0) {\n // Nothing running — launch on default port\n await run(buildLaunchCommand(extraArgs, cwd, String(DEFAULT_PORT)));\n return;\n }\n\n if (alive.length === 1) {\n await run(buildAttachCommand(alive[0]!.url, extraArgs, cwd));\n return;\n }\n\n // Multiple instances — show selector\n const chosen = await selectInstance(alive);\n await run(buildAttachCommand(chosen.url, extraArgs, cwd));\n}\n\nif (import.meta.main) {\n await main();\n}\n\nexport {};\n"
6
+ ],
7
+ "mappings": "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",
8
+ "debugId": "CE8E69F7FD96D16564756E2164756E21",
9
+ "names": []
10
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scrylog/cli",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Terminal dashboard for monitoring tmux sessions running AI coding agents",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -31,15 +31,16 @@
31
31
  "bun": ">=1.3.0"
32
32
  },
33
33
  "bin": {
34
- "scrylog": "src/cli/index.ts",
35
- "scrylogd": "src/daemon/index.ts",
36
- "scrylog-mcp": "src/mcp/index.ts",
37
- "scrylog-claude-hook": "src/hooks/claude.ts",
38
- "scrylog-codex-hook": "src/hooks/codex.ts",
39
- "oc": "src/oc/index.ts"
34
+ "scrylog": "dist/cli/index.js",
35
+ "scrylogd": "dist/daemon/index.js",
36
+ "scrylog-mcp": "dist/mcp/index.js",
37
+ "scrylog-claude-hook": "dist/hooks/claude.js",
38
+ "scrylog-codex-hook": "dist/hooks/codex.js",
39
+ "oc": "dist/oc/index.js"
40
40
  },
41
41
  "files": [
42
- "src",
42
+ "dist",
43
+ "src/postinstall.cjs",
43
44
  "README.md",
44
45
  "LICENSE"
45
46
  ],
@@ -52,16 +53,20 @@
52
53
  "dev": "bun run src/cli/index.ts",
53
54
  "dev:daemon": "bun run src/daemon/index.ts --loglevel debug",
54
55
  "start": "bun run src/cli/index.ts",
56
+ "build": "bun run build.ts",
57
+ "prepack": "bun run build",
55
58
  "postinstall": "bun src/postinstall.cjs",
56
59
  "check-types": "tsc --noEmit",
57
60
  "test": "bun test",
58
61
  "pack:dry-run": "bash ../../scripts/npm-pack.sh \"$PWD\" \"\" --dry-run"
59
62
  },
60
63
  "dependencies": {
64
+ "@opentui/core": "0.5.11"
65
+ },
66
+ "devDependencies": {
61
67
  "@hono/zod-openapi": "^1.3.0",
62
68
  "@joeldmtz/prtl-core": "^0.3.0",
63
69
  "@modelcontextprotocol/sdk": "^1.0.0",
64
- "@opentui/core": "0.5.11",
65
70
  "@opentui/react": "0.5.11",
66
71
  "@opencode-ai/sdk": "1.4.10",
67
72
  "@scalar/hono-api-reference": "^0.10.11",
@@ -69,12 +74,10 @@
69
74
  "commander": "15.0.0",
70
75
  "hono": "^4.12.15",
71
76
  "react": "19.2.7",
72
- "zod": "^4.3.6"
73
- },
74
- "devDependencies": {
75
77
  "@types/bun": "^1.3.14",
76
78
  "@types/node": "^24.3.0",
77
79
  "@types/react": "19.2.17",
78
- "typescript": "5.9.3"
80
+ "typescript": "5.9.3",
81
+ "zod": "^4.3.6"
79
82
  }
80
83
  }
@@ -1,6 +0,0 @@
1
- export enum SessionRouteKey {
2
- LIST_SESSIONS = "LIST_SESSIONS",
3
- SESSION_CHILDREN = "SESSION_CHILDREN",
4
- SESSION_MESSAGES = "SESSION_MESSAGES",
5
- SESSION_DETAIL = "SESSION_DETAIL",
6
- }
@@ -1,82 +0,0 @@
1
- import type { RouteConfig } from "@hono/zod-openapi";
2
- import { ApiErrorSchema, IncludeUnknownQuerySchema, SessionIdParamSchema } from "../schemas/common";
3
- import {
4
- SessionsResponseSchema,
5
- SessionDetailResponseSchema,
6
- SessionChildrenResponseSchema,
7
- SessionMessagesResponseSchema,
8
- } from "../schemas/sessions";
9
- import { SessionRouteKey } from "./sessions.keys";
10
-
11
- export const SessionRoutes = {
12
- [SessionRouteKey.LIST_SESSIONS]: {
13
- method: "get",
14
- path: "/sessions",
15
- tags: ["Sessions"],
16
- summary: "List sessions",
17
- description: "Returns the full session tree with counts.",
18
- security: [{ BearerAuth: [] }],
19
- request: { query: IncludeUnknownQuerySchema },
20
- responses: {
21
- 200: {
22
- content: { "application/json": { schema: SessionsResponseSchema } },
23
- description: "Session tree",
24
- },
25
- },
26
- },
27
- [SessionRouteKey.SESSION_CHILDREN]: {
28
- method: "get",
29
- path: "/sessions/{id}/children",
30
- tags: ["Sessions"],
31
- summary: "List session children",
32
- security: [{ BearerAuth: [] }],
33
- request: { params: SessionIdParamSchema, query: IncludeUnknownQuerySchema },
34
- responses: {
35
- 200: {
36
- content: { "application/json": { schema: SessionChildrenResponseSchema } },
37
- description: "Child sessions",
38
- },
39
- 404: {
40
- content: { "application/json": { schema: ApiErrorSchema } },
41
- description: "Session not found",
42
- },
43
- },
44
- },
45
- [SessionRouteKey.SESSION_MESSAGES]: {
46
- method: "get",
47
- path: "/sessions/{id}/messages",
48
- tags: ["Sessions"],
49
- summary: "List session messages",
50
- description: "Message history for a session (placeholder — always returns empty array).",
51
- security: [{ BearerAuth: [] }],
52
- request: { params: SessionIdParamSchema },
53
- responses: {
54
- 200: {
55
- content: { "application/json": { schema: SessionMessagesResponseSchema } },
56
- description: "Message list",
57
- },
58
- 404: {
59
- content: { "application/json": { schema: ApiErrorSchema } },
60
- description: "Session not found",
61
- },
62
- },
63
- },
64
- [SessionRouteKey.SESSION_DETAIL]: {
65
- method: "get",
66
- path: "/sessions/{id}",
67
- tags: ["Sessions"],
68
- summary: "Get session",
69
- security: [{ BearerAuth: [] }],
70
- request: { params: SessionIdParamSchema },
71
- responses: {
72
- 200: {
73
- content: { "application/json": { schema: SessionDetailResponseSchema } },
74
- description: "Session detail",
75
- },
76
- 404: {
77
- content: { "application/json": { schema: ApiErrorSchema } },
78
- description: "Session not found",
79
- },
80
- },
81
- },
82
- } satisfies Record<SessionRouteKey, RouteConfig>;
@@ -1,5 +0,0 @@
1
- export enum SystemRouteKey {
2
- HEALTH = "HEALTH",
3
- STATUS = "STATUS",
4
- CONFIG = "CONFIG",
5
- }
@@ -1,69 +0,0 @@
1
- import { z } from "@hono/zod-openapi";
2
- import type { RouteConfig } from "@hono/zod-openapi";
3
- import { IncludeUnknownQuerySchema } from "../schemas/common";
4
- import { SystemRouteKey } from "./system.keys";
5
-
6
- export const SystemRoutes = {
7
- [SystemRouteKey.HEALTH]: {
8
- method: "get",
9
- path: "/health",
10
- tags: ["System"],
11
- summary: "Health check",
12
- responses: {
13
- 200: {
14
- content: {
15
- "application/json": {
16
- schema: z.object({
17
- ok: z.literal(true),
18
- version: z.string(),
19
- startedAt: z.number(),
20
- refreshedAt: z.number(),
21
- collectors: z.record(z.string(), z.unknown()),
22
- }),
23
- },
24
- },
25
- description: "Daemon is running",
26
- },
27
- },
28
- },
29
- [SystemRouteKey.STATUS]: {
30
- method: "get",
31
- path: "/status",
32
- tags: ["System"],
33
- summary: "Session counts and collector status",
34
- security: [{ BearerAuth: [] }],
35
- request: { query: IncludeUnknownQuerySchema },
36
- responses: {
37
- 200: {
38
- content: {
39
- "application/json": {
40
- schema: z.object({
41
- counts: z.record(z.string(), z.unknown()),
42
- collectorStatus: z.record(z.string(), z.unknown()),
43
- generatedAt: z.number(),
44
- }),
45
- },
46
- },
47
- description: "Current status",
48
- },
49
- },
50
- },
51
- [SystemRouteKey.CONFIG]: {
52
- method: "get",
53
- path: "/config",
54
- tags: ["System"],
55
- summary: "Current daemon configuration",
56
- description: "Bearer token is redacted in the response.",
57
- security: [{ BearerAuth: [] }],
58
- responses: {
59
- 200: {
60
- content: {
61
- "application/json": {
62
- schema: z.object({ config: z.record(z.string(), z.unknown()) }),
63
- },
64
- },
65
- description: "Configuration",
66
- },
67
- },
68
- },
69
- } satisfies Record<SystemRouteKey, RouteConfig>;
@@ -1,3 +0,0 @@
1
- export enum UsageRouteKey {
2
- GET_USAGE = "GET_USAGE",
3
- }
@@ -1,24 +0,0 @@
1
- import { z } from "@hono/zod-openapi";
2
- import type { RouteConfig } from "@hono/zod-openapi";
3
- import { UsageRouteKey } from "./usage.keys";
4
- import { UsageResponseSchema } from "../schemas/usage";
5
-
6
- export const UsageRoutes = {
7
- [UsageRouteKey.GET_USAGE]: {
8
- method: "get",
9
- path: "/usage",
10
- tags: ["Usage"],
11
- summary: "Get subscription usage and extra-use credits",
12
- security: [{ BearerAuth: [] }],
13
- responses: {
14
- 200: {
15
- content: {
16
- "application/json": {
17
- schema: UsageResponseSchema,
18
- },
19
- },
20
- description: "Current limits, extra-usage spend, and balances for configured providers",
21
- },
22
- },
23
- },
24
- } satisfies Record<UsageRouteKey, RouteConfig>;
@@ -1,5 +0,0 @@
1
- export enum WebhookRouteKey {
2
- WEBHOOK_OPENCODE = "WEBHOOK_OPENCODE",
3
- WEBHOOK_CLAUDE = "WEBHOOK_CLAUDE",
4
- WEBHOOK_CODEX = "WEBHOOK_CODEX",
5
- }
@@ -1,78 +0,0 @@
1
- import type { RouteConfig } from "@hono/zod-openapi";
2
- import { ApiErrorSchema, WebhookOkSchema } from "../schemas/common";
3
- import { OpencodeWebhookBodySchema } from "../schemas/webhooks/opencode";
4
- import { ClaudeWebhookBodySchema } from "../schemas/webhooks/claude";
5
- import { CodexWebhookBodySchema } from "../schemas/webhooks/codex";
6
- import { WebhookRouteKey } from "./webhooks.keys";
7
-
8
- export const WebhookRoutes = {
9
- [WebhookRouteKey.WEBHOOK_OPENCODE]: {
10
- method: "post",
11
- path: "/opencode",
12
- tags: ["Webhooks"],
13
- summary: "OpenCode session hook",
14
- description: "Receives lifecycle events from OpenCode webhooks.",
15
- request: {
16
- body: {
17
- required: true,
18
- content: { "application/json": { schema: OpencodeWebhookBodySchema } },
19
- },
20
- },
21
- responses: {
22
- 200: {
23
- content: { "application/json": { schema: WebhookOkSchema } },
24
- description: "Event accepted",
25
- },
26
- 400: {
27
- content: { "application/json": { schema: ApiErrorSchema } },
28
- description: "Missing required field",
29
- },
30
- },
31
- },
32
- [WebhookRouteKey.WEBHOOK_CLAUDE]: {
33
- method: "post",
34
- path: "/claude",
35
- tags: ["Webhooks"],
36
- summary: "Claude session hook",
37
- description: "Receives lifecycle events from the Claude CLI hooks system.",
38
- request: {
39
- body: {
40
- required: true,
41
- content: { "application/json": { schema: ClaudeWebhookBodySchema } },
42
- },
43
- },
44
- responses: {
45
- 200: {
46
- content: { "application/json": { schema: WebhookOkSchema } },
47
- description: "Event accepted",
48
- },
49
- 400: {
50
- content: { "application/json": { schema: ApiErrorSchema } },
51
- description: "Missing required field",
52
- },
53
- },
54
- },
55
- [WebhookRouteKey.WEBHOOK_CODEX]: {
56
- method: "post",
57
- path: "/codex",
58
- tags: ["Webhooks"],
59
- summary: "Codex session hook",
60
- description: "Receives lifecycle events from the Codex CLI hooks system.",
61
- request: {
62
- body: {
63
- required: true,
64
- content: { "application/json": { schema: CodexWebhookBodySchema } },
65
- },
66
- },
67
- responses: {
68
- 200: {
69
- content: { "application/json": { schema: WebhookOkSchema } },
70
- description: "Event accepted",
71
- },
72
- 400: {
73
- content: { "application/json": { schema: ApiErrorSchema } },
74
- description: "Missing required field",
75
- },
76
- },
77
- },
78
- } satisfies Record<WebhookRouteKey, RouteConfig>;
package/src/api/index.ts DELETED
@@ -1,18 +0,0 @@
1
- import type { ScrylogConfig } from "../shared/config";
2
- import type { DaemonStore } from "../daemon/store";
3
- import type { GitCollector } from "../daemon/collectors/git";
4
- import type { Logger } from "../shared/logger";
5
- import { createApiApp } from "./routes";
6
-
7
- /**
8
- * Create the fetch handler for the daemon HTTP API.
9
- * Used by both the Unix socket and TCP listeners (same handler, two servers).
10
- */
11
- export function createApiHandler(
12
- store: DaemonStore,
13
- config: ScrylogConfig,
14
- logger: Logger,
15
- gitCollector?: GitCollector,
16
- ): (request: Request) => Response | Promise<Response> {
17
- return createApiApp(store, config, logger, gitCollector).fetch;
18
- }
@@ -1,43 +0,0 @@
1
- import { timingSafeEqual } from "node:crypto";
2
- import type { MiddlewareHandler } from "hono";
3
- import { UnauthorizedError } from "./errors";
4
-
5
- function secureCompare(a: string, b: string): boolean {
6
- const bufA = Buffer.from(a, "utf-8");
7
- const bufB = Buffer.from(b, "utf-8");
8
- if (bufA.length !== bufB.length) return false;
9
- return timingSafeEqual(bufA, bufB);
10
- }
11
-
12
- export function createBearerAuthMiddleware(bearerToken: string | undefined): MiddlewareHandler {
13
- const PUBLIC_PATHS = [
14
- "/api/health",
15
- "/api/docs",
16
- "/api/openapi.json",
17
- "/api/llms.txt",
18
- ];
19
-
20
- return async (c, next) => {
21
- if (!bearerToken) {
22
- return next();
23
- }
24
-
25
- const path = c.req.path;
26
-
27
- if (PUBLIC_PATHS.includes(path)) {
28
- return next();
29
- }
30
-
31
- if (path.startsWith("/api/webhook/")) {
32
- return next();
33
- }
34
-
35
- const auth = c.req.header("Authorization");
36
- const expected = `Bearer ${bearerToken}`;
37
- if (!auth || !secureCompare(auth, expected)) {
38
- throw new UnauthorizedError();
39
- }
40
-
41
- return next();
42
- };
43
- }
@@ -1,28 +0,0 @@
1
- export class ApiError extends Error {
2
- constructor(
3
- public readonly status: number,
4
- public readonly code: string,
5
- message: string,
6
- ) {
7
- super(message);
8
- this.name = "ApiError";
9
- }
10
- }
11
-
12
- export class NotFoundError extends ApiError {
13
- constructor(resource: string, id: string) {
14
- super(404, "NOT_FOUND", `${resource} '${id}' not found`);
15
- }
16
- }
17
-
18
- export class UnauthorizedError extends ApiError {
19
- constructor() {
20
- super(401, "UNAUTHORIZED", "Unauthorized");
21
- }
22
- }
23
-
24
- export class ValidationError extends ApiError {
25
- constructor(message: string) {
26
- super(400, "VALIDATION_ERROR", message);
27
- }
28
- }
@@ -1,18 +0,0 @@
1
- import { createRoute } from "@hono/zod-openapi";
2
- import { SystemRoutes } from "../docs/system";
3
- import { SessionRoutes } from "../docs/sessions";
4
- import { WebhookRoutes } from "../docs/webhooks";
5
- import { UsageRoutes } from "../docs/usage";
6
-
7
- const _registry = {
8
- ...SystemRoutes,
9
- ...SessionRoutes,
10
- ...WebhookRoutes,
11
- ...UsageRoutes,
12
- };
13
-
14
- export type RouteKey = keyof typeof _registry;
15
-
16
- export function defineRoute<K extends RouteKey>(key: K) {
17
- return createRoute(_registry[key] as (typeof _registry)[K]);
18
- }
@@ -1,4 +0,0 @@
1
- import type { z } from "@hono/zod-openapi";
2
- import type { ClaudeWebhookBodySchema } from "../schemas/webhooks/claude";
3
-
4
- export type ClaudeWebhookPayload = z.infer<typeof ClaudeWebhookBodySchema>;
@@ -1,4 +0,0 @@
1
- import type { z } from "@hono/zod-openapi";
2
- import type { CodexWebhookBodySchema } from "../schemas/webhooks/codex";
3
-
4
- export type CodexWebhookPayload = z.infer<typeof CodexWebhookBodySchema>;
@@ -1,11 +0,0 @@
1
- import type { z } from "@hono/zod-openapi";
2
- import type { OpencodeWebhookBodySchema } from "../schemas/webhooks/opencode";
3
- import type { SessionState } from "../../shared/types";
4
-
5
- export type OpencodeWebhookPayload = z.infer<typeof OpencodeWebhookBodySchema>;
6
-
7
- export interface HookStateResult {
8
- state: SessionState;
9
- hasAttention: boolean;
10
- pendingAttentionCount?: number;
11
- }
@@ -1,51 +0,0 @@
1
- import { OpenAPIHono } from "@hono/zod-openapi";
2
- import type { ScrylogConfig } from "../../shared/config";
3
- import type { DaemonStore } from "../../daemon/store";
4
- import { buildSessionView } from "../../shared/session-view";
5
- import { NotFoundError } from "../lib/errors";
6
- import { SessionRouteKey } from "../docs/sessions.keys";
7
- import { defineRoute } from "../lib/route";
8
-
9
- export function createSessionsRouter(store: DaemonStore, config: ScrylogConfig): OpenAPIHono {
10
- const router = new OpenAPIHono();
11
-
12
- router.openapi(defineRoute(SessionRouteKey.LIST_SESSIONS), (c) => {
13
- const { includeUnknown } = c.req.valid("query");
14
- const view = buildSessionView(store.getSessions(), config, Date.now(), { includeUnknown: includeUnknown === "true" });
15
- return c.json({ sessions: view.sessions, counts: view.counts, generatedAt: store.getState().refreshedAt }, 200);
16
- });
17
-
18
- // Must be registered before /:id to avoid the wildcard swallowing the path
19
- router.openapi(defineRoute(SessionRouteKey.SESSION_CHILDREN), (c) => {
20
- const { id } = c.req.valid("param");
21
- const { includeUnknown } = c.req.valid("query");
22
- const parent = store.getSession(id);
23
- if (!parent) {
24
- throw new NotFoundError("Session", id);
25
- }
26
- const children = buildSessionView(store.getSessions(), config, Date.now(), {
27
- includeUnknown: includeUnknown === "true",
28
- }).sessions.find((s) => s.id === id)?.children ?? [];
29
- return c.json({ sessionId: id, children }, 200);
30
- });
31
-
32
- router.openapi(defineRoute(SessionRouteKey.SESSION_MESSAGES), (c) => {
33
- const { id } = c.req.valid("param");
34
- const session = store.getSession(id);
35
- if (!session) {
36
- throw new NotFoundError("Session", id);
37
- }
38
- return c.json({ sessionId: id, messages: [] }, 200);
39
- });
40
-
41
- router.openapi(defineRoute(SessionRouteKey.SESSION_DETAIL), (c) => {
42
- const { id } = c.req.valid("param");
43
- const session = store.getSession(id);
44
- if (!session) {
45
- throw new NotFoundError("Session", id);
46
- }
47
- return c.json({ session: session as unknown as Record<string, unknown> }, 200);
48
- });
49
-
50
- return router;
51
- }