@scrylog/cli 0.2.0 → 0.3.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 (171) hide show
  1. package/README.md +46 -7
  2. package/dist/assets/highlights-eq9cgrbb.scm +604 -0
  3. package/dist/assets/highlights-ghv9g403.scm +205 -0
  4. package/dist/assets/highlights-hk7bwhj4.scm +284 -0
  5. package/dist/assets/highlights-r812a2qc.scm +150 -0
  6. package/dist/assets/highlights-x6tmsnaa.scm +115 -0
  7. package/dist/assets/injections-73j83es3.scm +27 -0
  8. package/dist/assets/parser.worker-xyhf83rf.js +4563 -0
  9. package/dist/assets/tree-sitter-3jzf13jk.wasm +0 -0
  10. package/dist/assets/tree-sitter-javascript-nd0q4pe9.wasm +0 -0
  11. package/dist/assets/tree-sitter-markdown-411r6y9b.wasm +0 -0
  12. package/dist/assets/tree-sitter-markdown_inline-j5349f42.wasm +0 -0
  13. package/dist/assets/tree-sitter-typescript-zxjzwt75.wasm +0 -0
  14. package/dist/assets/tree-sitter-zig-e78zbjpm.wasm +0 -0
  15. package/dist/cli/assets/providers/SOURCES.md +21 -0
  16. package/dist/cli/assets/providers/claude-terminal.png +0 -0
  17. package/dist/cli/assets/providers/claude-terminal.svg +62 -0
  18. package/dist/cli/assets/providers/claude.png +0 -0
  19. package/dist/cli/assets/providers/copilot.png +0 -0
  20. package/dist/cli/assets/providers/copilot.svg +44 -0
  21. package/dist/cli/assets/providers/openai-terminal.png +0 -0
  22. package/dist/cli/assets/providers/openai-terminal.svg +62 -0
  23. package/dist/cli/assets/providers/openai.png +0 -0
  24. package/dist/cli/assets/providers/opencode-terminal.png +0 -0
  25. package/dist/cli/assets/providers/opencode-terminal.svg +62 -0
  26. package/dist/cli/assets/providers/opencode.png +0 -0
  27. package/dist/cli/assets/providers/shell.png +0 -0
  28. package/dist/cli/assets/providers/shell.svg +23 -0
  29. package/dist/cli/assets/providers/unknown.png +0 -0
  30. package/dist/cli/assets/providers/unknown.svg +23 -0
  31. package/dist/cli/index.js +499 -0
  32. package/dist/cli/index.js.map +61 -0
  33. package/dist/daemon/index.js +670 -0
  34. package/dist/daemon/index.js.map +1475 -0
  35. package/dist/hooks/claude.js +6 -0
  36. package/dist/hooks/claude.js.map +11 -0
  37. package/dist/hooks/codex.js +10 -0
  38. package/dist/hooks/codex.js.map +11 -0
  39. package/dist/mcp/index.js +100 -0
  40. package/dist/mcp/index.js.map +231 -0
  41. package/dist/oc/index.js +21 -0
  42. package/dist/oc/index.js.map +10 -0
  43. package/package.json +26 -20
  44. package/src/postinstall.cjs +11 -0
  45. package/src/api/docs/sessions.keys.ts +0 -6
  46. package/src/api/docs/sessions.ts +0 -81
  47. package/src/api/docs/system.keys.ts +0 -5
  48. package/src/api/docs/system.ts +0 -66
  49. package/src/api/docs/usage.keys.ts +0 -3
  50. package/src/api/docs/usage.ts +0 -24
  51. package/src/api/docs/webhooks.keys.ts +0 -5
  52. package/src/api/docs/webhooks.ts +0 -78
  53. package/src/api/index.ts +0 -18
  54. package/src/api/lib/auth.ts +0 -43
  55. package/src/api/lib/errors.ts +0 -28
  56. package/src/api/lib/route.ts +0 -18
  57. package/src/api/models/claude.ts +0 -4
  58. package/src/api/models/codex.ts +0 -4
  59. package/src/api/models/opencode.ts +0 -11
  60. package/src/api/routes/sessions.ts +0 -47
  61. package/src/api/routes/system.ts +0 -103
  62. package/src/api/routes/usage.ts +0 -15
  63. package/src/api/routes/webhooks/claude.ts +0 -141
  64. package/src/api/routes/webhooks/codex.ts +0 -175
  65. package/src/api/routes/webhooks/opencode.ts +0 -111
  66. package/src/api/routes.ts +0 -93
  67. package/src/api/schemas/common.ts +0 -21
  68. package/src/api/schemas/sessions.ts +0 -72
  69. package/src/api/schemas/usage.ts +0 -50
  70. package/src/api/schemas/webhooks/claude.ts +0 -32
  71. package/src/api/schemas/webhooks/codex.ts +0 -34
  72. package/src/api/schemas/webhooks/opencode.ts +0 -29
  73. package/src/api/services/claude-session.ts +0 -9
  74. package/src/api/services/codex-session.ts +0 -8
  75. package/src/api/services/opencode-session.ts +0 -73
  76. package/src/classifier.ts +0 -206
  77. package/src/cli/commands.ts +0 -632
  78. package/src/cli/prompt.ts +0 -44
  79. package/src/daemon/cli.ts +0 -27
  80. package/src/daemon/collectors/claude/README.md +0 -108
  81. package/src/daemon/collectors/claude/collector.ts +0 -423
  82. package/src/daemon/collectors/claude/discovery.ts +0 -148
  83. package/src/daemon/collectors/claude/hooks.ts +0 -181
  84. package/src/daemon/collectors/claude/index.ts +0 -8
  85. package/src/daemon/collectors/claude/jsonl.ts +0 -85
  86. package/src/daemon/collectors/claude/transcript.ts +0 -246
  87. package/src/daemon/collectors/codex/README.md +0 -147
  88. package/src/daemon/collectors/codex/collector.ts +0 -661
  89. package/src/daemon/collectors/codex/discovery.ts +0 -104
  90. package/src/daemon/collectors/codex/hooks.ts +0 -179
  91. package/src/daemon/collectors/codex/index.ts +0 -7
  92. package/src/daemon/collectors/codex/jsonl.ts +0 -108
  93. package/src/daemon/collectors/codex/transcript.ts +0 -299
  94. package/src/daemon/collectors/git.ts +0 -295
  95. package/src/daemon/collectors/opencode/README.md +0 -106
  96. package/src/daemon/collectors/opencode/collector.ts +0 -545
  97. package/src/daemon/collectors/opencode/discovery.ts +0 -257
  98. package/src/daemon/collectors/opencode/index.ts +0 -5
  99. package/src/daemon/collectors/opencode/session-builder.ts +0 -59
  100. package/src/daemon/collectors/opencode-db.ts +0 -282
  101. package/src/daemon/collectors/process.ts +0 -201
  102. package/src/daemon/collectors/tmux/README.md +0 -128
  103. package/src/daemon/collectors/tmux/collector.ts +0 -472
  104. package/src/daemon/collectors/tmux/enrichment.ts +0 -241
  105. package/src/daemon/collectors/tmux/index.ts +0 -4
  106. package/src/daemon/collectors/tmux/parse.ts +0 -128
  107. package/src/daemon/collectors/usage.ts +0 -193
  108. package/src/daemon/index.ts +0 -207
  109. package/src/daemon/scheduler.ts +0 -86
  110. package/src/daemon/store.ts +0 -588
  111. package/src/detect.ts +0 -143
  112. package/src/hooks/claude.ts +0 -596
  113. package/src/hooks/codex.ts +0 -517
  114. package/src/legacy/classifier.ts +0 -215
  115. package/src/legacy/detect.ts +0 -143
  116. package/src/mcp/client-server.ts +0 -21
  117. package/src/mcp/http-handler.ts +0 -126
  118. package/src/mcp/index.ts +0 -61
  119. package/src/mcp/resources/config-current.ts +0 -19
  120. package/src/mcp/resources/index.ts +0 -11
  121. package/src/mcp/resources/sessions-active.ts +0 -19
  122. package/src/mcp/resources/sessions-all.ts +0 -19
  123. package/src/mcp/schemas/sessions.ts +0 -3
  124. package/src/mcp/server.ts +0 -17
  125. package/src/mcp/services/api-adapter.ts +0 -48
  126. package/src/mcp/services/data-source.ts +0 -22
  127. package/src/mcp/services/store-adapter.ts +0 -40
  128. package/src/mcp/tools/get-active-sessions.ts +0 -16
  129. package/src/mcp/tools/get-session-children.ts +0 -24
  130. package/src/mcp/tools/get-session-messages.ts +0 -24
  131. package/src/mcp/tools/get-session.ts +0 -23
  132. package/src/mcp/tools/index.ts +0 -15
  133. package/src/mcp/tools/list-sessions.ts +0 -16
  134. package/src/neo-blessed.d.ts +0 -4
  135. package/src/oc/index.ts +0 -302
  136. package/src/service/manager.ts +0 -284
  137. package/src/shared/config.ts +0 -478
  138. package/src/shared/ipc.ts +0 -80
  139. package/src/shared/logger.ts +0 -248
  140. package/src/shared/paths.ts +0 -73
  141. package/src/shared/platform.ts +0 -110
  142. package/src/shared/session-view.ts +0 -241
  143. package/src/shared/types.ts +0 -299
  144. package/src/shared/utils.ts +0 -339
  145. package/src/tui/app.ts +0 -920
  146. package/src/tui/client.ts +0 -149
  147. package/src/tui/index.ts +0 -217
  148. package/src/types.ts +0 -113
  149. package/src/usage/README.md +0 -323
  150. package/src/usage/api.ts +0 -99
  151. package/src/usage/client.ts +0 -3
  152. package/src/usage/clients/anthropic-oauth-usage.ts +0 -178
  153. package/src/usage/clients/anthropic-web-usage.ts +0 -245
  154. package/src/usage/clients/claude-cli-probe.ts +0 -187
  155. package/src/usage/clients/codex-oauth-usage.ts +0 -113
  156. package/src/usage/clients/opencode-go-usage.ts +0 -441
  157. package/src/usage/index.ts +0 -20
  158. package/src/usage/mappers.ts +0 -273
  159. package/src/usage/planner.ts +0 -54
  160. package/src/usage/providers/claude.ts +0 -113
  161. package/src/usage/providers/codex.ts +0 -34
  162. package/src/usage/providers/opencode-go.ts +0 -37
  163. package/src/usage/resolver.ts +0 -4
  164. package/src/usage/resolvers/browser-cookies.ts +0 -470
  165. package/src/usage/resolvers/claude-auth.ts +0 -108
  166. package/src/usage/resolvers/claude-cli.ts +0 -24
  167. package/src/usage/resolvers/claude-desktop-auth.ts +0 -160
  168. package/src/usage/resolvers/claude-web.ts +0 -16
  169. package/src/usage/resolvers/codex-auth.ts +0 -117
  170. package/src/usage/resolvers/opencode-go-web.ts +0 -14
  171. package/src/usage/types.ts +0 -190
@@ -1,103 +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
-
10
- export function createSystemRouter(store: DaemonStore, config: ScrylogConfig, logger: Logger): OpenAPIHono {
11
- const router = new OpenAPIHono();
12
-
13
- router.openapi(defineRoute(SystemRouteKey.HEALTH), (c) => {
14
- const state = store.getState();
15
- return c.json({
16
- ok: true as const,
17
- startedAt: state.startedAt,
18
- refreshedAt: state.refreshedAt,
19
- collectors: state.collectorStatus,
20
- });
21
- });
22
-
23
- router.openapi(defineRoute(SystemRouteKey.STATUS), (c) => {
24
- const state = store.getState();
25
- const view = buildSessionView(state.sessions, config);
26
- return c.json({
27
- counts: view.counts,
28
- collectorStatus: state.collectorStatus,
29
- generatedAt: state.refreshedAt,
30
- });
31
- });
32
-
33
- router.openapi(defineRoute(SystemRouteKey.CONFIG), (c) => {
34
- const redacted = {
35
- ...config,
36
- api: { ...config.api, bearerToken: config.api.bearerToken ? "[redacted]" : undefined },
37
- mcp: { ...config.mcp, bearerToken: config.mcp.bearerToken ? "[redacted]" : undefined },
38
- };
39
- return c.json({ config: redacted });
40
- });
41
-
42
- router.get("/events", (c) => {
43
- const state = store.getState();
44
- const clientId = Math.random().toString(36).slice(2, 8);
45
-
46
- return streamSSE(c, async (stream) => {
47
- logger.info("api", "SSE client connected", { event: "sse.client_connected", clientId });
48
-
49
- await stream.writeSSE({
50
- data: JSON.stringify({ type: "state:initial", data: state }),
51
- });
52
-
53
- let keepaliveTimer: ReturnType<typeof setInterval> | undefined;
54
- let closed = false;
55
-
56
- const cleanup = () => {
57
- if (closed) return;
58
- closed = true;
59
- if (keepaliveTimer) clearInterval(keepaliveTimer);
60
- unsubscribe();
61
- logger.info("api", "SSE client disconnected", { event: "sse.client_disconnected", clientId });
62
- };
63
-
64
- const unsubscribe = store.onEvent((event) => {
65
- if (closed) return;
66
- stream.writeSSE({
67
- event: event.type,
68
- data: JSON.stringify(event),
69
- }).catch((error) => {
70
- logger.debug("api", "SSE write failed, closing", {
71
- event: "sse.write_failed",
72
- clientId,
73
- errorMessage: error instanceof Error ? error.message : String(error),
74
- });
75
- cleanup();
76
- });
77
- });
78
-
79
- if (config.api.sseKeepaliveMs > 0) {
80
- keepaliveTimer = setInterval(() => {
81
- if (closed) return;
82
- stream.write(":keepalive\n\n").catch((error) => {
83
- logger.debug("api", "SSE keepalive failed, closing", {
84
- event: "sse.keepalive_failed",
85
- clientId,
86
- errorMessage: error instanceof Error ? error.message : String(error),
87
- });
88
- cleanup();
89
- });
90
- }, config.api.sseKeepaliveMs);
91
- }
92
-
93
- stream.onAbort(() => {
94
- cleanup();
95
- });
96
-
97
- // Keep the SSE stream alive indefinitely; cleanup runs via onAbort.
98
- await new Promise(() => {});
99
- });
100
- });
101
-
102
- return router;
103
- }
@@ -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,141 +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
-
11
- export function createClaudeWebhookRouter(store: DaemonStore, logger: Logger, gitCollector?: GitCollector): OpenAPIHono {
12
- const router = new OpenAPIHono();
13
-
14
- router.openapi(defineRoute(WebhookRouteKey.WEBHOOK_CLAUDE), async (c) => {
15
- const payload = c.req.valid("json") as ClaudeWebhookPayload;
16
-
17
- const {
18
- sessionId,
19
- eventType,
20
- eventTime,
21
- hookSeq,
22
- state,
23
- hasAttention,
24
- attentionKind,
25
- summary,
26
- activeTool,
27
- toolName,
28
- toolInputSummary,
29
- toolResultStatus,
30
- permissionMode,
31
- startSource,
32
- endReason,
33
- errorType,
34
- agentId,
35
- agentType,
36
- transcriptPath,
37
- currentPath,
38
- lastActivity,
39
- parentSessionId,
40
- } = payload;
41
-
42
- if (!sessionId || typeof sessionId !== "string") {
43
- throw new ValidationError("sessionId required");
44
- }
45
-
46
- const storeId = `claude:${sessionId}`;
47
- if (eventType === "SessionEnd") {
48
- store.markLocalCommand(sessionId);
49
- store.removeSession(storeId);
50
- logger.info("webhook", "claude session ended", { event: "session.ended", tool: "claude", sessionId, reason: endReason });
51
- return c.json({ ok: true as const }, 200);
52
- }
53
-
54
- const existing = store.getSession(storeId);
55
-
56
- if (existing) {
57
- const existingSeq = existing.claude?.hookSeq;
58
- if (typeof hookSeq === "number" && typeof existingSeq === "number" && hookSeq < existingSeq) {
59
- logger.debug("webhook", "claude hook rejected — stale sequence", { sessionId, hookSeq, existingSeq });
60
- return c.json({ ok: true as const, skipped: "stale-sequence" }, 200);
61
- }
62
- }
63
-
64
- const isStateBearing = !isMetadataOnlyEvent(eventType ?? "");
65
-
66
- logger.debug("webhook", "claude hook received", { sessionId, state, eventType });
67
-
68
- if (existing) {
69
- store.upsertSession({
70
- ...existing,
71
- ...(isStateBearing && state !== undefined ? { state } : {}),
72
- ...(isStateBearing && hasAttention !== undefined ? { hasAttention } : {}),
73
- ...(attentionKind !== undefined ? { attentionKind } : {}),
74
- ...(summary !== undefined ? { summary } : {}),
75
- ...(activeTool !== undefined ? { activeTool } : {}),
76
- ...(!isStateBearing && currentPath !== undefined ? { currentPath } : {}),
77
- ...(currentPath && !existing.project && gitCollector ? { project: gitCollector.resolve(currentPath) } : {}),
78
- ...(isStateBearing ? {} : existing.currentPath && !currentPath ? {} : {}),
79
- lastActivity: lastActivity ?? Date.now(),
80
- detectionSource: "claude-hook",
81
- ...(parentSessionId ? { parentSessionId } : {}),
82
- claude: {
83
- sessionId,
84
- transcriptPath: transcriptPath ?? existing.claude?.transcriptPath ?? "",
85
- ...(eventType !== undefined ? { hookEventType: eventType } : {}),
86
- ...(eventTime !== undefined ? { hookEventTime: eventTime } : {}),
87
- ...(hookSeq !== undefined ? { hookSeq } : {}),
88
- ...(permissionMode !== undefined ? { permissionMode } : {}),
89
- ...(startSource !== undefined ? { startSource } : {}),
90
- ...(endReason !== undefined ? { endReason } : {}),
91
- ...(errorType !== undefined ? { errorType } : {}),
92
- ...(attentionKind !== undefined ? { attentionKind } : {}),
93
- ...(toolName !== undefined ? { toolName } : {}),
94
- ...(toolInputSummary !== undefined ? { toolInputSummary } : {}),
95
- ...(toolResultStatus !== undefined ? { toolResultStatus } : {}),
96
- ...(agentId !== undefined ? { agentId } : {}),
97
- ...(agentType !== undefined ? { agentType } : {}),
98
- },
99
- });
100
- } else {
101
- store.upsertSession({
102
- id: storeId,
103
- source: "standalone",
104
- tool: "claude",
105
- state: (isStateBearing ? state : undefined) ?? "idle",
106
- summary: summary ?? "Claude session",
107
- lastActivity: lastActivity ?? Date.now(),
108
- detectionSource: "claude-hook",
109
- hasAttention: isStateBearing ? (hasAttention ?? false) : false,
110
- activeTool,
111
- currentPath,
112
- ...(currentPath && gitCollector ? { project: gitCollector.resolve(currentPath) } : {}),
113
- ...(parentSessionId ? { parentSessionId } : {}),
114
- claude: {
115
- sessionId,
116
- transcriptPath: transcriptPath ?? "",
117
- ...(eventType !== undefined ? { hookEventType: eventType } : {}),
118
- ...(eventTime !== undefined ? { hookEventTime: eventTime } : {}),
119
- ...(hookSeq !== undefined ? { hookSeq } : {}),
120
- ...(permissionMode !== undefined ? { permissionMode } : {}),
121
- ...(startSource !== undefined ? { startSource } : {}),
122
- ...(endReason !== undefined ? { endReason } : {}),
123
- ...(errorType !== undefined ? { errorType } : {}),
124
- ...(attentionKind !== undefined ? { attentionKind } : {}),
125
- ...(toolName !== undefined ? { toolName } : {}),
126
- ...(toolInputSummary !== undefined ? { toolInputSummary } : {}),
127
- ...(toolResultStatus !== undefined ? { toolResultStatus } : {}),
128
- ...(agentId !== undefined ? { agentId } : {}),
129
- ...(agentType !== undefined ? { agentType } : {}),
130
- },
131
- });
132
-
133
- logger.debug("webhook", "claude stub session created from hook", { event: "session.upsert_from_webhook", tool: "claude", sessionId, state });
134
- }
135
-
136
- store.syncChildSessionIds();
137
- return c.json({ ok: true as const }, 200);
138
- });
139
-
140
- return router;
141
- }
@@ -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,111 +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
- opencode: {
75
- ...existing.opencode,
76
- sessionId: rawId,
77
- instanceUrl: existing.opencode?.instanceUrl ?? "",
78
- ...(pendingAttentionCount !== undefined ? { pendingAttentionCount } : {}),
79
- },
80
- };
81
- logger.debug("webhook", "upsert existing session", { event: "session.upsert_from_webhook", webhookEvent: event, sessionId: rawId, state: merged.state });
82
- store.upsertSession(merged);
83
- } else {
84
- const newSession: SessionModel = {
85
- id: storeId,
86
- source: "standalone",
87
- tool: "opencode",
88
- state,
89
- summary: title,
90
- lastActivity: timeUpdated,
91
- detectionSource: "opencode-hook",
92
- hasAttention,
93
- currentPath: directory,
94
- project: resolvedProject,
95
- parentSessionId,
96
- opencode: {
97
- sessionId: rawId,
98
- instanceUrl: "",
99
- ...(pendingAttentionCount !== undefined ? { pendingAttentionCount } : {}),
100
- },
101
- };
102
- logger.debug("webhook", "upsert new session", { event: "session.upsert_from_webhook", webhookEvent: event, sessionId: rawId, state, directory });
103
- store.upsertSession(newSession);
104
- }
105
- }
106
-
107
- return c.json({ ok: true as const }, 200);
108
- });
109
-
110
- return router;
111
- }
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,21 +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
- });