@blogic-cz/agent-tools 0.5.5 → 0.5.7
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
|
@@ -57,9 +57,26 @@
|
|
|
57
57
|
"defaultEnvironment": {
|
|
58
58
|
"description": "Optional default environment name used by tools when no --env flag is provided.",
|
|
59
59
|
"type": "string"
|
|
60
|
+
},
|
|
61
|
+
"github": {
|
|
62
|
+
"type": "object",
|
|
63
|
+
"description": "Named GitHub repository profiles as Record<string, GitHubRepoConfig>. Use 'default' key for single-repo projects. Multiple profiles can be selected via --repo <name>.",
|
|
64
|
+
"additionalProperties": {
|
|
65
|
+
"$ref": "#/definitions/GitHubRepoConfig"
|
|
66
|
+
}
|
|
60
67
|
}
|
|
61
68
|
},
|
|
62
69
|
"definitions": {
|
|
70
|
+
"GitHubRepoConfig": {
|
|
71
|
+
"description": "A single GitHub repository target.",
|
|
72
|
+
"type": "object",
|
|
73
|
+
"additionalProperties": false,
|
|
74
|
+
"required": ["owner", "repo"],
|
|
75
|
+
"properties": {
|
|
76
|
+
"owner": { "type": "string", "description": "GitHub organization or user name." },
|
|
77
|
+
"repo": { "type": "string", "description": "Repository name." }
|
|
78
|
+
}
|
|
79
|
+
},
|
|
63
80
|
"AzureConfig": {
|
|
64
81
|
"description": "Azure DevOps profile configuration.",
|
|
65
82
|
"type": "object",
|
package/src/config/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ export type {
|
|
|
8
8
|
AuditConfig,
|
|
9
9
|
CliToolOverride,
|
|
10
10
|
CredentialGuardConfig,
|
|
11
|
+
GitHubRepoConfig,
|
|
11
12
|
} from "./types.ts";
|
|
12
13
|
|
|
13
14
|
export {
|
|
@@ -15,5 +16,6 @@ export {
|
|
|
15
16
|
ConfigServiceLayer,
|
|
16
17
|
getToolConfig,
|
|
17
18
|
getDefaultEnvironment,
|
|
19
|
+
getGitHubConfig,
|
|
18
20
|
loadConfig,
|
|
19
21
|
} from "./loader";
|
package/src/config/loader.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { dirname } from "node:path";
|
|
|
2
2
|
|
|
3
3
|
import { Data, Effect, Layer, Schema, ServiceMap } from "effect";
|
|
4
4
|
|
|
5
|
-
import type { AgentToolsConfig } from "./types.ts";
|
|
5
|
+
import type { AgentToolsConfig, GitHubRepoConfig } from "./types.ts";
|
|
6
6
|
|
|
7
7
|
const CliToolOverrideSchema = Schema.Struct({
|
|
8
8
|
tool: Schema.String,
|
|
@@ -59,6 +59,11 @@ const AuditConfigSchema = Schema.Struct({
|
|
|
59
59
|
dbPath: Schema.optionalKey(Schema.String),
|
|
60
60
|
});
|
|
61
61
|
|
|
62
|
+
const GitHubRepoConfigSchema = Schema.Struct({
|
|
63
|
+
owner: Schema.String,
|
|
64
|
+
repo: Schema.String,
|
|
65
|
+
});
|
|
66
|
+
|
|
62
67
|
const AgentToolsConfigSchema = Schema.Struct({
|
|
63
68
|
$schema: Schema.optionalKey(Schema.String),
|
|
64
69
|
azure: Schema.optionalKey(Schema.Record(Schema.String, AzureConfigSchema)),
|
|
@@ -73,6 +78,7 @@ const AgentToolsConfigSchema = Schema.Struct({
|
|
|
73
78
|
audit: Schema.optionalKey(AuditConfigSchema),
|
|
74
79
|
credentialGuard: Schema.optionalKey(CredentialGuardConfigSchema),
|
|
75
80
|
defaultEnvironment: Schema.optionalKey(Schema.String),
|
|
81
|
+
github: Schema.optionalKey(Schema.Record(Schema.String, GitHubRepoConfigSchema)),
|
|
76
82
|
});
|
|
77
83
|
|
|
78
84
|
async function findConfigFile(startDirectory: string = process.cwd()): Promise<string | undefined> {
|
|
@@ -183,3 +189,22 @@ export function getToolConfig<T>(
|
|
|
183
189
|
export function getDefaultEnvironment(config: AgentToolsConfig | undefined): string | undefined {
|
|
184
190
|
return config?.defaultEnvironment;
|
|
185
191
|
}
|
|
192
|
+
|
|
193
|
+
export function getGitHubConfig(
|
|
194
|
+
config: AgentToolsConfig | undefined,
|
|
195
|
+
profile?: string,
|
|
196
|
+
): GitHubRepoConfig | undefined {
|
|
197
|
+
const repos = config?.github;
|
|
198
|
+
if (!repos) return undefined;
|
|
199
|
+
|
|
200
|
+
const keys = Object.keys(repos);
|
|
201
|
+
if (keys.length === 0) return undefined;
|
|
202
|
+
|
|
203
|
+
if (profile) return repos[profile];
|
|
204
|
+
if (keys.length === 1) return repos[keys[0] ?? ""];
|
|
205
|
+
if ("default" in repos) return repos.default;
|
|
206
|
+
|
|
207
|
+
throw new Error(
|
|
208
|
+
`Multiple github profiles found: [${keys.join(", ")}]. Use --repo <name> to select one.`,
|
|
209
|
+
);
|
|
210
|
+
}
|
package/src/config/types.ts
CHANGED
|
@@ -61,6 +61,12 @@ export type AuditConfig = {
|
|
|
61
61
|
dbPath?: string;
|
|
62
62
|
};
|
|
63
63
|
|
|
64
|
+
/** Single GitHub repository configuration */
|
|
65
|
+
export type GitHubRepoConfig = {
|
|
66
|
+
owner: string;
|
|
67
|
+
repo: string;
|
|
68
|
+
};
|
|
69
|
+
|
|
64
70
|
/**
|
|
65
71
|
* Root agent-tools configuration.
|
|
66
72
|
*
|
|
@@ -89,4 +95,6 @@ export type AgentToolsConfig = {
|
|
|
89
95
|
credentialGuard?: CredentialGuardConfig;
|
|
90
96
|
/** Optional default environment name (local|test|prod) used by tools when no --env flag is provided */
|
|
91
97
|
defaultEnvironment?: string;
|
|
98
|
+
/** Named GitHub repository profiles. e.g. { default: { owner: "...", repo: "..." } } */
|
|
99
|
+
github?: Record<string, GitHubRepoConfig>;
|
|
92
100
|
};
|
|
@@ -45,6 +45,7 @@ export type CredentialGuard = {
|
|
|
45
45
|
isDangerousBashCommand: (command: string) => boolean;
|
|
46
46
|
getBlockedCliTool: (command: string) => { name: string; wrapper: string } | null;
|
|
47
47
|
isGhCommandAllowed: (command: string) => boolean;
|
|
48
|
+
detectSleepPolling: (command: string) => string | null;
|
|
48
49
|
};
|
|
49
50
|
|
|
50
51
|
// ============================================================================
|
|
@@ -184,6 +185,34 @@ const DEFAULT_BLOCKED_CLI_TOOLS: BlockedCliTool[] = [
|
|
|
184
185
|
},
|
|
185
186
|
];
|
|
186
187
|
|
|
188
|
+
type PollingDetectionRule = {
|
|
189
|
+
pattern: RegExp;
|
|
190
|
+
suggestion: string;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const DEFAULT_POLLING_DETECTION_RULES: PollingDetectionRule[] = [
|
|
194
|
+
{
|
|
195
|
+
pattern: /workflow\s+(?:list|view|jobs|logs|job-logs)\b/,
|
|
196
|
+
suggestion: "bun agent-tools-gh workflow watch --run <ID>",
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
pattern: /pr\s+checks(?![\w-])(?!.*--watch)/,
|
|
200
|
+
suggestion: "bun agent-tools-gh pr checks --pr <N> --watch",
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
pattern: /pr\s+rerun-checks\b/,
|
|
204
|
+
suggestion: "bun agent-tools-gh pr checks --pr <N> --watch (after rerun completes)",
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
pattern: /kubectl\b/,
|
|
208
|
+
suggestion: 'bun agent-tools-k8s kubectl --env <env> --cmd "wait --for=condition=..."',
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
pattern: /\bpipelines?\s+runs?\b/,
|
|
212
|
+
suggestion: "bun agent-tools-az build summary --build-id <ID>",
|
|
213
|
+
},
|
|
214
|
+
];
|
|
215
|
+
|
|
187
216
|
/**
|
|
188
217
|
* Read-only gh subcommands safe on external repos with -R flag.
|
|
189
218
|
*/
|
|
@@ -325,6 +354,17 @@ export function createCredentialGuard(config?: CredentialGuardConfig): Credentia
|
|
|
325
354
|
return ghSegments.every((segment) => isGhCommandAllowed(segment.trim()));
|
|
326
355
|
}
|
|
327
356
|
|
|
357
|
+
function detectSleepPolling(command: string): string | null {
|
|
358
|
+
if (!/\bsleep\s+\d+/.test(command)) return null;
|
|
359
|
+
|
|
360
|
+
for (const { pattern, suggestion } of DEFAULT_POLLING_DETECTION_RULES) {
|
|
361
|
+
if (pattern.test(command)) {
|
|
362
|
+
return suggestion;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
|
|
328
368
|
function getBlockedCliTool(command: string): { name: string; wrapper: string } | null {
|
|
329
369
|
for (const { pattern, name, wrapper } of blockedCliTools) {
|
|
330
370
|
if (pattern.test(command)) {
|
|
@@ -390,6 +430,17 @@ export function createCredentialGuard(config?: CredentialGuardConfig): Credentia
|
|
|
390
430
|
);
|
|
391
431
|
}
|
|
392
432
|
|
|
433
|
+
const sleepSuggestion = detectSleepPolling(command);
|
|
434
|
+
if (sleepSuggestion) {
|
|
435
|
+
throw new Error(
|
|
436
|
+
`\u{26A0}\u{FE0F} Sleep-polling detected.\n\n` +
|
|
437
|
+
`Instead of polling with sleep, use the built-in watch command:\n\n` +
|
|
438
|
+
`Use instead: ${sleepSuggestion}\n\n` +
|
|
439
|
+
`Watch commands block until completion — no polling needed.\n\n` +
|
|
440
|
+
`→ Skill "agent-tools"`,
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
|
|
393
444
|
const blockedTool = getBlockedCliTool(command);
|
|
394
445
|
if (blockedTool) {
|
|
395
446
|
throw new Error(
|
|
@@ -412,6 +463,7 @@ export function createCredentialGuard(config?: CredentialGuardConfig): Credentia
|
|
|
412
463
|
isDangerousBashCommand,
|
|
413
464
|
getBlockedCliTool,
|
|
414
465
|
isGhCommandAllowed,
|
|
466
|
+
detectSleepPolling,
|
|
415
467
|
};
|
|
416
468
|
}
|
|
417
469
|
|
|
@@ -441,3 +493,6 @@ export const getBlockedCliTool = defaultGuard.getBlockedCliTool;
|
|
|
441
493
|
|
|
442
494
|
/** Check if a gh command is allowed (default guard). */
|
|
443
495
|
export const isGhCommandAllowed = defaultGuard.isGhCommandAllowed;
|
|
496
|
+
|
|
497
|
+
/** Detect sleep-polling with agent-tools wrapper commands (default guard). */
|
|
498
|
+
export const detectSleepPolling = defaultGuard.detectSleepPolling;
|
package/src/gh-tool/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { Effect, Layer } from "effect";
|
|
|
5
5
|
|
|
6
6
|
import { renderCauseToStderr, VERSION } from "#shared";
|
|
7
7
|
import { AuditServiceLayer, withAudit } from "#shared/audit";
|
|
8
|
+
import { ConfigServiceLayer } from "#config";
|
|
8
9
|
import {
|
|
9
10
|
issueCloseCommand,
|
|
10
11
|
issueCommentCommand,
|
|
@@ -166,6 +167,7 @@ const cli = Command.run(mainCommand, {
|
|
|
166
167
|
const MainLayer = GitHubService.layer.pipe(
|
|
167
168
|
Layer.provideMerge(BunServices.layer),
|
|
168
169
|
Layer.provideMerge(AuditServiceLayer),
|
|
170
|
+
Layer.provideMerge(ConfigServiceLayer),
|
|
169
171
|
);
|
|
170
172
|
|
|
171
173
|
const program = withAudit("gh", cli).pipe(
|
package/src/gh-tool/service.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { RepoInfo } from "./types";
|
|
|
5
5
|
|
|
6
6
|
import { GH_BINARY } from "./config";
|
|
7
7
|
import { GitHubAuthError, GitHubCommandError, GitHubNotFoundError } from "./errors";
|
|
8
|
+
import { ConfigService, getGitHubConfig } from "#config";
|
|
8
9
|
|
|
9
10
|
type GhResult = {
|
|
10
11
|
stdout: string;
|
|
@@ -31,6 +32,9 @@ export class GitHubService extends ServiceMap.Service<
|
|
|
31
32
|
Effect.scoped(
|
|
32
33
|
Effect.gen(function* () {
|
|
33
34
|
const executor = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
35
|
+
const config = yield* ConfigService;
|
|
36
|
+
const ghRepoConfig = getGitHubConfig(config);
|
|
37
|
+
const ghRepo = ghRepoConfig ? `${ghRepoConfig.owner}/${ghRepoConfig.repo}` : undefined;
|
|
34
38
|
|
|
35
39
|
let cachedRepoInfo: RepoInfo | null = null;
|
|
36
40
|
|
|
@@ -40,6 +44,7 @@ export class GitHubService extends ServiceMap.Service<
|
|
|
40
44
|
const command = ChildProcess.make(GH_BINARY, args, {
|
|
41
45
|
stdout: "pipe",
|
|
42
46
|
stderr: "pipe",
|
|
47
|
+
...(ghRepo ? { env: { GH_REPO: ghRepo }, extendEnv: true } : {}),
|
|
43
48
|
});
|
|
44
49
|
|
|
45
50
|
const proc = yield* executor.spawn(command);
|