@blogic-cz/agent-tools 0.15.13 → 0.16.0

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.
package/README.md CHANGED
@@ -27,6 +27,11 @@ These tools wrap each CLI with:
27
27
  bun add -d @blogic-cz/agent-tools
28
28
  ```
29
29
 
30
+ `effect` is a peer dependency pinned to an exact version, and package managers install
31
+ it for you. If your project depends on `effect` too, pin it to the same exact version.
32
+ Two copies of `effect` are structurally incompatible types, so the values this package
33
+ exports stop typechecking in your project when the versions drift.
34
+
30
35
  ### Skill installation
31
36
 
32
37
  Install per-tool skills for only the tools your project uses:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blogic-cz/agent-tools",
3
- "version": "0.15.13",
3
+ "version": "0.16.0",
4
4
  "description": "CLI tools for AI coding agent workflows — GitHub, database, Kubernetes, Azure DevOps, logs, sessions, and audit",
5
5
  "keywords": [
6
6
  "agent",
@@ -39,90 +39,38 @@
39
39
  },
40
40
  "files": [
41
41
  "src/",
42
- "dist/",
43
42
  "schemas/",
44
43
  "README.md",
45
44
  "LICENSE"
46
45
  ],
47
46
  "type": "module",
48
- "types": "./dist/index.d.ts",
47
+ "types": "./src/index.ts",
49
48
  "imports": {
50
- "#shared": {
51
- "types": "./dist/shared/index.d.ts",
52
- "default": "./src/shared/index.ts"
53
- },
54
- "#shared/*": {
55
- "types": "./dist/shared/*.d.ts",
56
- "default": "./src/shared/*.ts"
57
- },
58
- "#config": {
59
- "types": "./dist/config/index.d.ts",
60
- "default": "./src/config/index.ts"
61
- },
62
- "#config/*": {
63
- "types": "./dist/config/*.d.ts",
64
- "default": "./src/config/*.ts"
65
- },
66
- "#gh/*": {
67
- "types": "./dist/gh-tool/*.d.ts",
68
- "default": "./src/gh-tool/*.ts"
69
- },
70
- "#k8s/*": {
71
- "types": "./dist/k8s-tool/*.d.ts",
72
- "default": "./src/k8s-tool/*.ts"
73
- },
74
- "#db/*": {
75
- "types": "./dist/db-tool/*.d.ts",
76
- "default": "./src/db-tool/*.ts"
77
- },
78
- "#observability/*": {
79
- "types": "./dist/observability-tool/*.d.ts",
80
- "default": "./src/observability-tool/*.ts"
81
- },
82
- "#logs/*": {
83
- "types": "./dist/logs-tool/*.d.ts",
84
- "default": "./src/logs-tool/*.ts"
85
- },
86
- "#az/*": {
87
- "types": "./dist/az-tool/*.d.ts",
88
- "default": "./src/az-tool/*.ts"
89
- },
90
- "#guard": {
91
- "types": "./dist/credential-guard/index.d.ts",
92
- "default": "./src/credential-guard/index.ts"
93
- },
94
- "#session/*": {
95
- "types": "./dist/session-tool/*.d.ts",
96
- "default": "./src/session-tool/*.ts"
97
- }
49
+ "#shared": "./src/shared/index.ts",
50
+ "#shared/*": "./src/shared/*.ts",
51
+ "#config": "./src/config/index.ts",
52
+ "#config/*": "./src/config/*.ts",
53
+ "#gh/*": "./src/gh-tool/*.ts",
54
+ "#k8s/*": "./src/k8s-tool/*.ts",
55
+ "#db/*": "./src/db-tool/*.ts",
56
+ "#observability/*": "./src/observability-tool/*.ts",
57
+ "#logs/*": "./src/logs-tool/*.ts",
58
+ "#az/*": "./src/az-tool/*.ts",
59
+ "#guard": "./src/credential-guard/index.ts",
60
+ "#session/*": "./src/session-tool/*.ts"
98
61
  },
99
62
  "exports": {
100
- ".": {
101
- "types": "./dist/index.d.ts",
102
- "default": "./src/index.ts"
103
- },
104
- "./credential-guard": {
105
- "types": "./dist/credential-guard/index.d.ts",
106
- "default": "./src/credential-guard/index.ts"
107
- },
108
- "./config": {
109
- "types": "./dist/config/index.d.ts",
110
- "default": "./src/config/index.ts"
111
- },
112
- "./audit": {
113
- "types": "./dist/shared/audit.d.ts",
114
- "default": "./src/shared/audit.ts"
115
- },
116
- "./k8s-probe": {
117
- "types": "./dist/shared/k8s-probe.d.ts",
118
- "default": "./src/shared/k8s-probe.ts"
119
- }
63
+ ".": "./src/index.ts",
64
+ "./credential-guard": "./src/credential-guard/index.ts",
65
+ "./config": "./src/config/index.ts",
66
+ "./audit": "./src/shared/audit.ts",
67
+ "./k8s-probe": "./src/shared/k8s-probe.ts",
68
+ "./prerequisites": "./src/shared/prerequisites/index.ts"
120
69
  },
121
70
  "publishConfig": {
122
71
  "access": "public"
123
72
  },
124
73
  "scripts": {
125
- "build:types": "tsc -p tsconfig.declarations.json",
126
74
  "check": "bun check.ts",
127
75
  "check:ci": "bun check.ts ci",
128
76
  "format": "oxfmt",
@@ -137,23 +85,26 @@
137
85
  "test": "vitest run"
138
86
  },
139
87
  "dependencies": {
140
- "@effect/platform-bun": "4.0.0-beta.98",
141
- "@toon-format/toon": "2.1.0",
142
- "effect": "4.0.0-beta.98"
88
+ "@effect/platform-bun": "4.0.0-beta.105",
89
+ "@toon-format/toon": "2.1.0"
143
90
  },
144
91
  "devDependencies": {
145
92
  "@effect/language-service": "0.86.2",
146
- "@effect/vitest": "4.0.0-beta.98",
93
+ "@effect/vitest": "4.0.0-beta.105",
147
94
  "@types/bun": "1.3.12",
95
+ "effect": "4.0.0-beta.105",
148
96
  "oxfmt": "0.44.0",
149
97
  "oxlint": "1.59.0",
150
98
  "typescript": "6.0.2",
151
99
  "vitest": "^4.1.4"
152
100
  },
101
+ "peerDependencies": {
102
+ "effect": "4.0.0-beta.105"
103
+ },
153
104
  "overrides": {
154
- "@effect/platform-node-shared": "4.0.0-beta.98"
105
+ "@effect/platform-node-shared": "4.0.0-beta.105"
155
106
  },
156
107
  "engines": {
157
- "bun": ">=1.0.0"
108
+ "bun": ">=1.3.0"
158
109
  }
159
110
  }
File without changes
@@ -1,6 +1,6 @@
1
1
  import { Schema } from "effect";
2
2
 
3
- export class AzSecurityError extends Schema.TaggedErrorClass<AzSecurityError>()("AzSecurityError", {
3
+ export class AzSecurityError extends Schema.TaggedError<AzSecurityError>()("AzSecurityError", {
4
4
  message: Schema.String,
5
5
  command: Schema.String,
6
6
  hint: Schema.optionalKey(Schema.String),
@@ -8,7 +8,7 @@ export class AzSecurityError extends Schema.TaggedErrorClass<AzSecurityError>()(
8
8
  retryable: Schema.optionalKey(Schema.Boolean),
9
9
  }) {}
10
10
 
11
- export class AzCommandError extends Schema.TaggedErrorClass<AzCommandError>()("AzCommandError", {
11
+ export class AzCommandError extends Schema.TaggedError<AzCommandError>()("AzCommandError", {
12
12
  message: Schema.String,
13
13
  command: Schema.String,
14
14
  exitCode: Schema.optionalKey(Schema.Number),
@@ -18,7 +18,7 @@ export class AzCommandError extends Schema.TaggedErrorClass<AzCommandError>()("A
18
18
  retryable: Schema.optionalKey(Schema.Boolean),
19
19
  }) {}
20
20
 
21
- export class AzTimeoutError extends Schema.TaggedErrorClass<AzTimeoutError>()("AzTimeoutError", {
21
+ export class AzTimeoutError extends Schema.TaggedError<AzTimeoutError>()("AzTimeoutError", {
22
22
  message: Schema.String,
23
23
  command: Schema.String,
24
24
  timeoutMs: Schema.Number,
@@ -27,7 +27,7 @@ export class AzTimeoutError extends Schema.TaggedErrorClass<AzTimeoutError>()("A
27
27
  retryable: Schema.optionalKey(Schema.Boolean),
28
28
  }) {}
29
29
 
30
- export class AzParseError extends Schema.TaggedErrorClass<AzParseError>()("AzParseError", {
30
+ export class AzParseError extends Schema.TaggedError<AzParseError>()("AzParseError", {
31
31
  message: Schema.String,
32
32
  rawOutput: Schema.String,
33
33
  hint: Schema.optionalKey(Schema.String),
@@ -141,7 +141,7 @@ export class AzService extends Context.Service<
141
141
  message: result.stderr || `Command failed with exit code ${result.exitCode}`,
142
142
  command: fullCommand,
143
143
  exitCode: result.exitCode,
144
- stderr: result.stderr || undefined,
144
+ ...(result.stderr ? { stderr: result.stderr } : {}),
145
145
  });
146
146
  }
147
147
 
@@ -209,7 +209,7 @@ export class AzService extends Context.Service<
209
209
  message: result.stderr || `Invoke failed with exit code ${result.exitCode}`,
210
210
  command: fullCommand,
211
211
  exitCode: result.exitCode,
212
- stderr: result.stderr || undefined,
212
+ ...(result.stderr ? { stderr: result.stderr } : {}),
213
213
  });
214
214
  }
215
215
 
@@ -1,6 +1,6 @@
1
1
  import { Schema } from "effect";
2
2
 
3
- export class DbConnectionError extends Schema.TaggedErrorClass<DbConnectionError>()(
3
+ export class DbConnectionError extends Schema.TaggedError<DbConnectionError>()(
4
4
  "DbConnectionError",
5
5
  {
6
6
  message: Schema.String,
@@ -11,7 +11,7 @@ export class DbConnectionError extends Schema.TaggedErrorClass<DbConnectionError
11
11
  },
12
12
  ) {}
13
13
 
14
- export class DbQueryError extends Schema.TaggedErrorClass<DbQueryError>()("DbQueryError", {
14
+ export class DbQueryError extends Schema.TaggedError<DbQueryError>()("DbQueryError", {
15
15
  message: Schema.String,
16
16
  sql: Schema.String,
17
17
  stderr: Schema.optionalKey(Schema.String),
@@ -20,7 +20,7 @@ export class DbQueryError extends Schema.TaggedErrorClass<DbQueryError>()("DbQue
20
20
  retryable: Schema.optionalKey(Schema.Boolean),
21
21
  }) {}
22
22
 
23
- export class DbTunnelError extends Schema.TaggedErrorClass<DbTunnelError>()("DbTunnelError", {
23
+ export class DbTunnelError extends Schema.TaggedError<DbTunnelError>()("DbTunnelError", {
24
24
  message: Schema.String,
25
25
  port: Schema.Number,
26
26
  hint: Schema.optionalKey(Schema.String),
@@ -28,15 +28,7 @@ export class DbTunnelError extends Schema.TaggedErrorClass<DbTunnelError>()("DbT
28
28
  retryable: Schema.optionalKey(Schema.Boolean),
29
29
  }) {}
30
30
 
31
- export class DbParseError extends Schema.TaggedErrorClass<DbParseError>()("DbParseError", {
32
- message: Schema.String,
33
- rawOutput: Schema.String,
34
- hint: Schema.optionalKey(Schema.String),
35
- nextCommand: Schema.optionalKey(Schema.String),
36
- retryable: Schema.optionalKey(Schema.Boolean),
37
- }) {}
38
-
39
- export class DbMutationBlockedError extends Schema.TaggedErrorClass<DbMutationBlockedError>()(
31
+ export class DbMutationBlockedError extends Schema.TaggedError<DbMutationBlockedError>()(
40
32
  "DbMutationBlockedError",
41
33
  {
42
34
  message: Schema.String,
@@ -47,9 +39,4 @@ export class DbMutationBlockedError extends Schema.TaggedErrorClass<DbMutationBl
47
39
  },
48
40
  ) {}
49
41
 
50
- export type DbError =
51
- | DbConnectionError
52
- | DbMutationBlockedError
53
- | DbParseError
54
- | DbQueryError
55
- | DbTunnelError;
42
+ export type DbError = DbConnectionError | DbMutationBlockedError | DbQueryError | DbTunnelError;
@@ -17,6 +17,7 @@ import { ConfigService, ConfigServiceLayer, getDefaultEnvironment } from "#confi
17
17
  import { DbConfigService, makeDbConfigLayer } from "./config-service";
18
18
  import { DbConnectionError } from "./errors";
19
19
  import { DbService } from "./service";
20
+ import { DbSqlClient } from "./sql-client";
20
21
 
21
22
  // Extract --profile from argv before @effect/cli parsing
22
23
  // so we can build the correct config layer.
@@ -243,6 +244,7 @@ const dbConfigLayer = makeDbConfigLayer(profileArg);
243
244
  const MainLayer = DbService.layer.pipe(
244
245
  // provideMerge (not provide) so DbConfigService stays in the program context for the `envs` command,
245
246
  // which reads it directly rather than going through DbService.
247
+ Layer.provideMerge(DbSqlClient.layer),
246
248
  Layer.provideMerge(dbConfigLayer),
247
249
  Layer.provideMerge(ConfigServiceLayer),
248
250
  Layer.provideMerge(BunServices.layer),
@@ -25,9 +25,27 @@ const MUTATION_TARGET_PATTERNS: Array<[DbMutationOperation, RegExp]> = [
25
25
  ["delete", new RegExp(String.raw`^\s*DELETE\s+FROM\s+(${QUALIFIED_IDENTIFIER_PATTERN})`, "i")],
26
26
  ];
27
27
 
28
+ const DOLLAR_QUOTE_TAG = /^\$(?:[A-Za-z_][A-Za-z0-9_]*)?\$/;
29
+
30
+ /**
31
+ * Postgres treats a backslash as an escape only inside an E'...' literal; in a standard string
32
+ * it is an ordinary character, so applying escapes everywhere would swallow a real closing quote.
33
+ */
34
+ function isEscapeStringPrefix(sql: string, quoteIndex: number): boolean {
35
+ const prefix = sql[quoteIndex - 1];
36
+ if (prefix !== "E" && prefix !== "e") return false;
37
+ const before = sql[quoteIndex - 2];
38
+ return before === undefined || !/[A-Za-z0-9_$]/.test(before);
39
+ }
40
+
28
41
  /**
29
- * Strip SQL comments (block and line) while preserving string literals.
30
- * This prevents bypass via inline comment masking before DELETE statements.
42
+ * Strip SQL comments (block and line) and empty out every string literal, keeping only
43
+ * double-quoted identifiers verbatim because the mutation-target parser needs them.
44
+ * Single quotes, double-quoted identifiers and dollar-quoted bodies must all be tracked:
45
+ * a quote or comment token surviving inside a region another check reads differently is a
46
+ * guard bypass. `SELECT * FROM "weird--table"; DROP TABLE users` would lose its statement
47
+ * separator to a phantom comment, and `SELECT $$O\'Brien$$ AS n; DROP TABLE users` would
48
+ * leave an unpaired apostrophe that swallows the separator instead.
31
49
  */
32
50
  export function stripSqlComments(sql: string): string {
33
51
  let result = "";
@@ -38,38 +56,63 @@ export function stripSqlComments(sql: string): string {
38
56
  const ch = sql[i];
39
57
  const next = i + 1 < len ? sql[i + 1] : "";
40
58
 
41
- // Single-quoted string literal — skip through
42
- if (ch === "'") {
59
+ if (ch === "'" || ch === '"') {
60
+ const keepBody = ch === '"';
61
+ const backslashEscapes = ch === "'" && isEscapeStringPrefix(sql, i);
43
62
  result += ch;
44
63
  i++;
45
64
  while (i < len) {
46
- if (sql[i] === "'" && i + 1 < len && sql[i + 1] === "'") {
47
- result += "''";
65
+ if (backslashEscapes && sql[i] === "\\" && i + 1 < len) {
66
+ if (keepBody) result += sql[i] + sql[i + 1];
48
67
  i += 2;
49
- } else if (sql[i] === "'") {
50
- result += "'";
68
+ } else if (sql[i] === ch && sql[i + 1] === ch) {
69
+ if (keepBody) result += ch + ch;
70
+ i += 2;
71
+ } else if (sql[i] === ch) {
72
+ result += ch;
51
73
  i++;
52
74
  break;
53
75
  } else {
54
- result += sql[i];
76
+ if (keepBody) result += sql[i];
55
77
  i++;
56
78
  }
57
79
  }
58
80
  continue;
59
81
  }
60
82
 
61
- // Block comment /* ... */
83
+ if (ch === "$") {
84
+ const tag = DOLLAR_QUOTE_TAG.exec(sql.slice(i))?.[0];
85
+ if (tag) {
86
+ const closing = sql.indexOf(tag, i + tag.length);
87
+ const stop = closing === -1 ? len : closing + tag.length;
88
+ result += closing === -1 ? tag : tag + tag;
89
+ i = stop;
90
+ continue;
91
+ }
92
+ }
93
+
94
+ // Postgres block comments nest, so track depth rather than stopping at the first */.
62
95
  if (ch === "/" && next === "*") {
96
+ let depth = 1;
63
97
  i += 2;
64
- while (i + 1 < len && !(sql[i] === "*" && sql[i + 1] === "/")) {
65
- i++;
98
+ while (i + 1 < len && depth > 0) {
99
+ if (sql[i] === "/" && sql[i + 1] === "*") {
100
+ depth++;
101
+ i += 2;
102
+ } else if (sql[i] === "*" && sql[i + 1] === "/") {
103
+ depth--;
104
+ i += 2;
105
+ } else {
106
+ i++;
107
+ }
108
+ }
109
+ if (depth > 0) {
110
+ i = len;
66
111
  }
67
- i += 2; // skip closing */
68
- result += " "; // replace comment with space
112
+ result += " ";
69
113
  continue;
70
114
  }
71
115
 
72
- // Line comment -- ...
73
116
  if (ch === "-" && next === "-") {
74
117
  i += 2;
75
118
  while (i < len && sql[i] !== "\n") {
@@ -86,6 +129,44 @@ export function stripSqlComments(sql: string): string {
86
129
  return result;
87
130
  }
88
131
 
132
+ /**
133
+ * Postgres runs every statement in a simple-query batch, but MUTATION_PATTERNS only match the
134
+ * first one, so `select 1; delete from x` would otherwise pass the guard as a plain read.
135
+ * Dollar-quoted bodies containing `;` are rejected as well: this fails closed by design.
136
+ */
137
+ export function hasMultipleStatements(sql: string): boolean {
138
+ const stripped = stripSqlComments(sql);
139
+ let index = 0;
140
+
141
+ while (index < stripped.length) {
142
+ const char = stripped[index];
143
+
144
+ if (char === "'" || char === '"') {
145
+ index++;
146
+ while (index < stripped.length) {
147
+ if (stripped[index] === char && stripped[index + 1] === char) {
148
+ index += 2;
149
+ continue;
150
+ }
151
+ if (stripped[index] === char) {
152
+ index++;
153
+ break;
154
+ }
155
+ index++;
156
+ }
157
+ continue;
158
+ }
159
+
160
+ if (char === ";" && stripped.slice(index + 1).trim().length > 0) {
161
+ return true;
162
+ }
163
+
164
+ index++;
165
+ }
166
+
167
+ return false;
168
+ }
169
+
89
170
  export function isMutationQuery(sql: string): boolean {
90
171
  const stripped = stripSqlComments(sql);
91
172
  return MUTATION_PATTERNS.some((pattern) => pattern.test(stripped));