@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blogic-cz/agent-tools",
3
- "version": "0.5.5",
3
+ "version": "0.5.7",
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",
@@ -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",
@@ -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";
@@ -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
+ }
@@ -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;
@@ -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(
@@ -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);