@blogic-cz/agent-tools 0.2.0 → 0.2.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blogic-cz/agent-tools",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "CLI tools for AI coding agent workflows — GitHub, database, Kubernetes, Azure DevOps, logs, and sessions",
5
5
  "keywords": [
6
6
  "agent",
@@ -33,6 +33,9 @@
33
33
  "LICENSE"
34
34
  ],
35
35
  "type": "module",
36
+ "imports": {
37
+ "#src/*": "./src/*"
38
+ },
36
39
  "exports": {
37
40
  ".": "./src/index.ts",
38
41
  "./credential-guard": "./src/credential-guard/index.ts",
@@ -3,7 +3,7 @@ import { Command, Flag } from "effect/unstable/cli";
3
3
  import { BunRuntime, BunServices } from "@effect/platform-bun";
4
4
  import { Console, Effect, Layer, Option } from "effect";
5
5
 
6
- import { formatAny, formatOption, formatOutput, renderCauseToStderr, VERSION } from "../shared";
6
+ import { formatAny, formatOption, formatOutput, renderCauseToStderr, VERSION } from "#src/shared";
7
7
  import {
8
8
  findFailedJobs,
9
9
  getBuildJobSummary,
@@ -12,7 +12,7 @@ import {
12
12
  getBuildTimeline,
13
13
  } from "./build";
14
14
  import { AzService, AzServiceLayer } from "./service";
15
- import { ConfigServiceLayer } from "../config";
15
+ import { ConfigServiceLayer } from "#src/config";
16
16
 
17
17
  // ---------------------------------------------------------------------------
18
18
  // Common flags shared across build subcommands
@@ -2,12 +2,12 @@ import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
2
2
  import { Effect, Layer, ServiceMap, Stream, Option } from "effect";
3
3
 
4
4
  import type { InvokeParams } from "./types";
5
- import type { AzureConfig } from "../config/types";
5
+ import type { AzureConfig } from "#src/config/types";
6
6
 
7
7
  import { DIRECT_AZ_COMMANDS, STANDALONE_AZ_COMMANDS } from "./config";
8
8
  import { AzSecurityError, AzCommandError, AzTimeoutError, AzParseError } from "./errors";
9
9
  import { isCommandAllowed, isInvokeAllowed } from "./security";
10
- import { ConfigService, getToolConfig } from "../config";
10
+ import { ConfigService, getToolConfig } from "#src/config";
11
11
 
12
12
  export class AzService extends ServiceMap.Service<
13
13
  AzService,
@@ -73,11 +73,13 @@ async function findConfigFile(startDirectory: string = process.cwd()): Promise<s
73
73
 
74
74
  while (true) {
75
75
  const json5Path = `${currentDirectory}/agent-tools.json5`;
76
+ // eslint-disable-next-line eslint/no-await-in-loop -- sequential directory walk, each iteration may short-circuit
76
77
  if (await Bun.file(json5Path).exists()) {
77
78
  return json5Path;
78
79
  }
79
80
 
80
81
  const jsonPath = `${currentDirectory}/agent-tools.json`;
82
+ // eslint-disable-next-line eslint/no-await-in-loop -- sequential directory walk, each iteration may short-circuit
81
83
  if (await Bun.file(jsonPath).exists()) {
82
84
  return jsonPath;
83
85
  }
@@ -107,6 +109,7 @@ export async function loadConfig(): Promise<AgentToolsConfig | undefined> {
107
109
  `Invalid agent-tools config at ${configPath}: ${
108
110
  error instanceof Error ? error.message : String(error)
109
111
  }`,
112
+ { cause: error },
110
113
  );
111
114
  }
112
115
  }
@@ -14,7 +14,7 @@
14
14
  * at infrastructure level (K8s RBAC, file permissions, etc.)
15
15
  */
16
16
 
17
- import type { CliToolOverride, CredentialGuardConfig } from "../config/types.ts";
17
+ import type { CliToolOverride, CredentialGuardConfig } from "#src/config/types.ts";
18
18
 
19
19
  // ============================================================================
20
20
  // TYPES
@@ -108,6 +108,7 @@ const SECRET_PATTERNS = [
108
108
  /(?:secret|token|password|passwd|pwd)[" \t:=]+["']?(?!\$\{|process\.env|z\.|generate|create|read|get|fetch|import|export|const|function|return|Schema)[^\s"']{32,}["']?/i,
109
109
  },
110
110
  {
111
+ // eslint-disable-next-line eslint/no-useless-concat -- intentionally split to avoid credential guard self-detection
111
112
  name: "Priv" + "ate Key",
112
113
  pattern: new RegExp("-----BEGIN.*PRIVATE KEY-----"),
113
114
  },
@@ -1,7 +1,7 @@
1
1
  import { Effect, Layer, ServiceMap } from "effect";
2
2
 
3
- import { ConfigService, getToolConfig } from "../config";
4
- import type { DatabaseConfig } from "../config";
3
+ import { ConfigService, getToolConfig } from "#src/config";
4
+ import type { DatabaseConfig } from "#src/config";
5
5
 
6
6
  /**
7
7
  * DbConfigService wraps the resolved DatabaseConfig for the selected profile.
@@ -5,8 +5,8 @@ import { Console, Effect, Layer, Option } from "effect";
5
5
 
6
6
  import type { SchemaMode } from "./types";
7
7
 
8
- import { formatOption, formatOutput, renderCauseToStderr, VERSION } from "../shared";
9
- import { ConfigService, ConfigServiceLayer, getDefaultEnvironment } from "../config";
8
+ import { formatOption, formatOutput, renderCauseToStderr, VERSION } from "#src/shared";
9
+ import { ConfigService, ConfigServiceLayer, getDefaultEnvironment } from "#src/config";
10
10
  import { makeDbConfigLayer } from "./config-service";
11
11
  import { DbConnectionError } from "./errors";
12
12
  import { DbService } from "./service";
@@ -1,4 +1,4 @@
1
- import type { Environment, OutputFormat } from "../shared";
1
+ import type { Environment, OutputFormat } from "#src/shared";
2
2
  export type { Environment, OutputFormat };
3
3
 
4
4
  export type SchemaMode = "tables" | "columns" | "full" | "relationships";
@@ -3,7 +3,7 @@ import { Command } from "effect/unstable/cli";
3
3
  import { BunRuntime, BunServices } from "@effect/platform-bun";
4
4
  import { Effect, Layer } from "effect";
5
5
 
6
- import { renderCauseToStderr, VERSION } from "../shared";
6
+ import { renderCauseToStderr, VERSION } from "#src/shared";
7
7
  import {
8
8
  issueListCommand,
9
9
  issueViewCommand,
@@ -1,7 +1,7 @@
1
1
  import { Command, Flag } from "effect/unstable/cli";
2
2
  import { Effect, Option } from "effect";
3
3
 
4
- import { formatOption, logFormatted } from "../shared";
4
+ import { formatOption, logFormatted } from "#src/shared";
5
5
  import { GitHubCommandError } from "./errors";
6
6
  import { GitHubService } from "./service";
7
7
 
@@ -1,15 +1,15 @@
1
1
  import { Command, Flag } from "effect/unstable/cli";
2
2
  import { Effect, Option } from "effect";
3
3
 
4
- import type { PRStatusResult } from "../types";
4
+ import type { PRStatusResult } from "#src/gh-tool/types";
5
5
 
6
- import { formatOption, logFormatted } from "../../shared";
6
+ import { formatOption, logFormatted } from "#src/shared";
7
7
  import {
8
8
  CI_CHECK_WATCH_TIMEOUT_MS,
9
9
  DEFAULT_DELETE_BRANCH,
10
10
  DEFAULT_MERGE_STRATEGY,
11
11
  MERGE_STRATEGIES,
12
- } from "../config";
12
+ } from "#src/gh-tool/config";
13
13
 
14
14
  import {
15
15
  createPR,
@@ -1,9 +1,15 @@
1
1
  import { Console, Effect, Option } from "effect";
2
2
 
3
- import type { BranchPRDetail, CheckResult, MergeResult, MergeStrategy, PRInfo } from "../types";
3
+ import type {
4
+ BranchPRDetail,
5
+ CheckResult,
6
+ MergeResult,
7
+ MergeStrategy,
8
+ PRInfo,
9
+ } from "#src/gh-tool/types";
4
10
 
5
- import { GitHubCommandError, GitHubMergeError, GitHubTimeoutError } from "../errors";
6
- import { GitHubService } from "../service";
11
+ import { GitHubCommandError, GitHubMergeError, GitHubTimeoutError } from "#src/gh-tool/errors";
12
+ import { GitHubService } from "#src/gh-tool/service";
7
13
 
8
14
  import type { ButStatusJson, PRViewJsonResult } from "./helpers";
9
15
  import { runLocalCommand } from "./helpers";
@@ -155,7 +161,7 @@ export const detectPRStatus = Effect.fn("pr.detectPRStatus")(function* () {
155
161
  { concurrency: "unbounded" },
156
162
  );
157
163
 
158
- const foundPrs = branchResults.filter((r) => r.openPr !== null).map((r) => r.openPr!);
164
+ const foundPrs = branchResults.flatMap((r) => (r.openPr === null ? [] : [r.openPr]));
159
165
 
160
166
  if (foundPrs.length === 0) {
161
167
  const branchDetails: BranchPRDetail[] = branchResults.map((r) => ({
@@ -172,7 +178,7 @@ export const detectPRStatus = Effect.fn("pr.detectPRStatus")(function* () {
172
178
  if (foundPrs.length === 1) {
173
179
  return {
174
180
  mode: "single" as const,
175
- pr: foundPrs[0]!,
181
+ pr: foundPrs[0] as PRInfo,
176
182
  };
177
183
  }
178
184
 
@@ -205,7 +211,11 @@ export const createPR = Effect.fn("pr.createPR")(function* (opts: {
205
211
  "--limit",
206
212
  "1",
207
213
  ])
208
- .pipe(Effect.map((prs) => (prs.length > 0 ? Option.some(prs[0]!) : Option.none())))
214
+ .pipe(
215
+ Effect.map((prs) =>
216
+ prs.length > 0 ? Option.some(prs[0] as PRInfo) : Option.none<PRInfo>(),
217
+ ),
218
+ )
209
219
  : gh
210
220
  .runGhJson<{ number: number; url: string }>(["pr", "view", "--json", "number,url"])
211
221
  .pipe(Effect.option);
@@ -258,7 +268,7 @@ export const createPR = Effect.fn("pr.createPR")(function* (opts: {
258
268
  "1",
259
269
  ]);
260
270
  if (prs.length > 0) {
261
- return prs[0]!;
271
+ return prs[0] as PRInfo;
262
272
  }
263
273
 
264
274
  return yield* Effect.fail(
@@ -1,7 +1,7 @@
1
1
  import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
2
2
  import { Effect, Stream } from "effect";
3
3
 
4
- import { GitHubCommandError } from "../errors";
4
+ import { GitHubCommandError } from "#src/gh-tool/errors";
5
5
 
6
6
  export type LocalCommandResult = {
7
7
  stdout: string;
@@ -7,10 +7,10 @@ import type {
7
7
  IsoTimestamp,
8
8
  ReviewComment,
9
9
  ReviewThread,
10
- } from "../types";
10
+ } from "#src/gh-tool/types";
11
11
 
12
- import { GitHubCommandError } from "../errors";
13
- import { GitHubService } from "../service";
12
+ import { GitHubCommandError } from "#src/gh-tool/errors";
13
+ import { GitHubService } from "#src/gh-tool/service";
14
14
 
15
15
  import { viewPR } from "./core";
16
16
 
@@ -190,9 +190,12 @@ export const fetchThreads = Effect.fn("pr.fetchThreads")(function* (
190
190
  const threads = response.repository.pullRequest.reviewThreads.nodes;
191
191
 
192
192
  const mapped: ReviewThread[] = threads
193
- .filter((node) => node.comments.nodes.length > 0)
194
193
  .map((node) => {
195
- const comment = node.comments.nodes[0]!;
194
+ const comment = node.comments.nodes[0];
195
+ if (!comment) {
196
+ return null;
197
+ }
198
+
196
199
  return {
197
200
  threadId: node.id,
198
201
  commentId: comment.databaseId,
@@ -201,7 +204,8 @@ export const fetchThreads = Effect.fn("pr.fetchThreads")(function* (
201
204
  body: comment.body,
202
205
  isResolved: node.isResolved,
203
206
  };
204
- });
207
+ })
208
+ .filter((thread): thread is ReviewThread => thread !== null);
205
209
 
206
210
  return unresolvedOnly ? mapped.filter((t) => !t.isResolved) : mapped;
207
211
  });
@@ -1,7 +1,7 @@
1
1
  import { Command, Flag } from "effect/unstable/cli";
2
2
  import { Effect, Option } from "effect";
3
3
 
4
- import { formatOption, logFormatted } from "../shared";
4
+ import { formatOption, logFormatted } from "#src/shared";
5
5
  import { GitHubCommandError } from "./errors";
6
6
  import { GitHubService } from "./service";
7
7
 
@@ -1,7 +1,7 @@
1
1
  import { Command, Flag } from "effect/unstable/cli";
2
2
  import { Console, Effect, Option } from "effect";
3
3
 
4
- import { formatOption, logFormatted } from "../shared";
4
+ import { formatOption, logFormatted } from "#src/shared";
5
5
  import { GitHubCommandError, GitHubNotFoundError } from "./errors";
6
6
  import { GitHubService } from "./service";
7
7
 
@@ -5,10 +5,15 @@ import { Console, Effect, Layer, Option } from "effect";
5
5
 
6
6
  import type { CommandResult } from "./types";
7
7
 
8
- import { formatOption, formatOutput, renderCauseToStderr, VERSION } from "../shared";
8
+ import { formatOption, formatOutput, renderCauseToStderr, VERSION } from "#src/shared";
9
9
  import { K8sService, K8sServiceLayer } from "./service";
10
- import { ConfigService, ConfigServiceLayer, getDefaultEnvironment, getToolConfig } from "../config";
11
- import type { K8sConfig } from "../config";
10
+ import {
11
+ ConfigService,
12
+ ConfigServiceLayer,
13
+ getDefaultEnvironment,
14
+ getToolConfig,
15
+ } from "#src/config";
16
+ import type { K8sConfig } from "#src/config";
12
17
  import { K8sContextError } from "./errors";
13
18
 
14
19
  /**
@@ -78,17 +83,17 @@ const runK8sCommand = (command: string, options: CommonK8sCommandOptions) =>
78
83
  const result = yield* k8sService.runKubectl(command, options.dryRun).pipe(
79
84
  Effect.catchTags({
80
85
  K8sContextError: (error) => {
81
- const result: CommandResult = {
86
+ const errorResult: CommandResult = {
82
87
  success: false,
83
88
  error: error.message,
84
89
  hint: `Verify cluster ID "${k8sConfig.clusterId}" matches a context in kubectl config. Run: kubectl config get-contexts`,
85
90
  nextCommand: "kubectl config get-contexts",
86
91
  executionTimeMs: 0,
87
92
  };
88
- return Effect.succeed(result);
93
+ return Effect.succeed(errorResult);
89
94
  },
90
95
  K8sCommandError: (error) => {
91
- const result: CommandResult = {
96
+ const errorResult: CommandResult = {
92
97
  success: false,
93
98
  error: error.message,
94
99
  command: error.command,
@@ -96,10 +101,10 @@ const runK8sCommand = (command: string, options: CommonK8sCommandOptions) =>
96
101
  error.hint ?? "Check command syntax and ensure the target namespace/resource exists.",
97
102
  executionTimeMs: 0,
98
103
  };
99
- return Effect.succeed(result);
104
+ return Effect.succeed(errorResult);
100
105
  },
101
106
  K8sTimeoutError: (error) => {
102
- const result: CommandResult = {
107
+ const errorResult: CommandResult = {
103
108
  success: false,
104
109
  error: error.message,
105
110
  command: error.command,
@@ -108,7 +113,7 @@ const runK8sCommand = (command: string, options: CommonK8sCommandOptions) =>
108
113
  `Command timed out after ${error.timeoutMs}ms. Consider increasing timeoutMs in config or narrowing the query.`,
109
114
  executionTimeMs: error.timeoutMs,
110
115
  };
111
- return Effect.succeed(result);
116
+ return Effect.succeed(errorResult);
112
117
  },
113
118
  }),
114
119
  );
@@ -4,8 +4,8 @@ import { Effect, Layer, Option, Ref, ServiceMap, Stream } from "effect";
4
4
  import type { CommandResult, Environment } from "./types";
5
5
 
6
6
  import { K8sCommandError, K8sContextError, K8sTimeoutError } from "./errors";
7
- import { ConfigService, getToolConfig } from "../config";
8
- import type { K8sConfig } from "../config";
7
+ import { ConfigService, getToolConfig } from "#src/config";
8
+ import type { K8sConfig } from "#src/config";
9
9
 
10
10
  export class K8sService extends ServiceMap.Service<
11
11
  K8sService,
@@ -20,8 +20,8 @@ import { Console, Effect, Layer, Option, Result } from "effect";
20
20
 
21
21
  import type { Environment, LogResult, ReadOptions } from "./types";
22
22
 
23
- import { formatOption, formatOutput, renderCauseToStderr, VERSION } from "../shared";
24
- import { ConfigService, ConfigServiceLayer, getDefaultEnvironment } from "../config";
23
+ import { formatOption, formatOutput, renderCauseToStderr, VERSION } from "#src/shared";
24
+ import { ConfigService, ConfigServiceLayer, getDefaultEnvironment } from "#src/config";
25
25
  import { LogsConfigError, LogsNotFoundError, LogsReadError, LogsTimeoutError } from "./errors";
26
26
  import { LogsService, LogsServiceLayer } from "./service";
27
27
 
@@ -3,10 +3,10 @@ import { Effect, Layer, Result, ServiceMap, Stream } from "effect";
3
3
 
4
4
  import type { Environment, LogFile, ReadOptions } from "./types";
5
5
 
6
- import { K8sCommandError } from "../k8s-tool/errors";
7
- import { K8sService, K8sServiceLayer } from "../k8s-tool/service";
8
- import { ConfigService, ConfigServiceLayer, getToolConfig } from "../config/loader";
9
- import type { LogsConfig } from "../config/types";
6
+ import { K8sCommandError } from "#src/k8s-tool/errors";
7
+ import { K8sService, K8sServiceLayer } from "#src/k8s-tool/service";
8
+ import { ConfigService, ConfigServiceLayer, getToolConfig } from "#src/config/loader";
9
+ import type { LogsConfig } from "#src/config/types";
10
10
  import { LogsNotFoundError, LogsReadError, type LogsError } from "./errors";
11
11
 
12
12
  export const parseLogFiles = (output: string): LogFile[] => {
@@ -1,4 +1,4 @@
1
- import type { Environment, OutputFormat } from "../shared";
1
+ import type { Environment, OutputFormat } from "#src/shared";
2
2
  export type { Environment, OutputFormat };
3
3
 
4
4
  export type LogFile = {
@@ -2,7 +2,7 @@ import { Effect, Layer, ServiceMap } from "effect";
2
2
  import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
4
 
5
- import { loadConfig } from "../config/loader";
5
+ import { loadConfig } from "#src/config/loader";
6
6
 
7
7
  /**
8
8
  * Resolves the OpenCode storage base path from config or default.
@@ -13,7 +13,7 @@ import { Console, Effect, Layer, Result } from "effect";
13
13
 
14
14
  import type { MessageSummary, SessionResult } from "./types";
15
15
 
16
- import { formatOption, formatOutput, VERSION } from "../shared";
16
+ import { formatOption, formatOutput, VERSION } from "#src/shared";
17
17
  import { ResolvedPaths, ResolvedPathsLayer } from "./config";
18
18
  import { SessionStorageNotFoundError } from "./errors";
19
19
  import { formatDate, SessionService, SessionServiceLayer, truncate } from "./service";
@@ -51,8 +51,10 @@ const readJsonFilesInTree = (parentDir: string): Effect.Effect<FileEntry[], Sess
51
51
  const subPath = `${parentDir}/${subDir}`;
52
52
  let files: string[];
53
53
  try {
54
+ // eslint-disable-next-line eslint/no-await-in-loop -- sequential directory walk, each iteration may short-circuit
54
55
  files = await readdir(subPath);
55
56
  } catch {
57
+ /* ignore unreadable files */
56
58
  continue;
57
59
  }
58
60
 
@@ -63,8 +65,11 @@ const readJsonFilesInTree = (parentDir: string): Effect.Effect<FileEntry[], Sess
63
65
  try {
64
66
  const content = await Bun.file(filePath).text();
65
67
  results.push({ filePath, content });
66
- } catch {}
68
+ } catch {
69
+ /* ignore unreadable files */
70
+ }
67
71
  });
72
+ // eslint-disable-next-line eslint/no-await-in-loop -- sequential directory walk, each iteration may short-circuit
68
73
  await Promise.all(reads);
69
74
  }
70
75
 
@@ -90,7 +95,9 @@ const readJsonFilesFlat = (dir: string): Effect.Effect<FileEntry[], SessionError
90
95
  try {
91
96
  const content = await Bun.file(filePath).text();
92
97
  results.push({ filePath, content });
93
- } catch {}
98
+ } catch {
99
+ /* ignore unreadable files */
100
+ }
94
101
  });
95
102
  await Promise.all(reads);
96
103
 
@@ -191,7 +198,13 @@ export class SessionService extends ServiceMap.Service<
191
198
  }
192
199
  }
193
200
 
194
- return summaries.sort((left, right) => right.created - left.created);
201
+ return (
202
+ summaries as MessageSummary[] & {
203
+ toSorted(
204
+ compareFn: (left: MessageSummary, right: MessageSummary) => number,
205
+ ): MessageSummary[];
206
+ }
207
+ ).toSorted((left, right) => right.created - left.created);
195
208
  }),
196
209
 
197
210
  searchSummaries: (summaries: MessageSummary[], query: string): MessageSummary[] => {
@@ -1,4 +1,4 @@
1
- import type { OutputFormat } from "../shared";
1
+ import type { OutputFormat } from "#src/shared";
2
2
 
3
3
  export type { OutputFormat };
4
4
 
package/src/shared/bun.ts CHANGED
@@ -51,7 +51,7 @@ export async function runCommand(
51
51
  };
52
52
  }
53
53
 
54
- export async function runShellCommand(
54
+ export function runShellCommand(
55
55
  command: string,
56
56
  options: CommandOptions = {},
57
57
  ): Promise<CommandResult> {
@@ -8,6 +8,7 @@ export { commonArgOptions, parseCommonArgs } from "./cli";
8
8
 
9
9
  export { renderCauseToStderr } from "./error-renderer";
10
10
 
11
+ // eslint-disable-next-line import/no-relative-parent-imports -- package.json lives at project root, outside src/
11
12
  import pkg from "../../package.json" with { type: "json" };
12
13
  export const VERSION = pkg.version;
13
14