@blogic-cz/agent-tools 1.5.3 → 1.5.5

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 (51) hide show
  1. package/README.md +7 -6
  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/main.d.ts +3 -0
  12. package/dist/gh-tool/main.d.ts.map +1 -0
  13. package/dist/gh-tool/pr/core.d.ts.map +1 -1
  14. package/dist/gh-tool/workflow.d.ts.map +1 -1
  15. package/dist/k8s-tool/main.d.ts +3 -0
  16. package/dist/k8s-tool/main.d.ts.map +1 -0
  17. package/dist/logs-tool/index.d.ts +1 -19
  18. package/dist/logs-tool/index.d.ts.map +1 -1
  19. package/dist/logs-tool/main.d.ts +21 -0
  20. package/dist/logs-tool/main.d.ts.map +1 -0
  21. package/dist/observability-tool/main.d.ts +3 -0
  22. package/dist/observability-tool/main.d.ts.map +1 -0
  23. package/dist/session-tool/index.d.ts +1 -11
  24. package/dist/session-tool/index.d.ts.map +1 -1
  25. package/dist/session-tool/main.d.ts +13 -0
  26. package/dist/session-tool/main.d.ts.map +1 -0
  27. package/dist/shared/effect-version.d.ts +4 -0
  28. package/dist/shared/effect-version.d.ts.map +1 -0
  29. package/package.json +2 -2
  30. package/src/audit-tool/index.ts +3 -109
  31. package/src/audit-tool/main.ts +111 -0
  32. package/src/az-tool/index.ts +3 -151
  33. package/src/az-tool/main.ts +153 -0
  34. package/src/azdo-tool/index.ts +3 -220
  35. package/src/azdo-tool/main.ts +222 -0
  36. package/src/credential-guard/index.ts +570 -96
  37. package/src/db-tool/index.ts +3 -260
  38. package/src/db-tool/main.ts +262 -0
  39. package/src/gh-tool/index.ts +3 -256
  40. package/src/gh-tool/main.ts +258 -0
  41. package/src/gh-tool/pr/core.ts +52 -2
  42. package/src/gh-tool/workflow.ts +1 -0
  43. package/src/k8s-tool/index.ts +3 -487
  44. package/src/k8s-tool/main.ts +489 -0
  45. package/src/logs-tool/index.ts +3 -247
  46. package/src/logs-tool/main.ts +249 -0
  47. package/src/observability-tool/index.ts +3 -34
  48. package/src/observability-tool/main.ts +36 -0
  49. package/src/session-tool/index.ts +3 -337
  50. package/src/session-tool/main.ts +339 -0
  51. package/src/shared/effect-version.ts +32 -0
@@ -1,262 +1,5 @@
1
1
  #!/usr/bin/env bun
2
- import { Command, Flag } from "effect/unstable/cli";
3
- import { BunRuntime, BunServices } from "@effect/platform-bun";
4
- import { Effect, Layer, Option } from "effect";
2
+ import { checkEffectVersion } from "#shared/effect-version";
5
3
 
6
- import type { SchemaMode } from "./types";
7
-
8
- import {
9
- makeSchemaCommand,
10
- formatOption,
11
- formatOutput,
12
- logText,
13
- renderCauseToStderr,
14
- VERSION,
15
- } from "#shared";
16
- import { AuditServiceLayer, withAudit } from "#shared/audit";
17
- import { ConfigService, ConfigServiceLayer, getDefaultEnvironment } from "#config";
18
- import { DbConfigService, makeDbConfigLayer } from "./config-service";
19
- import { DbConnectionError } from "./errors";
20
- import { DbService } from "./service";
21
- import { DbSqlClient } from "./sql-client";
22
-
23
- // Extract --profile from argv before @effect/cli parsing
24
- // so we can build the correct config layer.
25
- const profileIndex = process.argv.indexOf("--profile");
26
- const profileArg = profileIndex !== -1 ? process.argv[profileIndex + 1] : undefined;
27
-
28
- /**
29
- * Resolve environment from explicit --env flag, config defaultEnvironment, or fail with hint.
30
- */
31
- const resolveEnv = (envOption: Option.Option<string>) =>
32
- Effect.gen(function* () {
33
- const explicit = Option.getOrUndefined(envOption);
34
- if (explicit) return explicit;
35
-
36
- const config = yield* ConfigService;
37
- const defaultEnv = getDefaultEnvironment(config);
38
-
39
- if (defaultEnv === "prod") {
40
- return yield* new DbConnectionError({
41
- message:
42
- "Implicit prod access blocked. Config defaultEnvironment is 'prod' but --env was not passed explicitly.",
43
- environment: "(prod-safety)",
44
- hint: "Pass --env prod explicitly to confirm production access, or change defaultEnvironment to a non-prod value.",
45
- nextCommand: 'agent-tools-db sql --env prod --sql "SELECT 1"',
46
- });
47
- }
48
-
49
- if (defaultEnv) return defaultEnv;
50
-
51
- return yield* new DbConnectionError({
52
- message:
53
- "No environment specified. Use --env <name> or set defaultEnvironment in agent-tools.json5.",
54
- environment: "(not specified)",
55
- hint: 'Set defaultEnvironment in agent-tools.json5 (e.g. defaultEnvironment: "local") or pass --env explicitly.',
56
- nextCommand: 'agent-tools-db sql --env local --sql "SELECT 1"',
57
- });
58
- });
59
-
60
- const sqlCommand = Command.make(
61
- "sql",
62
- {
63
- env: Flag.optional(Flag.String("env")).pipe(
64
- Flag.withDescription(
65
- "Target database environment name (e.g. local, test, prod). Falls back to defaultEnvironment in config.",
66
- ),
67
- ),
68
- sql: Flag.String("sql").pipe(Flag.withDescription("SQL query to execute")),
69
- limit: Flag.optional(Flag.Int("limit")).pipe(
70
- Flag.withDescription(
71
- "Max rows to return (default 50). Use 0 for no cap. Prefer a SQL LIMIT for large tables.",
72
- ),
73
- ),
74
- format: formatOption,
75
- profile: Flag.optional(Flag.String("profile")).pipe(
76
- Flag.withDescription("Database profile name from agent-tools.json5 (if multiple configured)"),
77
- ),
78
- },
79
- ({ env, sql, limit, format }) =>
80
- Effect.gen(function* () {
81
- const resolvedEnv = yield* resolveEnv(env);
82
- const db = yield* DbService;
83
- const result = yield* db.executeQuery(resolvedEnv, sql, Option.getOrUndefined(limit));
84
- yield* logText(formatOutput(result, format));
85
- }),
86
- ).pipe(Command.withDescription("Execute a SQL query"));
87
-
88
- const schemaCommand = Command.make(
89
- "schema",
90
- {
91
- env: Flag.optional(Flag.String("env")).pipe(
92
- Flag.withDescription(
93
- "Target database environment name (e.g. local, test, prod). Falls back to defaultEnvironment in config.",
94
- ),
95
- ),
96
- mode: Flag.Literals("mode", ["tables", "columns", "full", "relationships"]).pipe(
97
- Flag.withDescription(
98
- "Schema introspection mode: tables (list all), columns (show columns for --table), full (all tables with columns), relationships (foreign keys)",
99
- ),
100
- ),
101
- table: Flag.String("table").pipe(
102
- Flag.withDescription("Table name (required for --mode columns)"),
103
- Flag.optional,
104
- ),
105
- format: formatOption,
106
- profile: Flag.optional(Flag.String("profile")).pipe(
107
- Flag.withDescription("Database profile name from agent-tools.json5 (if multiple configured)"),
108
- ),
109
- },
110
- ({ env, mode, table, format }) =>
111
- Effect.gen(function* () {
112
- const resolvedEnv = yield* resolveEnv(env);
113
- const db = yield* DbService;
114
- const result = yield* db.executeSchemaQuery(
115
- resolvedEnv,
116
- mode as SchemaMode,
117
- Option.getOrUndefined(table),
118
- );
119
- yield* logText(formatOutput(result, format));
120
- }),
121
- ).pipe(Command.withDescription("Introspect database schema (tables, columns, relationships)"));
122
-
123
- const envsCommand = Command.make(
124
- "envs",
125
- {
126
- format: formatOption,
127
- profile: Flag.optional(Flag.String("profile")).pipe(
128
- Flag.withDescription("Database profile name from agent-tools.json5 (if multiple configured)"),
129
- ),
130
- },
131
- ({ format }) =>
132
- Effect.gen(function* () {
133
- const config = yield* ConfigService;
134
- const dbConfig = yield* DbConfigService;
135
- const environments = dbConfig ? Object.keys(dbConfig.environments) : [];
136
- const result = {
137
- success: true as const,
138
- environments,
139
- default: getDefaultEnvironment(config) ?? null,
140
- message: `${environments.length} environment(s) configured`,
141
- executionTimeMs: 0,
142
- };
143
- yield* logText(formatOutput(result, format));
144
- }),
145
- ).pipe(
146
- Command.withDescription("List configured database environments and the default (no network)"),
147
- );
148
-
149
- const ACTIVITY_SQL = `SELECT pid, usename AS "user", state,
150
- date_trunc('second', now() - xact_start) AS xact_age,
151
- date_trunc('second', now() - query_start) AS query_age,
152
- wait_event_type, wait_event, left(query, 200) AS query
153
- FROM pg_stat_activity
154
- WHERE pid <> pg_backend_pid() AND state IS NOT NULL
155
- ORDER BY xact_start ASC NULLS LAST`;
156
-
157
- const LOCKS_SQL = `SELECT blocking.pid AS blocking_pid, blocking.usename AS blocking_user,
158
- left(blocking.query, 150) AS blocking_query,
159
- blocked.pid AS blocked_pid, blocked.usename AS blocked_user,
160
- left(blocked.query, 150) AS blocked_query
161
- FROM pg_stat_activity blocked
162
- JOIN pg_stat_activity blocking ON blocking.pid = ANY(pg_blocking_pids(blocked.pid))
163
- ORDER BY blocking.pid, blocked.pid`;
164
-
165
- const GRANTS_SQL = `SELECT grantee, table_schema AS schema, 'table' AS kind, table_name AS object,
166
- string_agg(privilege_type, ', ' ORDER BY privilege_type) AS privileges
167
- FROM information_schema.role_table_grants
168
- WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
169
- GROUP BY grantee, table_schema, table_name
170
- UNION ALL
171
- SELECT grantee, object_schema AS schema, 'sequence' AS kind, object_name AS object,
172
- string_agg(privilege_type, ', ' ORDER BY privilege_type) AS privileges
173
- FROM information_schema.role_usage_grants
174
- WHERE object_type = 'SEQUENCE' AND object_schema NOT IN ('pg_catalog', 'information_schema')
175
- GROUP BY grantee, object_schema, object_name
176
- ORDER BY grantee, schema, kind, object`;
177
-
178
- const makeDiagnosticCommand = (name: string, description: string, sql: string) =>
179
- Command.make(
180
- name,
181
- {
182
- env: Flag.optional(Flag.String("env")).pipe(
183
- Flag.withDescription(
184
- "Target database environment name (e.g. local, test, prod). Falls back to defaultEnvironment in config.",
185
- ),
186
- ),
187
- limit: Flag.optional(Flag.Int("limit")).pipe(
188
- Flag.withDescription("Max rows to return (default 50). Use 0 for no cap."),
189
- ),
190
- format: formatOption,
191
- profile: Flag.optional(Flag.String("profile")).pipe(
192
- Flag.withDescription(
193
- "Database profile name from agent-tools.json5 (if multiple configured)",
194
- ),
195
- ),
196
- },
197
- ({ env, limit, format }) =>
198
- Effect.gen(function* () {
199
- const resolvedEnv = yield* resolveEnv(env);
200
- const db = yield* DbService;
201
- const result = yield* db.executeQuery(resolvedEnv, sql, Option.getOrUndefined(limit));
202
- yield* logText(formatOutput(result, format));
203
- }),
204
- ).pipe(Command.withDescription(description));
205
-
206
- const activityCommand = makeDiagnosticCommand(
207
- "activity",
208
- "Show live sessions from pg_stat_activity (state, transaction/query age, wait event), longest-running first",
209
- ACTIVITY_SQL,
210
- );
211
-
212
- const locksCommand = makeDiagnosticCommand(
213
- "locks",
214
- "Show blocking chains (blocker -> blocked) from pg_blocking_pids + pg_stat_activity",
215
- LOCKS_SQL,
216
- );
217
-
218
- const grantsCommand = makeDiagnosticCommand(
219
- "grants",
220
- "Show per-role table/sequence privileges by schema (excludes system schemas)",
221
- GRANTS_SQL,
222
- );
223
-
224
- const commandsCommand = makeSchemaCommand(() => mainCommand);
225
-
226
- const mainCommand = Command.make("db-tool", {}).pipe(
227
- Command.withDescription("Database Query Tool for Coding Agents"),
228
- Command.withSubcommands([
229
- sqlCommand,
230
- schemaCommand,
231
- activityCommand,
232
- locksCommand,
233
- grantsCommand,
234
- envsCommand,
235
- commandsCommand,
236
- ]),
237
- );
238
-
239
- const cli = Command.run(mainCommand, {
240
- version: VERSION,
241
- });
242
-
243
- const dbConfigLayer = makeDbConfigLayer(profileArg);
244
-
245
- const MainLayer = DbService.layer.pipe(
246
- // provideMerge (not provide) so DbConfigService stays in the program context for the `envs` command,
247
- // which reads it directly rather than going through DbService.
248
- Layer.provideMerge(DbSqlClient.layer),
249
- Layer.provideMerge(dbConfigLayer),
250
- Layer.provideMerge(ConfigServiceLayer),
251
- Layer.provideMerge(BunServices.layer),
252
- Layer.provideMerge(AuditServiceLayer),
253
- );
254
-
255
- const program = withAudit("db", cli).pipe(
256
- Effect.provide(MainLayer),
257
- Effect.tapCause(renderCauseToStderr),
258
- );
259
-
260
- BunRuntime.runMain(program, {
261
- disableErrorReporting: true,
262
- });
4
+ if (await checkEffectVersion()) await import("./main");
5
+ else process.exitCode = 1;
@@ -0,0 +1,262 @@
1
+ #!/usr/bin/env bun
2
+ import { Command, Flag } from "effect/unstable/cli";
3
+ import { BunRuntime, BunServices } from "@effect/platform-bun";
4
+ import { Effect, Layer, Option } from "effect";
5
+
6
+ import type { SchemaMode } from "./types";
7
+
8
+ import {
9
+ makeSchemaCommand,
10
+ formatOption,
11
+ formatOutput,
12
+ logText,
13
+ renderCauseToStderr,
14
+ VERSION,
15
+ } from "#shared";
16
+ import { AuditServiceLayer, withAudit } from "#shared/audit";
17
+ import { ConfigService, ConfigServiceLayer, getDefaultEnvironment } from "#config";
18
+ import { DbConfigService, makeDbConfigLayer } from "./config-service";
19
+ import { DbConnectionError } from "./errors";
20
+ import { DbService } from "./service";
21
+ import { DbSqlClient } from "./sql-client";
22
+
23
+ // Extract --profile from argv before @effect/cli parsing
24
+ // so we can build the correct config layer.
25
+ const profileIndex = process.argv.indexOf("--profile");
26
+ const profileArg = profileIndex !== -1 ? process.argv[profileIndex + 1] : undefined;
27
+
28
+ /**
29
+ * Resolve environment from explicit --env flag, config defaultEnvironment, or fail with hint.
30
+ */
31
+ const resolveEnv = (envOption: Option.Option<string>) =>
32
+ Effect.gen(function* () {
33
+ const explicit = Option.getOrUndefined(envOption);
34
+ if (explicit) return explicit;
35
+
36
+ const config = yield* ConfigService;
37
+ const defaultEnv = getDefaultEnvironment(config);
38
+
39
+ if (defaultEnv === "prod") {
40
+ return yield* new DbConnectionError({
41
+ message:
42
+ "Implicit prod access blocked. Config defaultEnvironment is 'prod' but --env was not passed explicitly.",
43
+ environment: "(prod-safety)",
44
+ hint: "Pass --env prod explicitly to confirm production access, or change defaultEnvironment to a non-prod value.",
45
+ nextCommand: 'agent-tools-db sql --env prod --sql "SELECT 1"',
46
+ });
47
+ }
48
+
49
+ if (defaultEnv) return defaultEnv;
50
+
51
+ return yield* new DbConnectionError({
52
+ message:
53
+ "No environment specified. Use --env <name> or set defaultEnvironment in agent-tools.json5.",
54
+ environment: "(not specified)",
55
+ hint: 'Set defaultEnvironment in agent-tools.json5 (e.g. defaultEnvironment: "local") or pass --env explicitly.',
56
+ nextCommand: 'agent-tools-db sql --env local --sql "SELECT 1"',
57
+ });
58
+ });
59
+
60
+ const sqlCommand = Command.make(
61
+ "sql",
62
+ {
63
+ env: Flag.optional(Flag.String("env")).pipe(
64
+ Flag.withDescription(
65
+ "Target database environment name (e.g. local, test, prod). Falls back to defaultEnvironment in config.",
66
+ ),
67
+ ),
68
+ sql: Flag.String("sql").pipe(Flag.withDescription("SQL query to execute")),
69
+ limit: Flag.optional(Flag.Int("limit")).pipe(
70
+ Flag.withDescription(
71
+ "Max rows to return (default 50). Use 0 for no cap. Prefer a SQL LIMIT for large tables.",
72
+ ),
73
+ ),
74
+ format: formatOption,
75
+ profile: Flag.optional(Flag.String("profile")).pipe(
76
+ Flag.withDescription("Database profile name from agent-tools.json5 (if multiple configured)"),
77
+ ),
78
+ },
79
+ ({ env, sql, limit, format }) =>
80
+ Effect.gen(function* () {
81
+ const resolvedEnv = yield* resolveEnv(env);
82
+ const db = yield* DbService;
83
+ const result = yield* db.executeQuery(resolvedEnv, sql, Option.getOrUndefined(limit));
84
+ yield* logText(formatOutput(result, format));
85
+ }),
86
+ ).pipe(Command.withDescription("Execute a SQL query"));
87
+
88
+ const schemaCommand = Command.make(
89
+ "schema",
90
+ {
91
+ env: Flag.optional(Flag.String("env")).pipe(
92
+ Flag.withDescription(
93
+ "Target database environment name (e.g. local, test, prod). Falls back to defaultEnvironment in config.",
94
+ ),
95
+ ),
96
+ mode: Flag.Literals("mode", ["tables", "columns", "full", "relationships"]).pipe(
97
+ Flag.withDescription(
98
+ "Schema introspection mode: tables (list all), columns (show columns for --table), full (all tables with columns), relationships (foreign keys)",
99
+ ),
100
+ ),
101
+ table: Flag.String("table").pipe(
102
+ Flag.withDescription("Table name (required for --mode columns)"),
103
+ Flag.optional,
104
+ ),
105
+ format: formatOption,
106
+ profile: Flag.optional(Flag.String("profile")).pipe(
107
+ Flag.withDescription("Database profile name from agent-tools.json5 (if multiple configured)"),
108
+ ),
109
+ },
110
+ ({ env, mode, table, format }) =>
111
+ Effect.gen(function* () {
112
+ const resolvedEnv = yield* resolveEnv(env);
113
+ const db = yield* DbService;
114
+ const result = yield* db.executeSchemaQuery(
115
+ resolvedEnv,
116
+ mode as SchemaMode,
117
+ Option.getOrUndefined(table),
118
+ );
119
+ yield* logText(formatOutput(result, format));
120
+ }),
121
+ ).pipe(Command.withDescription("Introspect database schema (tables, columns, relationships)"));
122
+
123
+ const envsCommand = Command.make(
124
+ "envs",
125
+ {
126
+ format: formatOption,
127
+ profile: Flag.optional(Flag.String("profile")).pipe(
128
+ Flag.withDescription("Database profile name from agent-tools.json5 (if multiple configured)"),
129
+ ),
130
+ },
131
+ ({ format }) =>
132
+ Effect.gen(function* () {
133
+ const config = yield* ConfigService;
134
+ const dbConfig = yield* DbConfigService;
135
+ const environments = dbConfig ? Object.keys(dbConfig.environments) : [];
136
+ const result = {
137
+ success: true as const,
138
+ environments,
139
+ default: getDefaultEnvironment(config) ?? null,
140
+ message: `${environments.length} environment(s) configured`,
141
+ executionTimeMs: 0,
142
+ };
143
+ yield* logText(formatOutput(result, format));
144
+ }),
145
+ ).pipe(
146
+ Command.withDescription("List configured database environments and the default (no network)"),
147
+ );
148
+
149
+ const ACTIVITY_SQL = `SELECT pid, usename AS "user", state,
150
+ date_trunc('second', now() - xact_start) AS xact_age,
151
+ date_trunc('second', now() - query_start) AS query_age,
152
+ wait_event_type, wait_event, left(query, 200) AS query
153
+ FROM pg_stat_activity
154
+ WHERE pid <> pg_backend_pid() AND state IS NOT NULL
155
+ ORDER BY xact_start ASC NULLS LAST`;
156
+
157
+ const LOCKS_SQL = `SELECT blocking.pid AS blocking_pid, blocking.usename AS blocking_user,
158
+ left(blocking.query, 150) AS blocking_query,
159
+ blocked.pid AS blocked_pid, blocked.usename AS blocked_user,
160
+ left(blocked.query, 150) AS blocked_query
161
+ FROM pg_stat_activity blocked
162
+ JOIN pg_stat_activity blocking ON blocking.pid = ANY(pg_blocking_pids(blocked.pid))
163
+ ORDER BY blocking.pid, blocked.pid`;
164
+
165
+ const GRANTS_SQL = `SELECT grantee, table_schema AS schema, 'table' AS kind, table_name AS object,
166
+ string_agg(privilege_type, ', ' ORDER BY privilege_type) AS privileges
167
+ FROM information_schema.role_table_grants
168
+ WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
169
+ GROUP BY grantee, table_schema, table_name
170
+ UNION ALL
171
+ SELECT grantee, object_schema AS schema, 'sequence' AS kind, object_name AS object,
172
+ string_agg(privilege_type, ', ' ORDER BY privilege_type) AS privileges
173
+ FROM information_schema.role_usage_grants
174
+ WHERE object_type = 'SEQUENCE' AND object_schema NOT IN ('pg_catalog', 'information_schema')
175
+ GROUP BY grantee, object_schema, object_name
176
+ ORDER BY grantee, schema, kind, object`;
177
+
178
+ const makeDiagnosticCommand = (name: string, description: string, sql: string) =>
179
+ Command.make(
180
+ name,
181
+ {
182
+ env: Flag.optional(Flag.String("env")).pipe(
183
+ Flag.withDescription(
184
+ "Target database environment name (e.g. local, test, prod). Falls back to defaultEnvironment in config.",
185
+ ),
186
+ ),
187
+ limit: Flag.optional(Flag.Int("limit")).pipe(
188
+ Flag.withDescription("Max rows to return (default 50). Use 0 for no cap."),
189
+ ),
190
+ format: formatOption,
191
+ profile: Flag.optional(Flag.String("profile")).pipe(
192
+ Flag.withDescription(
193
+ "Database profile name from agent-tools.json5 (if multiple configured)",
194
+ ),
195
+ ),
196
+ },
197
+ ({ env, limit, format }) =>
198
+ Effect.gen(function* () {
199
+ const resolvedEnv = yield* resolveEnv(env);
200
+ const db = yield* DbService;
201
+ const result = yield* db.executeQuery(resolvedEnv, sql, Option.getOrUndefined(limit));
202
+ yield* logText(formatOutput(result, format));
203
+ }),
204
+ ).pipe(Command.withDescription(description));
205
+
206
+ const activityCommand = makeDiagnosticCommand(
207
+ "activity",
208
+ "Show live sessions from pg_stat_activity (state, transaction/query age, wait event), longest-running first",
209
+ ACTIVITY_SQL,
210
+ );
211
+
212
+ const locksCommand = makeDiagnosticCommand(
213
+ "locks",
214
+ "Show blocking chains (blocker -> blocked) from pg_blocking_pids + pg_stat_activity",
215
+ LOCKS_SQL,
216
+ );
217
+
218
+ const grantsCommand = makeDiagnosticCommand(
219
+ "grants",
220
+ "Show per-role table/sequence privileges by schema (excludes system schemas)",
221
+ GRANTS_SQL,
222
+ );
223
+
224
+ const commandsCommand = makeSchemaCommand(() => mainCommand);
225
+
226
+ const mainCommand = Command.make("db-tool", {}).pipe(
227
+ Command.withDescription("Database Query Tool for Coding Agents"),
228
+ Command.withSubcommands([
229
+ sqlCommand,
230
+ schemaCommand,
231
+ activityCommand,
232
+ locksCommand,
233
+ grantsCommand,
234
+ envsCommand,
235
+ commandsCommand,
236
+ ]),
237
+ );
238
+
239
+ const cli = Command.run(mainCommand, {
240
+ version: VERSION,
241
+ });
242
+
243
+ const dbConfigLayer = makeDbConfigLayer(profileArg);
244
+
245
+ const MainLayer = DbService.layer.pipe(
246
+ // provideMerge (not provide) so DbConfigService stays in the program context for the `envs` command,
247
+ // which reads it directly rather than going through DbService.
248
+ Layer.provideMerge(DbSqlClient.layer),
249
+ Layer.provideMerge(dbConfigLayer),
250
+ Layer.provideMerge(ConfigServiceLayer),
251
+ Layer.provideMerge(BunServices.layer),
252
+ Layer.provideMerge(AuditServiceLayer),
253
+ );
254
+
255
+ const program = withAudit("db", cli).pipe(
256
+ Effect.provide(MainLayer),
257
+ Effect.tapCause(renderCauseToStderr),
258
+ );
259
+
260
+ BunRuntime.runMain(program, {
261
+ disableErrorReporting: true,
262
+ });