@blogic-cz/agent-tools 0.8.16 → 0.8.18
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 +14 -10
- package/src/az-tool/service.ts +2 -2
- package/src/config/loader.ts +4 -5
- package/src/db-tool/config-service.ts +4 -5
- package/src/db-tool/service.ts +2 -2
- package/src/gh-tool/pr/core.ts +107 -23
- package/src/gh-tool/service.ts +2 -2
- package/src/k8s-tool/service.ts +2 -2
- package/src/logs-tool/service.ts +2 -2
- package/src/session-tool/config.ts +2 -2
- package/src/session-tool/service.ts +2 -2
- package/src/shared/audit.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blogic-cz/agent-tools",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.18",
|
|
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",
|
|
@@ -116,22 +116,26 @@
|
|
|
116
116
|
"lint": "oxlint -c ./.oxlintrc.json --deny-warnings",
|
|
117
117
|
"lint:fix": "oxlint -c ./.oxlintrc.json --fix",
|
|
118
118
|
"session-tool": "bun src/session-tool/index.ts",
|
|
119
|
+
"update:packages": "bun update -i -r",
|
|
119
120
|
"update:skills": "bun run .agents/skills/update-packages/references/skills-update-local.ts",
|
|
120
121
|
"test": "vitest run"
|
|
121
122
|
},
|
|
122
123
|
"dependencies": {
|
|
123
|
-
"@effect/platform-bun": "4.0.0-beta.
|
|
124
|
+
"@effect/platform-bun": "4.0.0-beta.48",
|
|
124
125
|
"@toon-format/toon": "2.1.0",
|
|
125
|
-
"effect": "4.0.0-beta.
|
|
126
|
+
"effect": "4.0.0-beta.48"
|
|
126
127
|
},
|
|
127
128
|
"devDependencies": {
|
|
128
|
-
"@effect/language-service": "0.
|
|
129
|
-
"@effect/vitest": "4.0.0-beta.
|
|
130
|
-
"@types/bun": "1.3.
|
|
131
|
-
"oxfmt": "0.
|
|
132
|
-
"oxlint": "1.
|
|
133
|
-
"typescript": "
|
|
134
|
-
"vitest": "^4.
|
|
129
|
+
"@effect/language-service": "0.85.1",
|
|
130
|
+
"@effect/vitest": "4.0.0-beta.48",
|
|
131
|
+
"@types/bun": "1.3.12",
|
|
132
|
+
"oxfmt": "0.44.0",
|
|
133
|
+
"oxlint": "1.59.0",
|
|
134
|
+
"typescript": "6.0.2",
|
|
135
|
+
"vitest": "^4.1.4"
|
|
136
|
+
},
|
|
137
|
+
"overrides": {
|
|
138
|
+
"@effect/platform-node-shared": "4.0.0-beta.48"
|
|
135
139
|
},
|
|
136
140
|
"engines": {
|
|
137
141
|
"bun": ">=1.0.0"
|
package/src/az-tool/service.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
|
|
2
|
-
import { Effect, Layer,
|
|
2
|
+
import { Context, Effect, Layer, Option, Stream } from "effect";
|
|
3
3
|
|
|
4
4
|
import type { InvokeParams } from "./types";
|
|
5
5
|
import type { AzureConfig } from "#config/types";
|
|
@@ -10,7 +10,7 @@ import { isCommandAllowed, isInvokeAllowed } from "./security";
|
|
|
10
10
|
import { transformCmdOutput } from "./transformers";
|
|
11
11
|
import { ConfigService, getToolConfig } from "#config";
|
|
12
12
|
|
|
13
|
-
export class AzService extends
|
|
13
|
+
export class AzService extends Context.Service<
|
|
14
14
|
AzService,
|
|
15
15
|
{
|
|
16
16
|
readonly runCommand: (
|
package/src/config/loader.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { dirname } from "node:path";
|
|
2
2
|
|
|
3
|
-
import { Data, Effect, Layer, Schema
|
|
3
|
+
import { Context, Data, Effect, Layer, Schema } from "effect";
|
|
4
4
|
|
|
5
5
|
import type { AgentToolsConfig, GitHubRepoConfig } from "./types";
|
|
6
6
|
|
|
@@ -159,10 +159,9 @@ export async function loadConfig(): Promise<AgentToolsConfig | undefined> {
|
|
|
159
159
|
return decodeConfig(parsed, configPath);
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
export class ConfigService extends
|
|
163
|
-
ConfigService,
|
|
164
|
-
|
|
165
|
-
>()("@agent-tools/ConfigService") {}
|
|
162
|
+
export class ConfigService extends Context.Service<ConfigService, AgentToolsConfig | undefined>()(
|
|
163
|
+
"@agent-tools/ConfigService",
|
|
164
|
+
) {}
|
|
166
165
|
|
|
167
166
|
export class ConfigLoadError extends Data.TaggedError("ConfigLoadError")<{
|
|
168
167
|
readonly cause: unknown;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect, Layer
|
|
1
|
+
import { Context, Effect, Layer } from "effect";
|
|
2
2
|
|
|
3
3
|
import { ConfigService, getToolConfig } from "#config";
|
|
4
4
|
import type { DatabaseConfig } from "#config";
|
|
@@ -12,10 +12,9 @@ import type { DatabaseConfig } from "#config";
|
|
|
12
12
|
* const dbConfig = yield* DbConfigService;
|
|
13
13
|
* if (!dbConfig) { // no config }
|
|
14
14
|
*/
|
|
15
|
-
export class DbConfigService extends
|
|
16
|
-
DbConfigService,
|
|
17
|
-
|
|
18
|
-
>()("@agent-tools/DbConfigService") {}
|
|
15
|
+
export class DbConfigService extends Context.Service<DbConfigService, DatabaseConfig | undefined>()(
|
|
16
|
+
"@agent-tools/DbConfigService",
|
|
17
|
+
) {}
|
|
19
18
|
|
|
20
19
|
/**
|
|
21
20
|
* Creates a DbConfigService layer that resolves the database config
|
package/src/db-tool/service.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
|
|
2
|
-
import { Clock, Duration, Effect, Layer, Ref,
|
|
2
|
+
import { Clock, Context, Duration, Effect, Layer, Ref, Stream } from "effect";
|
|
3
3
|
|
|
4
4
|
import type { DbConfig, QueryResult, SchemaMode } from "./types";
|
|
5
5
|
|
|
@@ -33,7 +33,7 @@ export function resolveDbAccessMode(
|
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
export class DbService extends
|
|
36
|
+
export class DbService extends Context.Service<
|
|
37
37
|
DbService,
|
|
38
38
|
{
|
|
39
39
|
readonly executeQuery: (env: string, sql: string) => Effect.Effect<QueryResult, DbError>;
|
package/src/gh-tool/pr/core.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Console, Effect, Option } from "effect";
|
|
1
|
+
import { Console, Effect, Option, Result } from "effect";
|
|
2
2
|
|
|
3
3
|
import type {
|
|
4
4
|
BranchPRDetail,
|
|
@@ -20,6 +20,16 @@ import { runLocalCommand } from "./helpers";
|
|
|
20
20
|
const CHECK_JSON_FIELDS = "name,state,bucket,link";
|
|
21
21
|
const GITHUB_ACTIONS_RUN_ID_RE = /github\.com\/[^/]+\/[^/]+\/actions\/runs\/(\d+)/;
|
|
22
22
|
|
|
23
|
+
type WorkflowRunJobsForRerun = {
|
|
24
|
+
databaseId: number;
|
|
25
|
+
jobs: Array<{
|
|
26
|
+
databaseId: number;
|
|
27
|
+
name: string;
|
|
28
|
+
status: string;
|
|
29
|
+
conclusion: string | null;
|
|
30
|
+
}>;
|
|
31
|
+
};
|
|
32
|
+
|
|
23
33
|
const buildChecksCommand = (pr: number | null, includeWatch: boolean): string =>
|
|
24
34
|
`bun agent-tools-gh pr checks${pr !== null ? ` --pr ${pr}` : ""}${includeWatch ? " --watch" : ""}`;
|
|
25
35
|
|
|
@@ -36,6 +46,47 @@ const extractRunIdFromCheckLink = (link: string): number | null => {
|
|
|
36
46
|
return Number.isFinite(runId) ? runId : null;
|
|
37
47
|
};
|
|
38
48
|
|
|
49
|
+
const isFailedWorkflowJob = (job: { status: string; conclusion: string | null }) =>
|
|
50
|
+
job.conclusion === "failure" || job.status === "failure";
|
|
51
|
+
|
|
52
|
+
const getCheckJobNameCandidates = (checkName: string): string[] => {
|
|
53
|
+
const exact = checkName.trim();
|
|
54
|
+
const suffixParts = exact.split("/").map((part) => part.trim());
|
|
55
|
+
let suffix: string | undefined;
|
|
56
|
+
for (let index = suffixParts.length - 1; index >= 0; index -= 1) {
|
|
57
|
+
const part = suffixParts[index];
|
|
58
|
+
if (part !== undefined && part.length > 0) {
|
|
59
|
+
suffix = part;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return [...new Set([exact, suffix].filter((value): value is string => value !== undefined))];
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const resolveJobIdsForFailedChecks = (
|
|
68
|
+
checks: CheckResult[],
|
|
69
|
+
jobs: WorkflowRunJobsForRerun["jobs"],
|
|
70
|
+
): number[] | null => {
|
|
71
|
+
const failedJobs = jobs.filter(isFailedWorkflowJob);
|
|
72
|
+
const jobIds = new Set<number>();
|
|
73
|
+
|
|
74
|
+
for (const check of checks) {
|
|
75
|
+
const candidates = getCheckJobNameCandidates(check.name);
|
|
76
|
+
const matches = failedJobs.filter((job) =>
|
|
77
|
+
candidates.some((candidate) => job.name.toLowerCase() === candidate.toLowerCase()),
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
if (matches.length !== 1) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
jobIds.add(matches[0].databaseId);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return [...jobIds];
|
|
88
|
+
};
|
|
89
|
+
|
|
39
90
|
const fetchWorkflowRunFailureContext = Effect.fn("pr.fetchWorkflowRunFailureContext")(function* (
|
|
40
91
|
runId: number,
|
|
41
92
|
) {
|
|
@@ -610,7 +661,7 @@ export const fetchChecks = Effect.fn("pr.fetchChecks")(function* (
|
|
|
610
661
|
yield* gh.runGh(watchArgs).pipe(
|
|
611
662
|
Effect.timeoutOrElse({
|
|
612
663
|
duration: timeoutMs,
|
|
613
|
-
|
|
664
|
+
orElse: () =>
|
|
614
665
|
Effect.fail(
|
|
615
666
|
new GitHubTimeoutError({
|
|
616
667
|
message: `CI check monitoring timed out after ${timeoutSeconds}s`,
|
|
@@ -652,13 +703,18 @@ export const fetchChecksForCommand = Effect.fn("pr.fetchChecksForCommand")(funct
|
|
|
652
703
|
}
|
|
653
704
|
|
|
654
705
|
const watchedChecks = yield* fetchChecks(pr, true, failFast, timeoutSeconds).pipe(
|
|
655
|
-
Effect.
|
|
656
|
-
|
|
657
|
-
|
|
706
|
+
Effect.result,
|
|
707
|
+
Effect.flatMap((result) => {
|
|
708
|
+
if (Result.isFailure(result) && result.failure._tag !== "GitHubCommandError") {
|
|
709
|
+
return Effect.fail(result.failure);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
return Effect.succeed(result);
|
|
713
|
+
}),
|
|
658
714
|
);
|
|
659
715
|
|
|
660
|
-
if (
|
|
661
|
-
return watchedChecks;
|
|
716
|
+
if (Result.isSuccess(watchedChecks)) {
|
|
717
|
+
return watchedChecks.success;
|
|
662
718
|
}
|
|
663
719
|
|
|
664
720
|
const finalChecks = yield* fetchCheckResults(pr);
|
|
@@ -666,7 +722,7 @@ export const fetchChecksForCommand = Effect.fn("pr.fetchChecksForCommand")(funct
|
|
|
666
722
|
return yield* buildFailedChecksReport(pr, finalChecks);
|
|
667
723
|
}
|
|
668
724
|
|
|
669
|
-
return yield* Effect.fail(watchedChecks.
|
|
725
|
+
return yield* Effect.fail(watchedChecks.failure);
|
|
670
726
|
});
|
|
671
727
|
|
|
672
728
|
export const rerunChecks = Effect.fn("pr.rerunChecks")(function* (
|
|
@@ -675,21 +731,20 @@ export const rerunChecks = Effect.fn("pr.rerunChecks")(function* (
|
|
|
675
731
|
) {
|
|
676
732
|
const gh = yield* GitHubService;
|
|
677
733
|
|
|
678
|
-
const checks = yield*
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
link: string;
|
|
682
|
-
bucket: string;
|
|
683
|
-
state: string;
|
|
684
|
-
}>
|
|
685
|
-
>(["pr", "checks", ...(pr !== null ? [String(pr)] : []), "--json", "name,link,bucket,state"]);
|
|
734
|
+
const checks = yield* fetchCheckResults(pr);
|
|
735
|
+
|
|
736
|
+
const targetChecks = failedOnly ? checks.filter((check) => check.bucket === "fail") : checks;
|
|
686
737
|
|
|
687
738
|
// Extract unique GitHub Actions run IDs from links
|
|
688
739
|
const runIds = new Set<string>();
|
|
689
|
-
|
|
690
|
-
|
|
740
|
+
const checksByRun = new Map<string, CheckResult[]>();
|
|
741
|
+
for (const check of targetChecks) {
|
|
742
|
+
const match = check.link.match(GITHUB_ACTIONS_RUN_ID_RE);
|
|
691
743
|
if (match?.[1]) {
|
|
692
744
|
runIds.add(match[1]);
|
|
745
|
+
const existing = checksByRun.get(match[1]) ?? [];
|
|
746
|
+
existing.push(check);
|
|
747
|
+
checksByRun.set(match[1], existing);
|
|
693
748
|
}
|
|
694
749
|
}
|
|
695
750
|
|
|
@@ -707,11 +762,40 @@ export const rerunChecks = Effect.fn("pr.rerunChecks")(function* (
|
|
|
707
762
|
success: boolean;
|
|
708
763
|
}> = [];
|
|
709
764
|
for (const runId of runIds) {
|
|
710
|
-
const
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
765
|
+
const success = yield* Effect.gen(function* () {
|
|
766
|
+
if (!failedOnly) {
|
|
767
|
+
return yield* gh.runGh(["run", "rerun", runId]).pipe(
|
|
768
|
+
Effect.map(() => true),
|
|
769
|
+
Effect.catch(() => Effect.succeed(false)),
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
const checksForRun = checksByRun.get(runId) ?? [];
|
|
774
|
+
const run = yield* gh
|
|
775
|
+
.runGhJson<WorkflowRunJobsForRerun>(["run", "view", runId, "--json", "databaseId,jobs"])
|
|
776
|
+
.pipe(Effect.catchTag("GitHubCommandError", () => Effect.succeed(null)));
|
|
777
|
+
|
|
778
|
+
const jobIds = run === null ? null : resolveJobIdsForFailedChecks(checksForRun, run.jobs);
|
|
779
|
+
if (jobIds === null || jobIds.length === 0) {
|
|
780
|
+
return yield* gh.runGh(["run", "rerun", runId, "--failed"]).pipe(
|
|
781
|
+
Effect.map(() => true),
|
|
782
|
+
Effect.catch(() => Effect.succeed(false)),
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
const rerunResults = yield* Effect.forEach(
|
|
787
|
+
jobIds,
|
|
788
|
+
(jobId) =>
|
|
789
|
+
gh.runGh(["run", "rerun", "--job", String(jobId)]).pipe(
|
|
790
|
+
Effect.map(() => true),
|
|
791
|
+
Effect.catch(() => Effect.succeed(false)),
|
|
792
|
+
),
|
|
793
|
+
{ concurrency: 1 },
|
|
794
|
+
);
|
|
795
|
+
|
|
796
|
+
return rerunResults.every(Boolean);
|
|
797
|
+
});
|
|
798
|
+
|
|
715
799
|
results.push({ runId, success });
|
|
716
800
|
}
|
|
717
801
|
|
package/src/gh-tool/service.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
|
|
2
|
-
import { Effect, Layer,
|
|
2
|
+
import { Context, Effect, Layer, Stream } from "effect";
|
|
3
3
|
|
|
4
4
|
import type { RepoInfo } from "./types";
|
|
5
5
|
|
|
@@ -15,7 +15,7 @@ type GhResult = {
|
|
|
15
15
|
|
|
16
16
|
type GhError = GitHubCommandError | GitHubAuthError | GitHubNotFoundError;
|
|
17
17
|
|
|
18
|
-
export class GitHubService extends
|
|
18
|
+
export class GitHubService extends Context.Service<
|
|
19
19
|
GitHubService,
|
|
20
20
|
{
|
|
21
21
|
readonly runGh: (args: string[]) => Effect.Effect<GhResult, GhError>;
|
package/src/k8s-tool/service.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
|
|
2
|
-
import { Effect, Layer, Option, Ref,
|
|
2
|
+
import { Context, Effect, Layer, Option, Ref, Stream } from "effect";
|
|
3
3
|
|
|
4
4
|
import type { CommandResult, Environment } from "./types";
|
|
5
5
|
|
|
@@ -13,7 +13,7 @@ import { ConfigService, getToolConfig } from "#config";
|
|
|
13
13
|
import type { K8sConfig } from "#config";
|
|
14
14
|
import { isKubectlCommandAllowed } from "./security";
|
|
15
15
|
|
|
16
|
-
export class K8sService extends
|
|
16
|
+
export class K8sService extends Context.Service<
|
|
17
17
|
K8sService,
|
|
18
18
|
{
|
|
19
19
|
readonly runCommand: (
|
package/src/logs-tool/service.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
|
|
2
|
-
import { Effect, Layer, Result,
|
|
2
|
+
import { Context, Effect, Layer, Result, Stream } from "effect";
|
|
3
3
|
|
|
4
4
|
import type { Environment, LogFile, ReadOptions } from "./types";
|
|
5
5
|
|
|
@@ -47,7 +47,7 @@ export const sanitizeShellArg = (input: string): string => `'${input.replace(/'/
|
|
|
47
47
|
|
|
48
48
|
const readCommandOutput = (output: unknown): string => (typeof output === "string" ? output : "");
|
|
49
49
|
|
|
50
|
-
export class LogsService extends
|
|
50
|
+
export class LogsService extends Context.Service<
|
|
51
51
|
LogsService,
|
|
52
52
|
{
|
|
53
53
|
readonly listLogs: (env: Environment, profile?: string) => Effect.Effect<LogFile[], LogsError>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect, Layer
|
|
1
|
+
import { Context, Effect, Layer } from "effect";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
@@ -38,7 +38,7 @@ export const resolveSessionsPath = Effect.gen(function* () {
|
|
|
38
38
|
/**
|
|
39
39
|
* Context tag for resolved paths (cached during effect execution).
|
|
40
40
|
*/
|
|
41
|
-
export class ResolvedPaths extends
|
|
41
|
+
export class ResolvedPaths extends Context.Service<
|
|
42
42
|
ResolvedPaths,
|
|
43
43
|
{
|
|
44
44
|
readonly messagesPath: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect, Layer
|
|
1
|
+
import { Context, Effect, Layer } from "effect";
|
|
2
2
|
import { readdir } from "node:fs/promises";
|
|
3
3
|
|
|
4
4
|
import type { MessageSummary, SessionInfo } from "./types";
|
|
@@ -142,7 +142,7 @@ const readJsonFilesFlat = (dir: string): Effect.Effect<FileEntry[], SessionError
|
|
|
142
142
|
}),
|
|
143
143
|
});
|
|
144
144
|
|
|
145
|
-
export class SessionService extends
|
|
145
|
+
export class SessionService extends Context.Service<
|
|
146
146
|
SessionService,
|
|
147
147
|
{
|
|
148
148
|
readonly getSessionsForProject: (
|
package/src/shared/audit.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { mkdirSync } from "node:fs";
|
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { basename, dirname, join } from "node:path";
|
|
5
5
|
|
|
6
|
-
import { Cause, Effect, Layer
|
|
6
|
+
import { Cause, Context, Effect, Layer } from "effect";
|
|
7
7
|
|
|
8
8
|
import { loadConfig } from "#config";
|
|
9
9
|
|
|
@@ -65,7 +65,7 @@ type TableInfoRow = {
|
|
|
65
65
|
name: string;
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
-
export class AuditService extends
|
|
68
|
+
export class AuditService extends Context.Service<AuditService, AuditServiceShape>()(
|
|
69
69
|
"@agent-tools/AuditService",
|
|
70
70
|
) {}
|
|
71
71
|
|