@blogic-cz/agent-tools 0.14.20 → 0.14.21

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
@@ -383,6 +383,39 @@ Secrets are **never** stored in the config file. The `db-tool` config references
383
383
  }
384
384
  ```
385
385
 
386
+ Database VPN prerequisites can be set at the database profile or environment level. If an environment declares `vpn` or `prerequisites`, that environment config replaces the profile prerequisites; `prerequisites: []` explicitly disables inherited VPN setup. DB commands try the query directly first and only connect VPN prerequisites if direct access fails.
387
+
388
+ ```json5
389
+ {
390
+ vpns: {
391
+ officeVpn: { name: "OfficeVPN" },
392
+ prodVpn: { name: "ProdVPN" },
393
+ },
394
+ database: {
395
+ default: {
396
+ vpn: "officeVpn",
397
+ environments: {
398
+ local: {
399
+ host: "127.0.0.1",
400
+ port: 5432,
401
+ user: "app",
402
+ database: "app",
403
+ prerequisites: [], // no VPN for local/direct access
404
+ },
405
+ prod: {
406
+ host: "db.prod.internal",
407
+ port: 5432,
408
+ user: "readonly",
409
+ database: "app",
410
+ passwordEnvVar: "AGENT_TOOLS_DB_PROD_PASSWORD",
411
+ vpn: "prodVpn", // overrides database.default.vpn
412
+ },
413
+ },
414
+ },
415
+ },
416
+ }
417
+ ```
418
+
386
419
  Set the values in your shell:
387
420
 
388
421
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blogic-cz/agent-tools",
3
- "version": "0.14.20",
3
+ "version": "0.14.21",
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",
@@ -185,6 +185,17 @@
185
185
  "passwordEnvVar": {
186
186
  "description": "Name of the environment variable holding the database password.",
187
187
  "type": "string"
188
+ },
189
+ "prerequisites": {
190
+ "description": "Ordered prerequisite references for this database environment. Declaring this or vpn overrides profile-level database prerequisites; an empty array explicitly disables inherited prerequisites.",
191
+ "type": "array",
192
+ "items": {
193
+ "$ref": "#/definitions/Prerequisite"
194
+ }
195
+ },
196
+ "vpn": {
197
+ "type": "string",
198
+ "description": "Convenience sugar for this database environment's VPN prerequisite key. Declaring this or prerequisites overrides profile-level database prerequisites."
188
199
  }
189
200
  },
190
201
  "required": ["host", "port", "user", "database"]
@@ -91,6 +91,8 @@ const DbEnvConfigSchema = Schema.Struct({
91
91
  database: Schema.String,
92
92
  password: Schema.optionalKey(Schema.String),
93
93
  passwordEnvVar: Schema.optionalKey(Schema.String),
94
+ prerequisites: Schema.optionalKey(PrerequisitesSchema),
95
+ vpn: Schema.optionalKey(Schema.String),
94
96
  });
95
97
 
96
98
  const DatabaseConfigSchema = Schema.Struct({
@@ -74,7 +74,7 @@ export type K8sConfig = ProfilePrerequisites & {
74
74
  };
75
75
 
76
76
  /** Single database environment connection details */
77
- export type DbEnvConfig = {
77
+ export type DbEnvConfig = ProfilePrerequisites & {
78
78
  host: string;
79
79
  port: number;
80
80
  user: string;
@@ -6,6 +6,7 @@ import type { DbConfig, DbMutationOperation, QueryResult, SchemaMode } from "./t
6
6
  import { ConfigService } from "#config";
7
7
  import { isPrerequisiteRunError } from "#shared/prerequisites/errors";
8
8
  import { resolveEnvTemplate } from "#shared/env-template";
9
+ import { resolveEnvironmentScopedPrerequisites } from "#shared/prerequisites/config";
9
10
  import { runWithProfilePrerequisites } from "#shared/prerequisites/runtime";
10
11
  import { DbConfigService, DbConfigServiceLayer, TUNNEL_CHECK_INTERVAL_MS } from "./config-service";
11
12
  import {
@@ -228,13 +229,15 @@ export class DbService extends Context.Service<
228
229
 
229
230
  const runWithVpnPrerequisites = <E>(
230
231
  port: number,
232
+ prerequisiteConfig: DbConfig,
231
233
  effect: Effect.Effect<QueryResult, E>,
232
234
  ): Effect.Effect<QueryResult, E | DbTunnelError> =>
233
235
  runWithProfilePrerequisites(
234
236
  agentToolsConfig ?? {},
235
- dbConfig,
237
+ prerequisiteConfig,
236
238
  (command, _label) => executeShellCommand(command),
237
239
  effect,
240
+ { tryWithoutPrerequisites: true },
238
241
  ).pipe(
239
242
  Effect.mapError((error) =>
240
243
  isPrerequisiteRunError(error)
@@ -655,6 +658,7 @@ export class DbService extends Context.Service<
655
658
  database: envConfig.database,
656
659
  password: envConfig.password,
657
660
  passwordEnvVar: envConfig.passwordEnvVar,
661
+ ...resolveEnvironmentScopedPrerequisites(dbConfig, envConfig),
658
662
  port: envConfig.port,
659
663
  needsTunnel: accessMode.needsTunnel,
660
664
  allowMutations: accessMode.allowMutations,
@@ -696,6 +700,7 @@ export class DbService extends Context.Service<
696
700
 
697
701
  return yield* runWithVpnPrerequisites(
698
702
  resolvedConfig.port,
703
+ resolvedConfig,
699
704
  runQueryWithOptionalTunnel(resolvedConfig, queryEffect),
700
705
  );
701
706
  });
@@ -752,6 +757,7 @@ export class DbService extends Context.Service<
752
757
 
753
758
  const result = yield* runWithVpnPrerequisites(
754
759
  resolvedConfig.port,
760
+ resolvedConfig,
755
761
  runQueryWithOptionalTunnel(resolvedConfig, queryEffect),
756
762
  );
757
763
 
@@ -1,4 +1,5 @@
1
1
  import type { DbMutationOperation } from "#config";
2
+ import type { ProfilePrerequisites } from "#config/types";
2
3
  import type { Environment, OutputFormat } from "#shared";
3
4
 
4
5
  export type { DbMutationOperation };
@@ -6,7 +7,7 @@ export type { Environment, OutputFormat };
6
7
 
7
8
  export type SchemaMode = "tables" | "columns" | "full" | "relationships";
8
9
 
9
- export type DbConfig = {
10
+ export type DbConfig = ProfilePrerequisites & {
10
11
  host: string;
11
12
  user: string;
12
13
  database: string;
@@ -119,6 +119,7 @@ export const prCreateCommand = Command.make(
119
119
  export const prEditCommand = Command.make(
120
120
  "edit",
121
121
  {
122
+ base: Flag.string("base").pipe(Flag.withDescription("New base branch"), Flag.optional),
122
123
  body: Flag.string("body").pipe(Flag.withDescription("New PR body/description"), Flag.optional),
123
124
  bodyFile: Flag.string("body-file").pipe(
124
125
  Flag.withDescription("Read PR body from a file path or '-' for stdin"),
@@ -128,7 +129,7 @@ export const prEditCommand = Command.make(
128
129
  pr: Flag.integer("pr").pipe(Flag.withDescription("PR number to edit")),
129
130
  title: Flag.string("title").pipe(Flag.withDescription("New PR title"), Flag.optional),
130
131
  },
131
- ({ body, bodyFile, format, pr, title }) =>
132
+ ({ base, body, bodyFile, format, pr, title }) =>
132
133
  Effect.gen(function* () {
133
134
  const resolvedBody = yield* resolveOptionalTextInput(
134
135
  "gh-tool pr edit",
@@ -143,6 +144,7 @@ export const prEditCommand = Command.make(
143
144
  pr,
144
145
  title: Option.getOrNull(title),
145
146
  body: resolvedBody,
147
+ base: Option.getOrNull(base),
146
148
  });
147
149
  yield* logFormatted(info, format);
148
150
  }),
@@ -632,14 +632,15 @@ export const editPR = Effect.fn("pr.editPR")(function* (opts: {
632
632
  pr: number;
633
633
  title: string | null;
634
634
  body: string | null;
635
+ base: string | null;
635
636
  }) {
636
- if (!opts.title && !opts.body) {
637
+ if (!opts.title && !opts.body && !opts.base) {
637
638
  return yield* Effect.fail(
638
639
  new GitHubCommandError({
639
640
  command: "pr edit",
640
641
  exitCode: 1,
641
- stderr: "At least one of --title or --body must be provided",
642
- message: "At least one of --title or --body must be provided",
642
+ stderr: "At least one of --title, --body, or --base must be provided",
643
+ message: "At least one of --title, --body, or --base must be provided",
643
644
  }),
644
645
  );
645
646
  }
@@ -654,6 +655,9 @@ export const editPR = Effect.fn("pr.editPR")(function* (opts: {
654
655
  if (opts.body) {
655
656
  editArgs.push("--body", opts.body);
656
657
  }
658
+ if (opts.base) {
659
+ editArgs.push("--base", opts.base);
660
+ }
657
661
 
658
662
  yield* gh.runGh(editArgs);
659
663
 
@@ -36,3 +36,22 @@ export function resolveProfilePrerequisites(
36
36
 
37
37
  return { success: true, prerequisites };
38
38
  }
39
+
40
+ const hasOwnPrerequisiteConfig = (profile: ProfilePrerequisites, key: keyof ProfilePrerequisites) =>
41
+ Object.prototype.hasOwnProperty.call(profile, key);
42
+
43
+ export function resolveEnvironmentScopedPrerequisites(
44
+ profile: ProfilePrerequisites,
45
+ environment: ProfilePrerequisites,
46
+ ): ProfilePrerequisites {
47
+ const source =
48
+ hasOwnPrerequisiteConfig(environment, "vpn") ||
49
+ hasOwnPrerequisiteConfig(environment, "prerequisites")
50
+ ? environment
51
+ : profile;
52
+
53
+ return {
54
+ ...(source.vpn !== undefined ? { vpn: source.vpn } : {}),
55
+ ...(source.prerequisites !== undefined ? { prerequisites: source.prerequisites } : {}),
56
+ };
57
+ }