@blogic-cz/agent-tools 1.5.2 → 1.5.4

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 (60) hide show
  1. package/README.md +4 -4
  2. package/dist/audit-tool/main.d.ts +3 -0
  3. package/dist/audit-tool/main.d.ts.map +1 -0
  4. package/dist/az-tool/main.d.ts +3 -0
  5. package/dist/az-tool/main.d.ts.map +1 -0
  6. package/dist/azdo-tool/main.d.ts +3 -0
  7. package/dist/azdo-tool/main.d.ts.map +1 -0
  8. package/dist/credential-guard/index.d.ts.map +1 -1
  9. package/dist/db-tool/main.d.ts +3 -0
  10. package/dist/db-tool/main.d.ts.map +1 -0
  11. package/dist/gh-tool/api.d.ts.map +1 -1
  12. package/dist/gh-tool/gist.d.ts.map +1 -1
  13. package/dist/gh-tool/main.d.ts +3 -0
  14. package/dist/gh-tool/main.d.ts.map +1 -0
  15. package/dist/gh-tool/pr/core.d.ts.map +1 -1
  16. package/dist/gh-tool/service.d.ts.map +1 -1
  17. package/dist/gh-tool/text-input.d.ts +4 -0
  18. package/dist/gh-tool/text-input.d.ts.map +1 -1
  19. package/dist/gh-tool/workflow.d.ts.map +1 -1
  20. package/dist/k8s-tool/main.d.ts +3 -0
  21. package/dist/k8s-tool/main.d.ts.map +1 -0
  22. package/dist/logs-tool/index.d.ts +1 -19
  23. package/dist/logs-tool/index.d.ts.map +1 -1
  24. package/dist/logs-tool/main.d.ts +21 -0
  25. package/dist/logs-tool/main.d.ts.map +1 -0
  26. package/dist/observability-tool/main.d.ts +3 -0
  27. package/dist/observability-tool/main.d.ts.map +1 -0
  28. package/dist/session-tool/index.d.ts +1 -11
  29. package/dist/session-tool/index.d.ts.map +1 -1
  30. package/dist/session-tool/main.d.ts +13 -0
  31. package/dist/session-tool/main.d.ts.map +1 -0
  32. package/dist/shared/effect-version.d.ts +4 -0
  33. package/dist/shared/effect-version.d.ts.map +1 -0
  34. package/package.json +2 -2
  35. package/src/audit-tool/index.ts +3 -109
  36. package/src/audit-tool/main.ts +111 -0
  37. package/src/az-tool/index.ts +3 -151
  38. package/src/az-tool/main.ts +153 -0
  39. package/src/azdo-tool/index.ts +3 -220
  40. package/src/azdo-tool/main.ts +222 -0
  41. package/src/credential-guard/index.ts +102 -4
  42. package/src/db-tool/index.ts +3 -260
  43. package/src/db-tool/main.ts +262 -0
  44. package/src/gh-tool/api.ts +21 -1
  45. package/src/gh-tool/gist.ts +7 -1
  46. package/src/gh-tool/index.ts +3 -256
  47. package/src/gh-tool/main.ts +258 -0
  48. package/src/gh-tool/pr/core.ts +52 -2
  49. package/src/gh-tool/service.ts +9 -5
  50. package/src/gh-tool/text-input.ts +63 -4
  51. package/src/gh-tool/workflow.ts +1 -0
  52. package/src/k8s-tool/index.ts +3 -487
  53. package/src/k8s-tool/main.ts +489 -0
  54. package/src/logs-tool/index.ts +3 -247
  55. package/src/logs-tool/main.ts +249 -0
  56. package/src/observability-tool/index.ts +3 -34
  57. package/src/observability-tool/main.ts +36 -0
  58. package/src/session-tool/index.ts +3 -337
  59. package/src/session-tool/main.ts +339 -0
  60. package/src/shared/effect-version.ts +32 -0
@@ -1,339 +1,5 @@
1
1
  #!/usr/bin/env bun
2
+ import { checkEffectVersion } from "#shared/effect-version";
2
3
 
3
- /**
4
- * OpenCode Session Tool for Coding Agents
5
- *
6
- * Lists, searches, and reads OpenCode session history.
7
- * Uses current project scope by default, or all projects with --all.
8
- */
9
-
10
- import { Argument, Command, Flag } from "effect/unstable/cli";
11
- import { BunRuntime, BunServices } from "@effect/platform-bun";
12
- import { Effect, Layer, Result } from "effect";
13
-
14
- import type { MessageSummary, SessionResult, SessionSource } from "./types";
15
-
16
- import { ALL_SESSION_SOURCES } from "./types";
17
-
18
- import { makeSchemaCommand, formatOption, formatOutput, logText, VERSION } from "#shared";
19
- import { AuditServiceLayer, withAudit } from "#shared/audit";
20
- import { ResolvedPaths, ResolvedPathsLayer } from "./config";
21
- import { SessionStorageNotFoundError } from "./errors";
22
- import { formatDate, SessionService, SessionServiceLayer } from "./service";
23
- import {
24
- projectSessionFilter,
25
- shapeBody,
26
- sessionSummariesFromMessages,
27
- sortSessionSummaries,
28
- } from "./summaries";
29
-
30
- const AppLayer = SessionServiceLayer.pipe(Layer.provideMerge(ResolvedPathsLayer));
31
-
32
- const sourceOption = Flag.String("source").pipe(
33
- Flag.withDescription("Filter by source: all, opencode, claude-code, codex, pi"),
34
- Flag.withDefault("all"),
35
- );
36
-
37
- const filterBySource = (summaries: MessageSummary[], source: string): MessageSummary[] => {
38
- if (source === "all") return summaries;
39
- return summaries.filter((s) => s.source === (source as SessionSource));
40
- };
41
-
42
- const sourceSet = (source: string): ReadonlySet<SessionSource> =>
43
- source === "all" ? ALL_SESSION_SOURCES : new Set([source as SessionSource]);
44
-
45
- const buildScopeLabel = (searchAll: boolean, currentDir: string) => {
46
- if (searchAll) {
47
- return "all projects";
48
- }
49
-
50
- const projectName = currentDir.split("/").pop() ?? currentDir;
51
- return `current project (${projectName})`;
52
- };
53
-
54
- const mapSummary = (maxBodyChars: number) => (summary: MessageSummary) => {
55
- return Effect.gen(function* () {
56
- const paths = yield* ResolvedPaths;
57
- return {
58
- sessionID: summary.sessionID,
59
- messageID: summary.id,
60
- title: summary.title,
61
- ...shapeBody(summary.body, maxBodyChars),
62
- created: formatDate(summary.created),
63
- ...(summary.source === "opencode"
64
- ? {
65
- messagePath: `${paths.messagesPath}/${summary.sessionID}/${summary.id}.json`,
66
- sessionPath: `${paths.messagesPath}/${summary.sessionID}`,
67
- }
68
- : {}),
69
- role: summary.role,
70
- source: summary.source,
71
- };
72
- });
73
- };
74
-
75
- const listCommand = Command.make(
76
- "list",
77
- {
78
- all: Flag.Boolean("all").pipe(
79
- Flag.withDescription("Search all projects"),
80
- Flag.withDefault(false),
81
- ),
82
- format: formatOption,
83
- limit: Flag.Int("limit").pipe(Flag.withDescription("Limit result count"), Flag.withDefault(10)),
84
- source: sourceOption,
85
- },
86
- ({ all, format, limit, source }) =>
87
- Effect.gen(function* () {
88
- const sessionService = yield* SessionService;
89
- const startTime = Date.now();
90
- const currentDir = process.cwd();
91
- const scope = buildScopeLabel(all, currentDir);
92
-
93
- const result = yield* Effect.gen(function* () {
94
- const sources = sourceSet(source);
95
- const projectSessions = new Map<SessionSource, Set<string>>();
96
- if (!all) {
97
- for (const sessionSource of sources) {
98
- const matching = yield* sessionService.getSessionsForProject(
99
- currentDir,
100
- new Set([sessionSource]),
101
- );
102
- projectSessions.set(sessionSource, matching);
103
- }
104
- }
105
-
106
- if (!all && [...projectSessions.values()].every((sessions) => sessions.size === 0)) {
107
- return {
108
- success: false,
109
- error: "No sessions found for current project",
110
- data: {
111
- project: currentDir,
112
- scope,
113
- },
114
- scope,
115
- count: 0,
116
- executionTimeMs: Date.now() - startTime,
117
- } satisfies SessionResult;
118
- }
119
-
120
- const nonPiSources = [...sources].filter((item) => item !== "pi");
121
- const [messagesBySource, piSummaries] = yield* Effect.all([
122
- Effect.all(
123
- nonPiSources.map((sessionSource) =>
124
- sessionService.getMessageSummaries(
125
- projectSessionFilter(projectSessions, sessionSource, all),
126
- new Set([sessionSource]),
127
- ),
128
- ),
129
- ),
130
- sources.has("pi")
131
- ? sessionService.getPiSessionSummaries(projectSessionFilter(projectSessions, "pi", all))
132
- : Effect.succeed([]),
133
- ]);
134
- const messages = messagesBySource.flat();
135
- const summaries = sortSessionSummaries([
136
- ...sessionSummariesFromMessages(messages),
137
- ...piSummaries,
138
- ]);
139
- const results = summaries.slice(0, limit).map((summary) => ({
140
- createdAt: formatDate(summary.createdAt),
141
- updatedAt: formatDate(summary.updatedAt),
142
- sessionID: summary.sessionID,
143
- title: summary.title,
144
- source: summary.source,
145
- }));
146
-
147
- return {
148
- success: true,
149
- data: {
150
- results,
151
- scope,
152
- },
153
- scope,
154
- count: results.length,
155
- executionTimeMs: Date.now() - startTime,
156
- } satisfies SessionResult;
157
- }).pipe(Effect.result);
158
-
159
- const output = Result.match(result, {
160
- onFailure: (error) =>
161
- ({
162
- success: false,
163
- error: error.message,
164
- data: error instanceof SessionStorageNotFoundError ? { path: error.path } : undefined,
165
- scope,
166
- count: 0,
167
- executionTimeMs: Date.now() - startTime,
168
- }) satisfies SessionResult,
169
- onSuccess: (okResult) => okResult,
170
- });
171
-
172
- yield* logText(formatOutput(output, format));
173
- }),
174
- ).pipe(Command.withDescription("List OpenCode session summaries"));
175
-
176
- const searchCommand = Command.make(
177
- "search",
178
- {
179
- query: Argument.String("query").pipe(Argument.withDescription("Search query")),
180
- all: Flag.Boolean("all").pipe(
181
- Flag.withDescription("Search all projects"),
182
- Flag.withDefault(false),
183
- ),
184
- format: formatOption,
185
- bodyChars: Flag.Int("body-chars").pipe(
186
- Flag.withDescription("Max message body characters per result (0 = full bodies)"),
187
- Flag.withDefault(500),
188
- ),
189
- limit: Flag.Int("limit").pipe(Flag.withDescription("Limit result count"), Flag.withDefault(10)),
190
- source: sourceOption,
191
- },
192
- ({ all, bodyChars, format, limit, query, source }) =>
193
- Effect.gen(function* () {
194
- const sessionService = yield* SessionService;
195
- const startTime = Date.now();
196
- const currentDir = process.cwd();
197
- const scope = buildScopeLabel(all, currentDir);
198
-
199
- const result = yield* Effect.gen(function* () {
200
- const sessionFilter = all ? null : yield* sessionService.getSessionsForProject(currentDir);
201
-
202
- if (sessionFilter !== null && sessionFilter.size === 0) {
203
- return {
204
- success: false,
205
- query,
206
- error: "No sessions found for current project",
207
- data: {
208
- project: currentDir,
209
- results: [],
210
- },
211
- scope,
212
- count: 0,
213
- executionTimeMs: Date.now() - startTime,
214
- } satisfies SessionResult;
215
- }
216
-
217
- const allSummaries = yield* sessionService.getMessageSummaries(sessionFilter);
218
- const summaries = filterBySource(allSummaries, source);
219
- const matched = sessionService.searchSummaries(summaries, query);
220
- const mappedResults = yield* Effect.all(matched.slice(0, limit).map(mapSummary(bodyChars)));
221
-
222
- return {
223
- success: true,
224
- query,
225
- data: {
226
- count: mappedResults.length,
227
- query,
228
- results: mappedResults,
229
- scope,
230
- },
231
- scope,
232
- count: mappedResults.length,
233
- executionTimeMs: Date.now() - startTime,
234
- } satisfies SessionResult;
235
- }).pipe(Effect.result);
236
-
237
- const output = Result.match(result, {
238
- onFailure: (error) =>
239
- ({
240
- success: false,
241
- query,
242
- error: error.message,
243
- data:
244
- error instanceof SessionStorageNotFoundError
245
- ? { path: error.path, results: [] }
246
- : { results: [] },
247
- scope,
248
- count: 0,
249
- executionTimeMs: Date.now() - startTime,
250
- }) satisfies SessionResult,
251
- onSuccess: (okResult) => okResult,
252
- });
253
-
254
- yield* logText(formatOutput(output, format));
255
- }),
256
- ).pipe(Command.withDescription("Search message history"));
257
-
258
- const readCommand = Command.make(
259
- "read",
260
- {
261
- session: Flag.String("session").pipe(Flag.withDescription("Session ID to read")),
262
- format: formatOption,
263
- source: sourceOption,
264
- },
265
- ({ format, session, source }) =>
266
- Effect.gen(function* () {
267
- const sessionService = yield* SessionService;
268
- const startTime = Date.now();
269
-
270
- const result = yield* sessionService
271
- .getMessageSummaries(new Set([session]))
272
- .pipe(Effect.result);
273
-
274
- const output: SessionResult = yield* Result.match(result, {
275
- onFailure: (error) =>
276
- Effect.succeed({
277
- success: false,
278
- error: error.message,
279
- data:
280
- error instanceof SessionStorageNotFoundError
281
- ? { path: error.path, session }
282
- : { session },
283
- count: 0,
284
- executionTimeMs: Date.now() - startTime,
285
- } satisfies SessionResult),
286
- onSuccess: (summaries) => {
287
- const filtered = filterBySource(summaries, source);
288
- const sessionResults = filtered.filter((summary) => summary.sessionID === session);
289
- return Effect.all(sessionResults.map(mapSummary(0))).pipe(
290
- Effect.map(
291
- (mapped) =>
292
- ({
293
- success: true,
294
- data: {
295
- files: mapped
296
- .map((message) =>
297
- "messagePath" in message ? (message.messagePath as string) : null,
298
- )
299
- .filter((filePath): filePath is string => filePath !== null),
300
- messages: mapped,
301
- session,
302
- },
303
- count: mapped.length,
304
- executionTimeMs: Date.now() - startTime,
305
- }) satisfies SessionResult,
306
- ),
307
- );
308
- },
309
- });
310
-
311
- yield* logText(formatOutput(output, format));
312
- }),
313
- ).pipe(Command.withDescription("Read all messages from a session"));
314
-
315
- const commandsCommand = makeSchemaCommand(() => mainCommand);
316
-
317
- const mainCommand = Command.make("session-tool", {}).pipe(
318
- Command.withDescription("OpenCode session history tool"),
319
- Command.withSubcommands([listCommand, readCommand, searchCommand, commandsCommand]),
320
- );
321
-
322
- const cli = Command.run(mainCommand, {
323
- version: VERSION,
324
- });
325
-
326
- export const run = Command.runWith(mainCommand, {
327
- version: VERSION,
328
- });
329
-
330
- const MainLayer = AppLayer.pipe(
331
- Layer.provideMerge(BunServices.layer),
332
- Layer.provideMerge(AuditServiceLayer),
333
- );
334
-
335
- const program = withAudit("session", cli).pipe(Effect.provide(MainLayer));
336
-
337
- BunRuntime.runMain(program, {
338
- disableErrorReporting: true,
339
- });
4
+ if (await checkEffectVersion()) await import("./main");
5
+ else process.exitCode = 1;
@@ -0,0 +1,339 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * OpenCode Session Tool for Coding Agents
5
+ *
6
+ * Lists, searches, and reads OpenCode session history.
7
+ * Uses current project scope by default, or all projects with --all.
8
+ */
9
+
10
+ import { Argument, Command, Flag } from "effect/unstable/cli";
11
+ import { BunRuntime, BunServices } from "@effect/platform-bun";
12
+ import { Effect, Layer, Result } from "effect";
13
+
14
+ import type { MessageSummary, SessionResult, SessionSource } from "./types";
15
+
16
+ import { ALL_SESSION_SOURCES } from "./types";
17
+
18
+ import { makeSchemaCommand, formatOption, formatOutput, logText, VERSION } from "#shared";
19
+ import { AuditServiceLayer, withAudit } from "#shared/audit";
20
+ import { ResolvedPaths, ResolvedPathsLayer } from "./config";
21
+ import { SessionStorageNotFoundError } from "./errors";
22
+ import { formatDate, SessionService, SessionServiceLayer } from "./service";
23
+ import {
24
+ projectSessionFilter,
25
+ shapeBody,
26
+ sessionSummariesFromMessages,
27
+ sortSessionSummaries,
28
+ } from "./summaries";
29
+
30
+ const AppLayer = SessionServiceLayer.pipe(Layer.provideMerge(ResolvedPathsLayer));
31
+
32
+ const sourceOption = Flag.String("source").pipe(
33
+ Flag.withDescription("Filter by source: all, opencode, claude-code, codex, pi"),
34
+ Flag.withDefault("all"),
35
+ );
36
+
37
+ const filterBySource = (summaries: MessageSummary[], source: string): MessageSummary[] => {
38
+ if (source === "all") return summaries;
39
+ return summaries.filter((s) => s.source === (source as SessionSource));
40
+ };
41
+
42
+ const sourceSet = (source: string): ReadonlySet<SessionSource> =>
43
+ source === "all" ? ALL_SESSION_SOURCES : new Set([source as SessionSource]);
44
+
45
+ const buildScopeLabel = (searchAll: boolean, currentDir: string) => {
46
+ if (searchAll) {
47
+ return "all projects";
48
+ }
49
+
50
+ const projectName = currentDir.split("/").pop() ?? currentDir;
51
+ return `current project (${projectName})`;
52
+ };
53
+
54
+ const mapSummary = (maxBodyChars: number) => (summary: MessageSummary) => {
55
+ return Effect.gen(function* () {
56
+ const paths = yield* ResolvedPaths;
57
+ return {
58
+ sessionID: summary.sessionID,
59
+ messageID: summary.id,
60
+ title: summary.title,
61
+ ...shapeBody(summary.body, maxBodyChars),
62
+ created: formatDate(summary.created),
63
+ ...(summary.source === "opencode"
64
+ ? {
65
+ messagePath: `${paths.messagesPath}/${summary.sessionID}/${summary.id}.json`,
66
+ sessionPath: `${paths.messagesPath}/${summary.sessionID}`,
67
+ }
68
+ : {}),
69
+ role: summary.role,
70
+ source: summary.source,
71
+ };
72
+ });
73
+ };
74
+
75
+ const listCommand = Command.make(
76
+ "list",
77
+ {
78
+ all: Flag.Boolean("all").pipe(
79
+ Flag.withDescription("Search all projects"),
80
+ Flag.withDefault(false),
81
+ ),
82
+ format: formatOption,
83
+ limit: Flag.Int("limit").pipe(Flag.withDescription("Limit result count"), Flag.withDefault(10)),
84
+ source: sourceOption,
85
+ },
86
+ ({ all, format, limit, source }) =>
87
+ Effect.gen(function* () {
88
+ const sessionService = yield* SessionService;
89
+ const startTime = Date.now();
90
+ const currentDir = process.cwd();
91
+ const scope = buildScopeLabel(all, currentDir);
92
+
93
+ const result = yield* Effect.gen(function* () {
94
+ const sources = sourceSet(source);
95
+ const projectSessions = new Map<SessionSource, Set<string>>();
96
+ if (!all) {
97
+ for (const sessionSource of sources) {
98
+ const matching = yield* sessionService.getSessionsForProject(
99
+ currentDir,
100
+ new Set([sessionSource]),
101
+ );
102
+ projectSessions.set(sessionSource, matching);
103
+ }
104
+ }
105
+
106
+ if (!all && [...projectSessions.values()].every((sessions) => sessions.size === 0)) {
107
+ return {
108
+ success: false,
109
+ error: "No sessions found for current project",
110
+ data: {
111
+ project: currentDir,
112
+ scope,
113
+ },
114
+ scope,
115
+ count: 0,
116
+ executionTimeMs: Date.now() - startTime,
117
+ } satisfies SessionResult;
118
+ }
119
+
120
+ const nonPiSources = [...sources].filter((item) => item !== "pi");
121
+ const [messagesBySource, piSummaries] = yield* Effect.all([
122
+ Effect.all(
123
+ nonPiSources.map((sessionSource) =>
124
+ sessionService.getMessageSummaries(
125
+ projectSessionFilter(projectSessions, sessionSource, all),
126
+ new Set([sessionSource]),
127
+ ),
128
+ ),
129
+ ),
130
+ sources.has("pi")
131
+ ? sessionService.getPiSessionSummaries(projectSessionFilter(projectSessions, "pi", all))
132
+ : Effect.succeed([]),
133
+ ]);
134
+ const messages = messagesBySource.flat();
135
+ const summaries = sortSessionSummaries([
136
+ ...sessionSummariesFromMessages(messages),
137
+ ...piSummaries,
138
+ ]);
139
+ const results = summaries.slice(0, limit).map((summary) => ({
140
+ createdAt: formatDate(summary.createdAt),
141
+ updatedAt: formatDate(summary.updatedAt),
142
+ sessionID: summary.sessionID,
143
+ title: summary.title,
144
+ source: summary.source,
145
+ }));
146
+
147
+ return {
148
+ success: true,
149
+ data: {
150
+ results,
151
+ scope,
152
+ },
153
+ scope,
154
+ count: results.length,
155
+ executionTimeMs: Date.now() - startTime,
156
+ } satisfies SessionResult;
157
+ }).pipe(Effect.result);
158
+
159
+ const output = Result.match(result, {
160
+ onFailure: (error) =>
161
+ ({
162
+ success: false,
163
+ error: error.message,
164
+ data: error instanceof SessionStorageNotFoundError ? { path: error.path } : undefined,
165
+ scope,
166
+ count: 0,
167
+ executionTimeMs: Date.now() - startTime,
168
+ }) satisfies SessionResult,
169
+ onSuccess: (okResult) => okResult,
170
+ });
171
+
172
+ yield* logText(formatOutput(output, format));
173
+ }),
174
+ ).pipe(Command.withDescription("List OpenCode session summaries"));
175
+
176
+ const searchCommand = Command.make(
177
+ "search",
178
+ {
179
+ query: Argument.String("query").pipe(Argument.withDescription("Search query")),
180
+ all: Flag.Boolean("all").pipe(
181
+ Flag.withDescription("Search all projects"),
182
+ Flag.withDefault(false),
183
+ ),
184
+ format: formatOption,
185
+ bodyChars: Flag.Int("body-chars").pipe(
186
+ Flag.withDescription("Max message body characters per result (0 = full bodies)"),
187
+ Flag.withDefault(500),
188
+ ),
189
+ limit: Flag.Int("limit").pipe(Flag.withDescription("Limit result count"), Flag.withDefault(10)),
190
+ source: sourceOption,
191
+ },
192
+ ({ all, bodyChars, format, limit, query, source }) =>
193
+ Effect.gen(function* () {
194
+ const sessionService = yield* SessionService;
195
+ const startTime = Date.now();
196
+ const currentDir = process.cwd();
197
+ const scope = buildScopeLabel(all, currentDir);
198
+
199
+ const result = yield* Effect.gen(function* () {
200
+ const sessionFilter = all ? null : yield* sessionService.getSessionsForProject(currentDir);
201
+
202
+ if (sessionFilter !== null && sessionFilter.size === 0) {
203
+ return {
204
+ success: false,
205
+ query,
206
+ error: "No sessions found for current project",
207
+ data: {
208
+ project: currentDir,
209
+ results: [],
210
+ },
211
+ scope,
212
+ count: 0,
213
+ executionTimeMs: Date.now() - startTime,
214
+ } satisfies SessionResult;
215
+ }
216
+
217
+ const allSummaries = yield* sessionService.getMessageSummaries(sessionFilter);
218
+ const summaries = filterBySource(allSummaries, source);
219
+ const matched = sessionService.searchSummaries(summaries, query);
220
+ const mappedResults = yield* Effect.all(matched.slice(0, limit).map(mapSummary(bodyChars)));
221
+
222
+ return {
223
+ success: true,
224
+ query,
225
+ data: {
226
+ count: mappedResults.length,
227
+ query,
228
+ results: mappedResults,
229
+ scope,
230
+ },
231
+ scope,
232
+ count: mappedResults.length,
233
+ executionTimeMs: Date.now() - startTime,
234
+ } satisfies SessionResult;
235
+ }).pipe(Effect.result);
236
+
237
+ const output = Result.match(result, {
238
+ onFailure: (error) =>
239
+ ({
240
+ success: false,
241
+ query,
242
+ error: error.message,
243
+ data:
244
+ error instanceof SessionStorageNotFoundError
245
+ ? { path: error.path, results: [] }
246
+ : { results: [] },
247
+ scope,
248
+ count: 0,
249
+ executionTimeMs: Date.now() - startTime,
250
+ }) satisfies SessionResult,
251
+ onSuccess: (okResult) => okResult,
252
+ });
253
+
254
+ yield* logText(formatOutput(output, format));
255
+ }),
256
+ ).pipe(Command.withDescription("Search message history"));
257
+
258
+ const readCommand = Command.make(
259
+ "read",
260
+ {
261
+ session: Flag.String("session").pipe(Flag.withDescription("Session ID to read")),
262
+ format: formatOption,
263
+ source: sourceOption,
264
+ },
265
+ ({ format, session, source }) =>
266
+ Effect.gen(function* () {
267
+ const sessionService = yield* SessionService;
268
+ const startTime = Date.now();
269
+
270
+ const result = yield* sessionService
271
+ .getMessageSummaries(new Set([session]))
272
+ .pipe(Effect.result);
273
+
274
+ const output: SessionResult = yield* Result.match(result, {
275
+ onFailure: (error) =>
276
+ Effect.succeed({
277
+ success: false,
278
+ error: error.message,
279
+ data:
280
+ error instanceof SessionStorageNotFoundError
281
+ ? { path: error.path, session }
282
+ : { session },
283
+ count: 0,
284
+ executionTimeMs: Date.now() - startTime,
285
+ } satisfies SessionResult),
286
+ onSuccess: (summaries) => {
287
+ const filtered = filterBySource(summaries, source);
288
+ const sessionResults = filtered.filter((summary) => summary.sessionID === session);
289
+ return Effect.all(sessionResults.map(mapSummary(0))).pipe(
290
+ Effect.map(
291
+ (mapped) =>
292
+ ({
293
+ success: true,
294
+ data: {
295
+ files: mapped
296
+ .map((message) =>
297
+ "messagePath" in message ? (message.messagePath as string) : null,
298
+ )
299
+ .filter((filePath): filePath is string => filePath !== null),
300
+ messages: mapped,
301
+ session,
302
+ },
303
+ count: mapped.length,
304
+ executionTimeMs: Date.now() - startTime,
305
+ }) satisfies SessionResult,
306
+ ),
307
+ );
308
+ },
309
+ });
310
+
311
+ yield* logText(formatOutput(output, format));
312
+ }),
313
+ ).pipe(Command.withDescription("Read all messages from a session"));
314
+
315
+ const commandsCommand = makeSchemaCommand(() => mainCommand);
316
+
317
+ const mainCommand = Command.make("session-tool", {}).pipe(
318
+ Command.withDescription("OpenCode session history tool"),
319
+ Command.withSubcommands([listCommand, readCommand, searchCommand, commandsCommand]),
320
+ );
321
+
322
+ const cli = Command.run(mainCommand, {
323
+ version: VERSION,
324
+ });
325
+
326
+ export const run = Command.runWith(mainCommand, {
327
+ version: VERSION,
328
+ });
329
+
330
+ const MainLayer = AppLayer.pipe(
331
+ Layer.provideMerge(BunServices.layer),
332
+ Layer.provideMerge(AuditServiceLayer),
333
+ );
334
+
335
+ const program = withAudit("session", cli).pipe(Effect.provide(MainLayer));
336
+
337
+ BunRuntime.runMain(program, {
338
+ disableErrorReporting: true,
339
+ });