@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
@@ -1,106 +0,0 @@
1
- import { OpenAPIHono } from "@hono/zod-openapi";
2
- import { streamSSE } from "hono/streaming";
3
- import type { ScrylogConfig } from "../../shared/config";
4
- import type { DaemonStore } from "../../daemon/store";
5
- import type { Logger } from "../../shared/logger";
6
- import { buildSessionView } from "../../shared/session-view";
7
- import { SystemRouteKey } from "../docs/system.keys";
8
- import { defineRoute } from "../lib/route";
9
- import { getInstalledVersion } from "../../shared/version";
10
-
11
- export function createSystemRouter(store: DaemonStore, config: ScrylogConfig, logger: Logger): OpenAPIHono {
12
- const router = new OpenAPIHono();
13
-
14
- router.openapi(defineRoute(SystemRouteKey.HEALTH), (c) => {
15
- const state = store.getState();
16
- return c.json({
17
- ok: true as const,
18
- version: getInstalledVersion(),
19
- startedAt: state.startedAt,
20
- refreshedAt: state.refreshedAt,
21
- collectors: state.collectorStatus,
22
- });
23
- });
24
-
25
- router.openapi(defineRoute(SystemRouteKey.STATUS), (c) => {
26
- const state = store.getState();
27
- const { includeUnknown } = c.req.valid("query");
28
- const view = buildSessionView(state.sessions, config, Date.now(), { includeUnknown: includeUnknown === "true" });
29
- return c.json({
30
- counts: view.counts,
31
- collectorStatus: state.collectorStatus,
32
- generatedAt: state.refreshedAt,
33
- });
34
- });
35
-
36
- router.openapi(defineRoute(SystemRouteKey.CONFIG), (c) => {
37
- const redacted = {
38
- ...config,
39
- api: { ...config.api, bearerToken: config.api.bearerToken ? "[redacted]" : undefined },
40
- mcp: { ...config.mcp, bearerToken: config.mcp.bearerToken ? "[redacted]" : undefined },
41
- };
42
- return c.json({ config: redacted });
43
- });
44
-
45
- router.get("/events", (c) => {
46
- const state = store.getState();
47
- const clientId = Math.random().toString(36).slice(2, 8);
48
-
49
- return streamSSE(c, async (stream) => {
50
- logger.info("api", "SSE client connected", { event: "sse.client_connected", clientId });
51
-
52
- await stream.writeSSE({
53
- data: JSON.stringify({ type: "state:initial", data: state }),
54
- });
55
-
56
- let keepaliveTimer: ReturnType<typeof setInterval> | undefined;
57
- let closed = false;
58
-
59
- const cleanup = () => {
60
- if (closed) return;
61
- closed = true;
62
- if (keepaliveTimer) clearInterval(keepaliveTimer);
63
- unsubscribe();
64
- logger.info("api", "SSE client disconnected", { event: "sse.client_disconnected", clientId });
65
- };
66
-
67
- const unsubscribe = store.onEvent((event) => {
68
- if (closed) return;
69
- stream.writeSSE({
70
- event: event.type,
71
- data: JSON.stringify(event),
72
- }).catch((error) => {
73
- logger.debug("api", "SSE write failed, closing", {
74
- event: "sse.write_failed",
75
- clientId,
76
- errorMessage: error instanceof Error ? error.message : String(error),
77
- });
78
- cleanup();
79
- });
80
- });
81
-
82
- if (config.api.sseKeepaliveMs > 0) {
83
- keepaliveTimer = setInterval(() => {
84
- if (closed) return;
85
- stream.write(":keepalive\n\n").catch((error) => {
86
- logger.debug("api", "SSE keepalive failed, closing", {
87
- event: "sse.keepalive_failed",
88
- clientId,
89
- errorMessage: error instanceof Error ? error.message : String(error),
90
- });
91
- cleanup();
92
- });
93
- }, config.api.sseKeepaliveMs);
94
- }
95
-
96
- stream.onAbort(() => {
97
- cleanup();
98
- });
99
-
100
- // Keep the SSE stream alive indefinitely; cleanup runs via onAbort.
101
- await new Promise(() => {});
102
- });
103
- });
104
-
105
- return router;
106
- }
@@ -1,15 +0,0 @@
1
- import { OpenAPIHono } from "@hono/zod-openapi";
2
- import type { DaemonStore } from "../../daemon/store";
3
- import { UsageRouteKey } from "../docs/usage.keys";
4
- import { defineRoute } from "../lib/route";
5
-
6
- export function createUsageRouter(store: DaemonStore): OpenAPIHono {
7
- const router = new OpenAPIHono();
8
-
9
- router.openapi(defineRoute(UsageRouteKey.GET_USAGE), (c) => {
10
- const state = store.getState().usage;
11
- return c.json(state);
12
- });
13
-
14
- return router;
15
- }
@@ -1,149 +0,0 @@
1
- import { OpenAPIHono } from "@hono/zod-openapi";
2
- import type { DaemonStore } from "../../../daemon/store";
3
- import type { GitCollector } from "../../../daemon/collectors/git";
4
- import type { Logger } from "../../../shared/logger";
5
- import type { ClaudeWebhookPayload } from "../../models/claude";
6
- import { isMetadataOnlyEvent } from "../../services/claude-session";
7
- import { ValidationError } from "../../lib/errors";
8
- import { WebhookRouteKey } from "../../docs/webhooks.keys";
9
- import { defineRoute } from "../../lib/route";
10
- import { isKnownClaudeProbePath } from "../../../shared/session-exclusions";
11
-
12
- export function createClaudeWebhookRouter(store: DaemonStore, logger: Logger, gitCollector?: GitCollector): OpenAPIHono {
13
- const router = new OpenAPIHono();
14
-
15
- router.openapi(defineRoute(WebhookRouteKey.WEBHOOK_CLAUDE), async (c) => {
16
- const payload = c.req.valid("json") as ClaudeWebhookPayload;
17
-
18
- const {
19
- sessionId,
20
- eventType,
21
- eventTime,
22
- hookSeq,
23
- state,
24
- hasAttention,
25
- attentionKind,
26
- summary,
27
- activeTool,
28
- toolName,
29
- toolInputSummary,
30
- toolResultStatus,
31
- permissionMode,
32
- startSource,
33
- endReason,
34
- errorType,
35
- agentId,
36
- agentType,
37
- transcriptPath,
38
- currentPath,
39
- lastActivity,
40
- parentSessionId,
41
- } = payload;
42
-
43
- if (!sessionId || typeof sessionId !== "string") {
44
- throw new ValidationError("sessionId required");
45
- }
46
-
47
- const storeId = `claude:${sessionId}`;
48
- if (isKnownClaudeProbePath(currentPath) || isKnownClaudeProbePath(transcriptPath)) {
49
- store.markLocalCommand(sessionId);
50
- store.removeSession(storeId);
51
- logger.debug("webhook", "claude probe excluded", { event: "session.excluded", tool: "claude", sessionId });
52
- return c.json({ ok: true as const, skipped: "claude-probe" }, 200);
53
- }
54
-
55
- if (eventType === "SessionEnd") {
56
- store.markLocalCommand(sessionId);
57
- store.removeSession(storeId);
58
- logger.info("webhook", "claude session ended", { event: "session.ended", tool: "claude", sessionId, reason: endReason });
59
- return c.json({ ok: true as const }, 200);
60
- }
61
-
62
- const existing = store.getSession(storeId);
63
-
64
- if (existing) {
65
- const existingSeq = existing.claude?.hookSeq;
66
- if (typeof hookSeq === "number" && typeof existingSeq === "number" && hookSeq < existingSeq) {
67
- logger.debug("webhook", "claude hook rejected — stale sequence", { sessionId, hookSeq, existingSeq });
68
- return c.json({ ok: true as const, skipped: "stale-sequence" }, 200);
69
- }
70
- }
71
-
72
- const isStateBearing = !isMetadataOnlyEvent(eventType ?? "");
73
-
74
- logger.debug("webhook", "claude hook received", { sessionId, state, eventType });
75
-
76
- if (existing) {
77
- store.upsertSession({
78
- ...existing,
79
- ...(isStateBearing && state !== undefined ? { state } : {}),
80
- ...(isStateBearing && hasAttention !== undefined ? { hasAttention } : {}),
81
- ...(attentionKind !== undefined ? { attentionKind } : {}),
82
- ...(summary !== undefined ? { summary } : {}),
83
- ...(activeTool !== undefined ? { activeTool } : {}),
84
- ...(!isStateBearing && currentPath !== undefined ? { currentPath } : {}),
85
- ...(currentPath && !existing.project && gitCollector ? { project: gitCollector.resolve(currentPath) } : {}),
86
- ...(isStateBearing ? {} : existing.currentPath && !currentPath ? {} : {}),
87
- lastActivity: lastActivity ?? Date.now(),
88
- detectionSource: "claude-hook",
89
- ...(parentSessionId ? { parentSessionId } : {}),
90
- claude: {
91
- sessionId,
92
- transcriptPath: transcriptPath ?? existing.claude?.transcriptPath ?? "",
93
- ...(eventType !== undefined ? { hookEventType: eventType } : {}),
94
- ...(eventTime !== undefined ? { hookEventTime: eventTime } : {}),
95
- ...(hookSeq !== undefined ? { hookSeq } : {}),
96
- ...(permissionMode !== undefined ? { permissionMode } : {}),
97
- ...(startSource !== undefined ? { startSource } : {}),
98
- ...(endReason !== undefined ? { endReason } : {}),
99
- ...(errorType !== undefined ? { errorType } : {}),
100
- ...(attentionKind !== undefined ? { attentionKind } : {}),
101
- ...(toolName !== undefined ? { toolName } : {}),
102
- ...(toolInputSummary !== undefined ? { toolInputSummary } : {}),
103
- ...(toolResultStatus !== undefined ? { toolResultStatus } : {}),
104
- ...(agentId !== undefined ? { agentId } : {}),
105
- ...(agentType !== undefined ? { agentType } : {}),
106
- },
107
- });
108
- } else {
109
- store.upsertSession({
110
- id: storeId,
111
- source: "standalone",
112
- tool: "claude",
113
- state: (isStateBearing ? state : undefined) ?? "idle",
114
- summary: summary ?? "Claude session",
115
- lastActivity: lastActivity ?? Date.now(),
116
- detectionSource: "claude-hook",
117
- hasAttention: isStateBearing ? (hasAttention ?? false) : false,
118
- activeTool,
119
- currentPath,
120
- ...(currentPath && gitCollector ? { project: gitCollector.resolve(currentPath) } : {}),
121
- ...(parentSessionId ? { parentSessionId } : {}),
122
- claude: {
123
- sessionId,
124
- transcriptPath: transcriptPath ?? "",
125
- ...(eventType !== undefined ? { hookEventType: eventType } : {}),
126
- ...(eventTime !== undefined ? { hookEventTime: eventTime } : {}),
127
- ...(hookSeq !== undefined ? { hookSeq } : {}),
128
- ...(permissionMode !== undefined ? { permissionMode } : {}),
129
- ...(startSource !== undefined ? { startSource } : {}),
130
- ...(endReason !== undefined ? { endReason } : {}),
131
- ...(errorType !== undefined ? { errorType } : {}),
132
- ...(attentionKind !== undefined ? { attentionKind } : {}),
133
- ...(toolName !== undefined ? { toolName } : {}),
134
- ...(toolInputSummary !== undefined ? { toolInputSummary } : {}),
135
- ...(toolResultStatus !== undefined ? { toolResultStatus } : {}),
136
- ...(agentId !== undefined ? { agentId } : {}),
137
- ...(agentType !== undefined ? { agentType } : {}),
138
- },
139
- });
140
-
141
- logger.debug("webhook", "claude stub session created from hook", { event: "session.upsert_from_webhook", tool: "claude", sessionId, state });
142
- }
143
-
144
- store.syncChildSessionIds();
145
- return c.json({ ok: true as const }, 200);
146
- });
147
-
148
- return router;
149
- }
@@ -1,175 +0,0 @@
1
- import { OpenAPIHono } from "@hono/zod-openapi";
2
- import type { DaemonStore } from "../../../daemon/store";
3
- import type { GitCollector } from "../../../daemon/collectors/git";
4
- import type { Logger } from "../../../shared/logger";
5
- import type { SessionModel } from "../../../shared/types";
6
- import type { CodexWebhookPayload } from "../../models/codex";
7
- import { isMetadataOnlyEvent } from "../../services/codex-session";
8
- import { ValidationError } from "../../lib/errors";
9
- import { WebhookRouteKey } from "../../docs/webhooks.keys";
10
- import { defineRoute } from "../../lib/route";
11
-
12
- export function createCodexWebhookRouter(store: DaemonStore, logger: Logger, gitCollector?: GitCollector): OpenAPIHono {
13
- const router = new OpenAPIHono();
14
-
15
- router.openapi(defineRoute(WebhookRouteKey.WEBHOOK_CODEX), async (c) => {
16
- const payload = c.req.valid("json") as CodexWebhookPayload;
17
-
18
- const {
19
- sessionId,
20
- eventType,
21
- eventTime,
22
- hookSeq,
23
- state,
24
- hasAttention,
25
- attentionKind,
26
- summary,
27
- activeTool,
28
- model,
29
- toolName,
30
- toolInputSummary,
31
- toolResultStatus,
32
- permissionMode,
33
- startSource,
34
- endReason,
35
- transcriptPath,
36
- currentPath,
37
- lastActivity,
38
- parentSessionId,
39
- forkedFromId,
40
- turnId,
41
- agentId,
42
- agentType,
43
- } = payload;
44
-
45
- if (!sessionId || typeof sessionId !== "string") {
46
- throw new ValidationError("sessionId required");
47
- }
48
-
49
- const storeId = `codex:${sessionId}`;
50
- const hasActiveChildren = () => store.getSessions().some(
51
- (s) => s.parentSessionId === storeId && (s.state === "working" || s.state === "waiting-input"),
52
- );
53
-
54
- // Stop with no continuation is the terminal event for Codex (no SessionEnd)
55
- if (eventType === "Stop") {
56
- const existing = store.getSession(storeId);
57
- if (existing) {
58
- const resolvedState: SessionModel["state"] =
59
- (state === "waiting-input" || state === "error") ? state : "idle";
60
- store.upsertSession({
61
- ...existing,
62
- state: resolvedState,
63
- hasAttention: resolvedState === "waiting-input" ? true : false,
64
- summary: summary ?? existing.summary ?? "Codex session ended",
65
- lastActivity: lastActivity ?? Date.now(),
66
- detectionSource: "codex-hook",
67
- codex: {
68
- sessionId,
69
- transcriptPath: transcriptPath ?? existing.codex?.transcriptPath ?? "",
70
- ...(eventType !== undefined ? { hookEventType: eventType } : {}),
71
- ...(eventTime !== undefined ? { hookEventTime: eventTime } : {}),
72
- ...(hookSeq !== undefined ? { hookSeq } : {}),
73
- ...(endReason !== undefined ? { endReason } : {}),
74
- },
75
- });
76
- }
77
-
78
- if (!hasActiveChildren()) {
79
- store.markLocalCommand(sessionId);
80
- }
81
- logger.info("webhook", "codex session ended", { event: "session.ended", tool: "codex", sessionId, reason: endReason });
82
- return c.json({ ok: true as const }, 200);
83
- }
84
-
85
- const existing = store.getSession(storeId);
86
-
87
- if (existing) {
88
- const existingSeq = existing.codex?.hookSeq;
89
- if (typeof hookSeq === "number" && typeof existingSeq === "number" && hookSeq < existingSeq) {
90
- logger.debug("webhook", "codex hook rejected — stale sequence", { sessionId, hookSeq, existingSeq });
91
- return c.json({ ok: true as const, skipped: "stale-sequence" }, 200);
92
- }
93
- }
94
-
95
- const isStateBearing = !isMetadataOnlyEvent(eventType ?? "");
96
-
97
- logger.debug("webhook", "codex hook received", { sessionId, state, eventType });
98
-
99
- if (existing) {
100
- store.upsertSession({
101
- ...existing,
102
- ...(isStateBearing && state !== undefined ? { state } : {}),
103
- ...(isStateBearing && hasAttention !== undefined ? { hasAttention } : {}),
104
- ...(attentionKind !== undefined ? { attentionKind } : {}),
105
- ...(summary !== undefined ? { summary } : {}),
106
- ...(activeTool !== undefined ? { activeTool } : {}),
107
- ...(!isStateBearing && currentPath !== undefined ? { currentPath } : {}),
108
- ...(currentPath && !existing.project && gitCollector ? { project: gitCollector.resolve(currentPath) } : {}),
109
- lastActivity: lastActivity ?? Date.now(),
110
- detectionSource: "codex-hook",
111
- ...(parentSessionId ? { parentSessionId } : {}),
112
- codex: {
113
- sessionId,
114
- transcriptPath: transcriptPath ?? existing.codex?.transcriptPath ?? "",
115
- ...(eventType !== undefined ? { hookEventType: eventType } : {}),
116
- ...(eventTime !== undefined ? { hookEventTime: eventTime } : {}),
117
- ...(hookSeq !== undefined ? { hookSeq } : {}),
118
- ...(permissionMode !== undefined ? { permissionMode } : {}),
119
- ...(startSource !== undefined ? { startSource } : {}),
120
- ...(endReason !== undefined ? { endReason } : {}),
121
- ...(model !== undefined ? { model } : {}),
122
- ...(toolName !== undefined ? { toolName } : {}),
123
- ...(toolInputSummary !== undefined ? { toolInputSummary } : {}),
124
- ...(toolResultStatus !== undefined ? { toolResultStatus } : {}),
125
- ...(attentionKind !== undefined ? { attentionKind } : {}),
126
- ...(turnId !== undefined ? { turnId } : {}),
127
- ...(agentId !== undefined ? { agentId } : {}),
128
- ...(agentType !== undefined ? { agentType } : {}),
129
- ...(forkedFromId !== undefined ? { forkedFromId } : {}),
130
- },
131
- });
132
- } else {
133
- store.upsertSession({
134
- id: storeId,
135
- source: "standalone",
136
- tool: "codex",
137
- state: (isStateBearing ? state : undefined) ?? "idle",
138
- summary: summary ?? "Codex session",
139
- lastActivity: lastActivity ?? Date.now(),
140
- detectionSource: "codex-hook",
141
- hasAttention: isStateBearing ? (hasAttention ?? false) : false,
142
- activeTool,
143
- currentPath,
144
- ...(currentPath && gitCollector ? { project: gitCollector.resolve(currentPath) } : {}),
145
- ...(parentSessionId ? { parentSessionId } : {}),
146
- codex: {
147
- sessionId,
148
- transcriptPath: transcriptPath ?? "",
149
- ...(eventType !== undefined ? { hookEventType: eventType } : {}),
150
- ...(eventTime !== undefined ? { hookEventTime: eventTime } : {}),
151
- ...(hookSeq !== undefined ? { hookSeq } : {}),
152
- ...(permissionMode !== undefined ? { permissionMode } : {}),
153
- ...(startSource !== undefined ? { startSource } : {}),
154
- ...(endReason !== undefined ? { endReason } : {}),
155
- ...(model !== undefined ? { model } : {}),
156
- ...(toolName !== undefined ? { toolName } : {}),
157
- ...(toolInputSummary !== undefined ? { toolInputSummary } : {}),
158
- ...(toolResultStatus !== undefined ? { toolResultStatus } : {}),
159
- ...(attentionKind !== undefined ? { attentionKind } : {}),
160
- ...(turnId !== undefined ? { turnId } : {}),
161
- ...(agentId !== undefined ? { agentId } : {}),
162
- ...(agentType !== undefined ? { agentType } : {}),
163
- ...(forkedFromId !== undefined ? { forkedFromId } : {}),
164
- },
165
- });
166
-
167
- logger.debug("webhook", "codex stub session created from hook", { event: "session.upsert_from_webhook", tool: "codex", sessionId, state });
168
- }
169
-
170
- store.syncChildSessionIds();
171
- return c.json({ ok: true as const }, 200);
172
- });
173
-
174
- return router;
175
- }
@@ -1,112 +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 type { GitCollector } from "../../../daemon/collectors/git";
5
- import type { Logger } from "../../../shared/logger";
6
- import type { SessionModel } from "../../../shared/types";
7
- import type { OpencodeWebhookPayload } from "../../models/opencode";
8
- import { mapHookState, extractSessionParentId } from "../../services/opencode-session";
9
- import { WebhookRouteKey } from "../../docs/webhooks.keys";
10
- import { defineRoute } from "../../lib/route";
11
-
12
- export function createOpencodeWebhookRouter(store: DaemonStore, config: ScrylogConfig, logger: Logger, gitCollector?: GitCollector): OpenAPIHono {
13
- const router = new OpenAPIHono();
14
-
15
- router.openapi(defineRoute(WebhookRouteKey.WEBHOOK_OPENCODE), async (c) => {
16
- const payload = c.req.valid("json") as OpencodeWebhookPayload;
17
- const { event } = payload;
18
-
19
- logger.debug("webhook", "received", {
20
- event: "integration.event_received",
21
- webhookEvent: event,
22
- sessionID: payload.sessionID ?? payload.info?.id,
23
- directory: payload.info?.directory ?? payload.directory,
24
- });
25
-
26
- if (config.opencode.mode === "sse") {
27
- logger.debug("webhook", "skipped: opencode-mode-sse", { event: "integration.event_skipped", webhookEvent: event, reason: "opencode-mode-sse" });
28
- return c.json({ ok: true as const, skipped: "opencode-mode-sse" }, 200);
29
- }
30
-
31
- const rawId = payload.sessionID ?? payload.info?.id;
32
- if (!rawId && event !== "session.created") {
33
- logger.debug("webhook", "skipped: no sessionID", { event: "integration.event_skipped", webhookEvent: event, reason: "no-session-id" });
34
- return c.json({ ok: true as const, skipped: "no sessionID" }, 200);
35
- }
36
-
37
- if (event === "session.deleted" && rawId) {
38
- logger.debug("webhook", "session.deleted", { event: "integration.event_received", tool: "opencode", sessionId: rawId });
39
- store.removeSession(`opencode:${rawId}`);
40
- return c.json({ ok: true as const }, 200);
41
- }
42
-
43
- if (rawId) {
44
- const storeId = `opencode:${rawId}`;
45
- const existing = store.getSession(storeId);
46
- const { state, hasAttention, pendingAttentionCount } = mapHookState(event, payload, existing);
47
-
48
- const title = payload.info?.title ?? payload.title ?? existing?.summary ?? "OpenCode session";
49
- const directory = payload.info?.directory ?? payload.directory ?? existing?.currentPath;
50
- const resolvedProject = directory && gitCollector ? gitCollector.resolve(directory) : undefined;
51
- const parentSessionId = extractSessionParentId(event, payload, existing);
52
- const timeUpdated = payload.info?.time?.updated ?? Date.now();
53
-
54
- if (payload.info?.parentID && payload.info.parentID !== parentSessionId?.replace(/^opencode:/, "")) {
55
- logger.debug("webhook", "ignoring non-session parentID", {
56
- event: "integration.event_skipped",
57
- webhookEvent: event,
58
- sessionId: rawId,
59
- parentID: payload.info.parentID,
60
- });
61
- }
62
-
63
- if (existing) {
64
- const stateChanged = state !== existing.state;
65
- const merged: SessionModel = {
66
- ...existing,
67
- state,
68
- hasAttention,
69
- summary: title,
70
- currentPath: directory ?? existing.currentPath,
71
- project: resolvedProject ?? existing.project,
72
- parentSessionId,
73
- lastActivity: stateChanged ? Date.now() : Math.max(existing.lastActivity, timeUpdated),
74
- detectionSource: "opencode-hook",
75
- opencode: {
76
- ...existing.opencode,
77
- sessionId: rawId,
78
- instanceUrl: existing.opencode?.instanceUrl ?? "",
79
- ...(pendingAttentionCount !== undefined ? { pendingAttentionCount } : {}),
80
- },
81
- };
82
- logger.debug("webhook", "upsert existing session", { event: "session.upsert_from_webhook", webhookEvent: event, sessionId: rawId, state: merged.state });
83
- store.upsertSession(merged);
84
- } else {
85
- const newSession: SessionModel = {
86
- id: storeId,
87
- source: "standalone",
88
- tool: "opencode",
89
- state,
90
- summary: title,
91
- lastActivity: timeUpdated,
92
- detectionSource: "opencode-hook",
93
- hasAttention,
94
- currentPath: directory,
95
- project: resolvedProject,
96
- parentSessionId,
97
- opencode: {
98
- sessionId: rawId,
99
- instanceUrl: "",
100
- ...(pendingAttentionCount !== undefined ? { pendingAttentionCount } : {}),
101
- },
102
- };
103
- logger.debug("webhook", "upsert new session", { event: "session.upsert_from_webhook", webhookEvent: event, sessionId: rawId, state, directory });
104
- store.upsertSession(newSession);
105
- }
106
- }
107
-
108
- return c.json({ ok: true as const }, 200);
109
- });
110
-
111
- return router;
112
- }
package/src/api/routes.ts DELETED
@@ -1,93 +0,0 @@
1
- import { OpenAPIHono } from "@hono/zod-openapi";
2
- import { Scalar } from "@scalar/hono-api-reference";
3
- import { createMarkdownFromOpenApi } from "@scalar/openapi-to-markdown";
4
- import { cors } from "hono/cors";
5
- import type { ContentfulStatusCode } from "hono/utils/http-status";
6
- import type { ScrylogConfig } from "../shared/config";
7
- import type { DaemonStore } from "../daemon/store";
8
- import type { GitCollector } from "../daemon/collectors/git";
9
- import type { Logger } from "../shared/logger";
10
- import { NOOP_LOGGER } from "../shared/logger";
11
- import { ApiError } from "./lib/errors";
12
- import { createSystemRouter } from "./routes/system";
13
- import { createSessionsRouter } from "./routes/sessions";
14
- import { createUsageRouter } from "./routes/usage";
15
- import { createOpencodeWebhookRouter } from "./routes/webhooks/opencode";
16
- import { createClaudeWebhookRouter } from "./routes/webhooks/claude";
17
- import { createCodexWebhookRouter } from "./routes/webhooks/codex";
18
- import { createBearerAuthMiddleware } from "./lib/auth";
19
-
20
- export function createApiApp(
21
- store: DaemonStore,
22
- config: ScrylogConfig,
23
- logger: Logger = NOOP_LOGGER,
24
- gitCollector?: GitCollector,
25
- ): OpenAPIHono {
26
- const app = new OpenAPIHono();
27
-
28
- app.onError((err, c) => {
29
- if (err instanceof ApiError) {
30
- return c.json({ error: err.message, code: err.code }, err.status as ContentfulStatusCode);
31
- }
32
- logger.debug("api", "unhandled error", { message: err.message });
33
- return c.json({ error: "Internal server error", code: "INTERNAL_ERROR" }, 500);
34
- });
35
-
36
- app.notFound((c) => c.json({ error: "Not found", code: "NOT_FOUND" }, 404));
37
-
38
- if (config.cors.enabled) {
39
- app.use("*", cors({
40
- origin: config.cors.origins,
41
- allowMethods: config.cors.methods,
42
- allowHeaders: config.cors.allowedHeaders,
43
- exposeHeaders: config.cors.exposedHeaders,
44
- credentials: config.cors.credentials,
45
- maxAge: config.cors.maxAge,
46
- }));
47
- }
48
-
49
- app.use(createBearerAuthMiddleware(config.api.bearerToken));
50
-
51
- app.route("/api", createSystemRouter(store, config, logger));
52
- app.route("/api", createSessionsRouter(store, config));
53
- app.route("/api", createUsageRouter(store));
54
- app.route("/api/webhook", createOpencodeWebhookRouter(store, config, logger, gitCollector));
55
- app.route("/api/webhook", createClaudeWebhookRouter(store, logger, gitCollector));
56
- app.route("/api/webhook", createCodexWebhookRouter(store, logger, gitCollector));
57
-
58
- app.openAPIRegistry.registerComponent("securitySchemes", "BearerAuth", {
59
- type: "http",
60
- scheme: "bearer",
61
- description: "Bearer token authentication (only enforced when api.bearerToken is configured)",
62
- });
63
-
64
- app.doc("/api/openapi.json", {
65
- openapi: "3.0.0",
66
- info: {
67
- title: "scrylog API",
68
- version: "1.0.0",
69
- description: "scrylogd daemon HTTP API. Bearer auth is enforced on protected endpoints when api.bearerToken is configured.",
70
- },
71
- });
72
-
73
- app.get("/api/docs", Scalar({ url: "/api/openapi.json" }));
74
-
75
- app.get("/api/llms.txt", async (c) => {
76
- const specUrl = new URL("/api/openapi.json", c.req.url);
77
- const spec = await fetch(specUrl).then((r) => r.text());
78
- const markdown = await createMarkdownFromOpenApi(spec);
79
- return c.text(markdown);
80
- });
81
-
82
- return app;
83
- }
84
-
85
- /** @deprecated use createApiApp directly */
86
- export function createRouteHandler(
87
- store: DaemonStore,
88
- config: ScrylogConfig,
89
- logger: Logger = NOOP_LOGGER,
90
- gitCollector?: GitCollector,
91
- ): (request: Request) => Response | Promise<Response> {
92
- return createApiApp(store, config, logger, gitCollector).fetch;
93
- }
@@ -1,29 +0,0 @@
1
- import { z } from "@hono/zod-openapi";
2
-
3
- export const ApiErrorSchema = z.object({
4
- error: z.string().openapi({ example: "Session not found" }),
5
- });
6
-
7
- export const WebhookOkSchema = z.object({
8
- ok: z.literal(true),
9
- skipped: z.string().optional().openapi({ example: "stale-sequence" }),
10
- });
11
-
12
- // Session IDs follow prefix:suffix format (claude:uuid, opencode:ses_xxx, tmux:1:0.0).
13
- // Rejects path traversal characters and shell metacharacters.
14
- export const SessionIdParamSchema = z.object({
15
- id: z
16
- .string()
17
- .min(1)
18
- .max(200)
19
- .regex(/^[a-zA-Z0-9:_\-.]+$/, "Invalid session ID")
20
- .openapi({ param: { name: "id", in: "path" }, example: "claude:abc-123" }),
21
- });
22
-
23
- export const IncludeUnknownQuerySchema = z.object({
24
- includeUnknown: z.enum(["true", "false"]).optional().openapi({
25
- param: { name: "includeUnknown", in: "query" },
26
- example: "true",
27
- description: "Include sessions whose tool could not be identified",
28
- }),
29
- });