@blogic-cz/agent-tools 0.1.0 → 0.2.0
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 +68 -30
- package/package.json +4 -4
- package/schemas/agent-tools.schema.json +4 -0
- package/src/az-tool/build.ts +5 -0
- package/src/az-tool/errors.ts +12 -0
- package/src/az-tool/index.ts +127 -103
- package/src/az-tool/service.ts +11 -2
- package/src/config/index.ts +7 -1
- package/src/config/loader.ts +5 -0
- package/src/config/types.ts +2 -0
- package/src/db-tool/errors.ts +15 -0
- package/src/db-tool/index.ts +46 -7
- package/src/gh-tool/errors.ts +15 -0
- package/src/gh-tool/index.ts +4 -0
- package/src/gh-tool/pr/commands.ts +55 -0
- package/src/gh-tool/pr/core.ts +11 -0
- package/src/gh-tool/pr/index.ts +2 -0
- package/src/gh-tool/service.ts +5 -0
- package/src/gh-tool/workflow.ts +4 -0
- package/src/k8s-tool/errors.ts +9 -0
- package/src/k8s-tool/index.ts +311 -64
- package/src/k8s-tool/types.ts +4 -0
- package/src/logs-tool/errors.ts +12 -0
- package/src/logs-tool/index.ts +72 -10
- package/src/logs-tool/types.ts +3 -0
- package/src/shared/error-renderer.ts +21 -11
- package/src/shared/types.ts +3 -0
package/README.md
CHANGED
|
@@ -15,6 +15,14 @@ These tools wrap each CLI with:
|
|
|
15
15
|
|
|
16
16
|
## Installation
|
|
17
17
|
|
|
18
|
+
> **Recommended:** Copy the repo URL and tell your AI agent to install it. The agent will set up everything — dependencies, config file, credential guard — in the right places for your project.
|
|
19
|
+
>
|
|
20
|
+
> ```
|
|
21
|
+
> Install @blogic-cz/agent-tools from https://github.com/blogic-cz/agent-tools and set it up for this project.
|
|
22
|
+
> ```
|
|
23
|
+
|
|
24
|
+
### Manual installation
|
|
25
|
+
|
|
18
26
|
```bash
|
|
19
27
|
bun add @blogic-cz/agent-tools
|
|
20
28
|
```
|
|
@@ -38,6 +46,7 @@ ls src/ # gh-tool/ db-tool/ k8s-tool/ az-tool/ logs-tool/ session-tool/ credent
|
|
|
38
46
|
```json5
|
|
39
47
|
{
|
|
40
48
|
$schema: "https://raw.githubusercontent.com/blogic-cz/agent-tools/main/schemas/agent-tools.schema.json",
|
|
49
|
+
defaultEnvironment: "test", // optional: any string (e.g. "local", "test", "prod")
|
|
41
50
|
kubernetes: {
|
|
42
51
|
default: {
|
|
43
52
|
clusterId: "your-cluster-id",
|
|
@@ -56,9 +65,14 @@ ls src/ # gh-tool/ db-tool/ k8s-tool/ az-tool/ logs-tool/ session-tool/ credent
|
|
|
56
65
|
3. Run tools:
|
|
57
66
|
|
|
58
67
|
```bash
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
68
|
+
bunx agent-tools-gh pr status
|
|
69
|
+
bunx agent-tools-k8s kubectl --env test --cmd "get pods"
|
|
70
|
+
bunx agent-tools-logs list --env local
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
bunx agent-tools-gh pr review-triage # interactive summary of PR feedback
|
|
75
|
+
bunx agent-tools-k8s pods --env test # list pods (structured command)
|
|
62
76
|
```
|
|
63
77
|
|
|
64
78
|
4. Hook up the credential guard in your agent config (Claude Code, OpenCode, etc.):
|
|
@@ -71,14 +85,14 @@ export default { handleToolExecuteBefore };
|
|
|
71
85
|
|
|
72
86
|
## Tools
|
|
73
87
|
|
|
74
|
-
| Binary | Description
|
|
75
|
-
| --------------------- |
|
|
76
|
-
| `agent-tools-gh` | GitHub CLI wrapper — PR management, issues, workflows
|
|
77
|
-
| `agent-tools-db` | Database query tool — SQL execution, schema introspection
|
|
78
|
-
| `agent-tools-k8s` | Kubernetes tool — kubectl
|
|
79
|
-
| `agent-tools-az` | Azure DevOps tool — pipelines, builds, repos
|
|
80
|
-
| `agent-tools-logs` | Application logs — read local and remote (k8s pod) logs
|
|
81
|
-
| `agent-tools-session` | OpenCode session browser — list, read, search sessions
|
|
88
|
+
| Binary | Description |
|
|
89
|
+
| --------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
90
|
+
| `agent-tools-gh` | GitHub CLI wrapper — PR management, issues, workflows, composite commands (`review-triage`, `reply-and-resolve`) |
|
|
91
|
+
| `agent-tools-db` | Database query tool — SQL execution, schema introspection |
|
|
92
|
+
| `agent-tools-k8s` | Kubernetes tool — kubectl wrapper + structured commands (`pods`, `logs`, `describe`, `exec`, `top`) |
|
|
93
|
+
| `agent-tools-az` | Azure DevOps tool — pipelines, builds, repos |
|
|
94
|
+
| `agent-tools-logs` | Application logs — read local and remote (k8s pod) logs |
|
|
95
|
+
| `agent-tools-session` | OpenCode session browser — list, read, search sessions |
|
|
82
96
|
|
|
83
97
|
All tools support `--help` for full usage documentation.
|
|
84
98
|
|
|
@@ -86,6 +100,16 @@ All tools support `--help` for full usage documentation.
|
|
|
86
100
|
|
|
87
101
|
Config is loaded from `agent-tools.json5` (or `agent-tools.json`) by walking up from the current working directory. Missing config = zero-config mode (works for `gh-tool`; others require config).
|
|
88
102
|
|
|
103
|
+
### Global Settings
|
|
104
|
+
|
|
105
|
+
Use `defaultEnvironment` to set the default target for tools that support environments (k8s-tool, logs-tool, db-tool). Passing `--env` explicitly always takes precedence. Note that tools will block implicit production access if `defaultEnvironment` is set to `"prod"`.
|
|
106
|
+
|
|
107
|
+
```json5
|
|
108
|
+
{
|
|
109
|
+
defaultEnvironment: "test",
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
89
113
|
### IDE Autocompletion
|
|
90
114
|
|
|
91
115
|
Add `$schema` to your config file:
|
|
@@ -110,8 +134,8 @@ Each tool section supports multiple named profiles. Select with `--profile <name
|
|
|
110
134
|
```
|
|
111
135
|
|
|
112
136
|
```bash
|
|
113
|
-
|
|
114
|
-
|
|
137
|
+
bunx agent-tools-az cmd --cmd "pipelines list" # uses "default" profile
|
|
138
|
+
bunx agent-tools-az cmd --cmd "pipelines list" --profile legacy # uses "legacy" profile
|
|
115
139
|
```
|
|
116
140
|
|
|
117
141
|
**Profile resolution:** `--profile` flag > auto-select (single profile) > `"default"` key > error.
|
|
@@ -120,34 +144,38 @@ npx agent-tools-az pipeline list --profile legacy # uses "legacy" profile
|
|
|
120
144
|
|
|
121
145
|
See [`examples/agent-tools.json5`](./examples/agent-tools.json5) for a complete example with all options documented.
|
|
122
146
|
|
|
123
|
-
##
|
|
147
|
+
## Authentication
|
|
124
148
|
|
|
125
|
-
|
|
149
|
+
Each tool uses its own auth method — no unified token store:
|
|
126
150
|
|
|
127
|
-
|
|
|
128
|
-
|
|
|
129
|
-
| `
|
|
130
|
-
| `
|
|
151
|
+
| Tool | Auth Method |
|
|
152
|
+
| ----------- | -------------------------------------------------------------------------------------------- |
|
|
153
|
+
| `gh-tool` | `gh` CLI session (`gh auth login`) or `GITHUB_TOKEN` env var |
|
|
154
|
+
| `k8s-tool` | Existing kubectl context (kubeconfig). Cluster ID from config resolves context automatically |
|
|
155
|
+
| `az-tool` | `az` CLI session (`az login`) |
|
|
156
|
+
| `db-tool` | Password from env var defined by `passwordEnvVar` in config (e.g. `AGENT_TOOLS_DB_PASSWORD`) |
|
|
157
|
+
| `logs-tool` | No auth — reads local files or uses k8s-tool for remote access |
|
|
131
158
|
|
|
132
|
-
|
|
159
|
+
Secrets are **never** stored in the config file. The `db-tool` config references env var **names** only:
|
|
133
160
|
|
|
134
|
-
|
|
161
|
+
```json5
|
|
162
|
+
{
|
|
163
|
+
databases: {
|
|
164
|
+
default: {
|
|
165
|
+
passwordEnvVar: "AGENT_TOOLS_DB_PASSWORD", // tool reads process.env[passwordEnvVar] at runtime
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
}
|
|
169
|
+
```
|
|
135
170
|
|
|
136
|
-
|
|
171
|
+
Set the values in your shell:
|
|
137
172
|
|
|
138
173
|
```bash
|
|
139
174
|
export AGENT_TOOLS_DB_PASSWORD="your-password"
|
|
140
175
|
export GITHUB_TOKEN="ghp_xxxxxxxxxxxx"
|
|
141
176
|
```
|
|
142
177
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
```powershell
|
|
146
|
-
[Environment]::SetEnvironmentVariable("AGENT_TOOLS_DB_PASSWORD", "your-password", "User")
|
|
147
|
-
[Environment]::SetEnvironmentVariable("GITHUB_TOKEN", "ghp_xxxxxxxxxxxx", "User")
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
Restart your terminal after adding env vars. The credential guard ensures these values never leak into agent output.
|
|
178
|
+
The credential guard ensures these values never leak into agent output.
|
|
151
179
|
|
|
152
180
|
## Credential Guard
|
|
153
181
|
|
|
@@ -231,6 +259,16 @@ Use the `credentialGuard` config section to extend built-in defaults (arrays are
|
|
|
231
259
|
|
|
232
260
|
The guard source is at [`src/credential-guard/index.ts`](./src/credential-guard/index.ts). Fork the repo, adjust patterns, submit a PR: https://github.com/blogic-cz/agent-tools
|
|
233
261
|
|
|
262
|
+
## Development & Evaluation
|
|
263
|
+
|
|
264
|
+
### Run Evaluation Harness
|
|
265
|
+
|
|
266
|
+
The evaluation harness runs a set of test cases against the tools to ensure quality and reliability:
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
bun run tests/eval/run.ts
|
|
270
|
+
```
|
|
271
|
+
|
|
234
272
|
## License
|
|
235
273
|
|
|
236
274
|
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blogic-cz/agent-tools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "CLI tools for AI coding agent workflows — GitHub, database, Kubernetes, Azure DevOps, logs, and sessions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -38,6 +38,9 @@
|
|
|
38
38
|
"./credential-guard": "./src/credential-guard/index.ts",
|
|
39
39
|
"./config": "./src/config/index.ts"
|
|
40
40
|
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
},
|
|
41
44
|
"scripts": {
|
|
42
45
|
"check": "bun check.ts",
|
|
43
46
|
"check:ci": "bun check.ts ci",
|
|
@@ -61,9 +64,6 @@
|
|
|
61
64
|
"typescript": "5.9.3",
|
|
62
65
|
"vitest": "^4.0.18"
|
|
63
66
|
},
|
|
64
|
-
"publishConfig": {
|
|
65
|
-
"access": "public"
|
|
66
|
-
},
|
|
67
67
|
"engines": {
|
|
68
68
|
"bun": ">=1.0.0"
|
|
69
69
|
}
|
|
@@ -49,6 +49,10 @@
|
|
|
49
49
|
"credentialGuard": {
|
|
50
50
|
"description": "Global credential guard configuration merged with built-in defaults.",
|
|
51
51
|
"$ref": "#/definitions/CredentialGuardConfig"
|
|
52
|
+
},
|
|
53
|
+
"defaultEnvironment": {
|
|
54
|
+
"description": "Optional default environment name used by tools when no --env flag is provided.",
|
|
55
|
+
"type": "string"
|
|
52
56
|
}
|
|
53
57
|
},
|
|
54
58
|
"definitions": {
|
package/src/az-tool/build.ts
CHANGED
|
@@ -54,6 +54,7 @@ export const getBuildTimeline = Effect.fn("Build.getBuildTimeline")(function* (b
|
|
|
54
54
|
new AzParseError({
|
|
55
55
|
message: `Failed to parse build timeline: ${String(e)}`,
|
|
56
56
|
rawOutput: JSON.stringify(result).slice(0, 500),
|
|
57
|
+
hint: "The Azure DevOps API returned an unexpected response format for build timeline",
|
|
57
58
|
}),
|
|
58
59
|
),
|
|
59
60
|
);
|
|
@@ -107,6 +108,7 @@ export const getBuildLogs = Effect.fn("Build.getBuildLogs")(function* (buildId:
|
|
|
107
108
|
new AzParseError({
|
|
108
109
|
message: `Failed to parse build logs: ${String(e)}`,
|
|
109
110
|
rawOutput: JSON.stringify(result).slice(0, 500),
|
|
111
|
+
hint: "The Azure DevOps API returned an unexpected response format for build logs",
|
|
110
112
|
}),
|
|
111
113
|
),
|
|
112
114
|
);
|
|
@@ -143,6 +145,7 @@ export const getBuildLogContent = Effect.fn("Build.getBuildLogContent")(function
|
|
|
143
145
|
new AzParseError({
|
|
144
146
|
message: `Failed to parse log content: ${String(e)}`,
|
|
145
147
|
rawOutput: String(result).slice(0, 500),
|
|
148
|
+
hint: "The Azure DevOps API returned an unexpected format for log content",
|
|
146
149
|
}),
|
|
147
150
|
),
|
|
148
151
|
);
|
|
@@ -245,6 +248,7 @@ export const listPipelineRuns = Effect.fn("Build.listPipelineRuns")(function* (o
|
|
|
245
248
|
new AzParseError({
|
|
246
249
|
message: "Failed to parse JSON from pipeline runs output",
|
|
247
250
|
rawOutput: rawResult.slice(0, 500),
|
|
251
|
+
hint: "The az CLI returned non-JSON output. Check that the command ran successfully.",
|
|
248
252
|
}),
|
|
249
253
|
});
|
|
250
254
|
|
|
@@ -268,6 +272,7 @@ export const listPipelineRuns = Effect.fn("Build.listPipelineRuns")(function* (o
|
|
|
268
272
|
new AzParseError({
|
|
269
273
|
message: `Failed to parse pipeline runs: ${String(e)}`,
|
|
270
274
|
rawOutput: rawResult.slice(0, 500),
|
|
275
|
+
hint: "The Azure DevOps API returned an unexpected response format for pipeline runs",
|
|
271
276
|
}),
|
|
272
277
|
),
|
|
273
278
|
);
|
package/src/az-tool/errors.ts
CHANGED
|
@@ -3,6 +3,9 @@ import { Schema } from "effect";
|
|
|
3
3
|
export class AzSecurityError extends Schema.TaggedErrorClass<AzSecurityError>()("AzSecurityError", {
|
|
4
4
|
message: Schema.String,
|
|
5
5
|
command: Schema.String,
|
|
6
|
+
hint: Schema.optionalKey(Schema.String),
|
|
7
|
+
nextCommand: Schema.optionalKey(Schema.String),
|
|
8
|
+
retryable: Schema.optionalKey(Schema.Boolean),
|
|
6
9
|
}) {}
|
|
7
10
|
|
|
8
11
|
export class AzCommandError extends Schema.TaggedErrorClass<AzCommandError>()("AzCommandError", {
|
|
@@ -10,17 +13,26 @@ export class AzCommandError extends Schema.TaggedErrorClass<AzCommandError>()("A
|
|
|
10
13
|
command: Schema.String,
|
|
11
14
|
exitCode: Schema.optionalKey(Schema.Number),
|
|
12
15
|
stderr: Schema.optionalKey(Schema.String),
|
|
16
|
+
hint: Schema.optionalKey(Schema.String),
|
|
17
|
+
nextCommand: Schema.optionalKey(Schema.String),
|
|
18
|
+
retryable: Schema.optionalKey(Schema.Boolean),
|
|
13
19
|
}) {}
|
|
14
20
|
|
|
15
21
|
export class AzTimeoutError extends Schema.TaggedErrorClass<AzTimeoutError>()("AzTimeoutError", {
|
|
16
22
|
message: Schema.String,
|
|
17
23
|
command: Schema.String,
|
|
18
24
|
timeoutMs: Schema.Number,
|
|
25
|
+
hint: Schema.optionalKey(Schema.String),
|
|
26
|
+
nextCommand: Schema.optionalKey(Schema.String),
|
|
27
|
+
retryable: Schema.optionalKey(Schema.Boolean),
|
|
19
28
|
}) {}
|
|
20
29
|
|
|
21
30
|
export class AzParseError extends Schema.TaggedErrorClass<AzParseError>()("AzParseError", {
|
|
22
31
|
message: Schema.String,
|
|
23
32
|
rawOutput: Schema.String,
|
|
33
|
+
hint: Schema.optionalKey(Schema.String),
|
|
34
|
+
nextCommand: Schema.optionalKey(Schema.String),
|
|
35
|
+
retryable: Schema.optionalKey(Schema.Boolean),
|
|
24
36
|
}) {}
|
|
25
37
|
|
|
26
38
|
export type AzError = AzSecurityError | AzCommandError | AzTimeoutError | AzParseError;
|
package/src/az-tool/index.ts
CHANGED
|
@@ -11,13 +11,111 @@ import {
|
|
|
11
11
|
getBuildLogs,
|
|
12
12
|
getBuildTimeline,
|
|
13
13
|
} from "./build";
|
|
14
|
-
import { AzCommandError } from "./errors";
|
|
15
|
-
import { extractOptionValue } from "./extract-option-value";
|
|
16
14
|
import { AzService, AzServiceLayer } from "./service";
|
|
17
15
|
import { ConfigServiceLayer } from "../config";
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Common flags shared across build subcommands
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
const commonBuildFlags = {
|
|
22
|
+
format: formatOption,
|
|
23
|
+
profile: Flag.optional(Flag.string("profile")).pipe(
|
|
24
|
+
Flag.withDescription("Azure DevOps profile name (from agent-tools config)"),
|
|
25
|
+
),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Build subcommands
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
const buildTimelineCommand = Command.make(
|
|
33
|
+
"timeline",
|
|
34
|
+
{
|
|
35
|
+
...commonBuildFlags,
|
|
36
|
+
buildId: Flag.integer("build-id").pipe(Flag.withDescription("Build ID")),
|
|
37
|
+
},
|
|
38
|
+
({ buildId, format, profile: _profile }) =>
|
|
39
|
+
Effect.gen(function* () {
|
|
40
|
+
const result = yield* getBuildTimeline(buildId);
|
|
41
|
+
yield* Console.log(formatAny(result, format));
|
|
42
|
+
}),
|
|
43
|
+
).pipe(Command.withDescription("Get build timeline with all records (jobs, stages, tasks)"));
|
|
44
|
+
|
|
45
|
+
const buildFailedJobsCommand = Command.make(
|
|
46
|
+
"failed-jobs",
|
|
47
|
+
{
|
|
48
|
+
...commonBuildFlags,
|
|
49
|
+
buildId: Flag.integer("build-id").pipe(Flag.withDescription("Build ID")),
|
|
50
|
+
},
|
|
51
|
+
({ buildId, format, profile: _profile }) =>
|
|
52
|
+
Effect.gen(function* () {
|
|
53
|
+
const failedJobs = yield* findFailedJobs(buildId);
|
|
54
|
+
yield* Console.log(formatAny({ buildId, failedJobs }, format));
|
|
55
|
+
}),
|
|
56
|
+
).pipe(Command.withDescription("Find failed or canceled jobs in a build"));
|
|
57
|
+
|
|
58
|
+
const buildLogsCommand = Command.make(
|
|
59
|
+
"logs",
|
|
60
|
+
{
|
|
61
|
+
...commonBuildFlags,
|
|
62
|
+
buildId: Flag.integer("build-id").pipe(Flag.withDescription("Build ID")),
|
|
63
|
+
},
|
|
64
|
+
({ buildId, format, profile: _profile }) =>
|
|
65
|
+
Effect.gen(function* () {
|
|
66
|
+
const result = yield* getBuildLogs(buildId);
|
|
67
|
+
yield* Console.log(formatAny(result, format));
|
|
68
|
+
}),
|
|
69
|
+
).pipe(Command.withDescription("Get list of build logs"));
|
|
70
|
+
|
|
71
|
+
const buildLogContentCommand = Command.make(
|
|
72
|
+
"log-content",
|
|
73
|
+
{
|
|
74
|
+
...commonBuildFlags,
|
|
75
|
+
buildId: Flag.integer("build-id").pipe(Flag.withDescription("Build ID")),
|
|
76
|
+
logId: Flag.integer("log-id").pipe(Flag.withDescription("Log ID")),
|
|
77
|
+
},
|
|
78
|
+
({ buildId, format, logId, profile: _profile }) =>
|
|
79
|
+
Effect.gen(function* () {
|
|
80
|
+
const content = yield* getBuildLogContent(buildId, logId);
|
|
81
|
+
yield* Console.log(formatAny({ buildId, logId, content }, format));
|
|
82
|
+
}),
|
|
83
|
+
).pipe(Command.withDescription("Get specific log content by log ID"));
|
|
84
|
+
|
|
85
|
+
const buildSummaryCommand = Command.make(
|
|
86
|
+
"summary",
|
|
87
|
+
{
|
|
88
|
+
...commonBuildFlags,
|
|
89
|
+
buildId: Flag.integer("build-id").pipe(Flag.withDescription("Build ID")),
|
|
90
|
+
},
|
|
91
|
+
({ buildId, format, profile: _profile }) =>
|
|
92
|
+
Effect.gen(function* () {
|
|
93
|
+
const summary = yield* getBuildJobSummary(buildId);
|
|
94
|
+
yield* Console.log(formatAny({ buildId, summary }, format));
|
|
95
|
+
}),
|
|
96
|
+
).pipe(Command.withDescription("Get job summaries with duration and status information"));
|
|
97
|
+
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
// Build parent command
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
const buildCommand = Command.make("build", {}).pipe(
|
|
103
|
+
Command.withDescription("Build helpers for Azure DevOps pipelines"),
|
|
104
|
+
Command.withSubcommands([
|
|
105
|
+
buildTimelineCommand,
|
|
106
|
+
buildFailedJobsCommand,
|
|
107
|
+
buildLogsCommand,
|
|
108
|
+
buildLogContentCommand,
|
|
109
|
+
buildSummaryCommand,
|
|
110
|
+
]),
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
// Raw command subcommand (preserves existing --cmd behavior)
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
const cmdCommand = Command.make(
|
|
118
|
+
"cmd",
|
|
21
119
|
{
|
|
22
120
|
profile: Flag.optional(Flag.string("profile")).pipe(
|
|
23
121
|
Flag.withDescription("Azure DevOps profile name (from agent-tools config)"),
|
|
@@ -36,20 +134,12 @@ const mainCommand = Command.make(
|
|
|
36
134
|
Effect.gen(function* () {
|
|
37
135
|
const projectName = project ? Option.getOrUndefined(project) : undefined;
|
|
38
136
|
|
|
39
|
-
// Handle dry-run mode
|
|
40
137
|
if (dryRun) {
|
|
41
138
|
const fullCommand = `az ${cmd}`;
|
|
42
139
|
yield* Console.log(`[DRY-RUN] Would execute: ${fullCommand}`);
|
|
43
140
|
return;
|
|
44
141
|
}
|
|
45
142
|
|
|
46
|
-
const buildHelperResult = yield* runBuildHelperCommand(cmd);
|
|
47
|
-
|
|
48
|
-
if (buildHelperResult !== undefined) {
|
|
49
|
-
yield* Console.log(formatAny(buildHelperResult, format));
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
143
|
const az = yield* AzService;
|
|
54
144
|
|
|
55
145
|
const startTime = Date.now();
|
|
@@ -68,16 +158,35 @@ const mainCommand = Command.make(
|
|
|
68
158
|
);
|
|
69
159
|
}),
|
|
70
160
|
).pipe(
|
|
161
|
+
Command.withDescription(
|
|
162
|
+
`Execute raw az CLI commands directly.
|
|
163
|
+
|
|
164
|
+
EXAMPLES:
|
|
165
|
+
agent-tools-az cmd --cmd "pipelines list"
|
|
166
|
+
agent-tools-az cmd --cmd "repos list" --project my-project
|
|
167
|
+
agent-tools-az cmd --cmd "pipelines runs list --output json"`,
|
|
168
|
+
),
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
// Main command with subcommands
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
|
|
175
|
+
const mainCommand = Command.make("az-tool", {}).pipe(
|
|
71
176
|
Command.withDescription(
|
|
72
177
|
`Azure CLI Tool for Coding Agents (READ-ONLY)
|
|
73
178
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
179
|
+
Typed build subcommands:
|
|
180
|
+
az-tool build timeline --build-id 123
|
|
181
|
+
az-tool build failed-jobs --build-id 123
|
|
182
|
+
az-tool build logs --build-id 123
|
|
183
|
+
az-tool build log-content --build-id 123 --log-id 45
|
|
184
|
+
az-tool build summary --build-id 123
|
|
185
|
+
|
|
186
|
+
Raw az wrapper:
|
|
187
|
+
az-tool cmd --cmd "pipelines list"`,
|
|
80
188
|
),
|
|
189
|
+
Command.withSubcommands([buildCommand, cmdCommand]),
|
|
81
190
|
);
|
|
82
191
|
|
|
83
192
|
const cli = Command.run(mainCommand, {
|
|
@@ -94,88 +203,3 @@ const program = cli.pipe(Effect.provide(MainLayer), Effect.tapCause(renderCauseT
|
|
|
94
203
|
BunRuntime.runMain(program, {
|
|
95
204
|
disableErrorReporting: true,
|
|
96
205
|
});
|
|
97
|
-
|
|
98
|
-
function runBuildHelperCommand(cmd: string) {
|
|
99
|
-
return Effect.gen(function* () {
|
|
100
|
-
const words = cmd.trim().split(/\s+/);
|
|
101
|
-
|
|
102
|
-
if (words[0] !== "build") {
|
|
103
|
-
return undefined;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const action = words[1];
|
|
107
|
-
if (!action) {
|
|
108
|
-
return yield* invalidBuildCommand(
|
|
109
|
-
cmd,
|
|
110
|
-
"Missing build action. Use one of: timeline, failed-jobs, logs, log-content, summary",
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const buildId = yield* parseRequiredIntOption(words, "--build-id", cmd);
|
|
115
|
-
|
|
116
|
-
if (action === "timeline") {
|
|
117
|
-
const timeline = yield* getBuildTimeline(buildId);
|
|
118
|
-
return timeline;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (action === "failed-jobs") {
|
|
122
|
-
const failedJobs = yield* findFailedJobs(buildId);
|
|
123
|
-
return { buildId, failedJobs };
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (action === "logs") {
|
|
127
|
-
const logs = yield* getBuildLogs(buildId);
|
|
128
|
-
return logs;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (action === "log-content") {
|
|
132
|
-
const logId = yield* parseRequiredIntOption(words, "--log-id", cmd);
|
|
133
|
-
const content = yield* getBuildLogContent(buildId, logId);
|
|
134
|
-
return {
|
|
135
|
-
buildId,
|
|
136
|
-
logId,
|
|
137
|
-
content,
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (action === "summary") {
|
|
142
|
-
const summary = yield* getBuildJobSummary(buildId);
|
|
143
|
-
return { buildId, summary };
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return yield* invalidBuildCommand(
|
|
147
|
-
cmd,
|
|
148
|
-
`Unknown build action '${action}'. Use one of: timeline, failed-jobs, logs, log-content, summary`,
|
|
149
|
-
);
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function invalidBuildCommand(cmd: string, message: string) {
|
|
154
|
-
return Effect.fail(
|
|
155
|
-
new AzCommandError({
|
|
156
|
-
message,
|
|
157
|
-
command: cmd,
|
|
158
|
-
exitCode: 2,
|
|
159
|
-
stderr: message,
|
|
160
|
-
}),
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function parseRequiredIntOption(args: readonly string[], optionName: string, cmd: string) {
|
|
165
|
-
return Effect.gen(function* () {
|
|
166
|
-
const rawValue = extractOptionValue(args, optionName);
|
|
167
|
-
if (!rawValue) {
|
|
168
|
-
return yield* invalidBuildCommand(cmd, `Missing required option ${optionName}`);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const parsedValue = Number.parseInt(rawValue, 10);
|
|
172
|
-
if (Number.isNaN(parsedValue)) {
|
|
173
|
-
return yield* invalidBuildCommand(
|
|
174
|
-
cmd,
|
|
175
|
-
`Option ${optionName} must be an integer, received '${rawValue}'`,
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
return parsedValue;
|
|
180
|
-
});
|
|
181
|
-
}
|
package/src/az-tool/service.ts
CHANGED
|
@@ -32,7 +32,7 @@ export class AzService extends ServiceMap.Service<
|
|
|
32
32
|
message: "No Azure configuration found. Add an 'azure' section to agent-tools.json5.",
|
|
33
33
|
command: "unknown",
|
|
34
34
|
exitCode: -1,
|
|
35
|
-
|
|
35
|
+
hint: "Create or update agent-tools.json5 with an 'azure' section containing organization and defaultProject",
|
|
36
36
|
});
|
|
37
37
|
return {
|
|
38
38
|
runCommand: (_cmd: string, _project?: string) => Effect.fail(noConfigError),
|
|
@@ -69,7 +69,9 @@ export class AzService extends ServiceMap.Service<
|
|
|
69
69
|
message: `Command execution failed: ${platformError.message}`,
|
|
70
70
|
command: fullCommand,
|
|
71
71
|
exitCode: -1,
|
|
72
|
-
|
|
72
|
+
hint: "Check that the az CLI is installed and authenticated",
|
|
73
|
+
nextCommand: "az login",
|
|
74
|
+
retryable: true,
|
|
73
75
|
}),
|
|
74
76
|
),
|
|
75
77
|
);
|
|
@@ -87,6 +89,7 @@ export class AzService extends ServiceMap.Service<
|
|
|
87
89
|
return yield* new AzSecurityError({
|
|
88
90
|
message: securityCheck.reason ?? "Command not allowed",
|
|
89
91
|
command: cmd,
|
|
92
|
+
hint: "Only read-only az devops commands are allowed. Use build helpers for common operations.",
|
|
90
93
|
});
|
|
91
94
|
}
|
|
92
95
|
|
|
@@ -125,6 +128,8 @@ export class AzService extends ServiceMap.Service<
|
|
|
125
128
|
message: `Command timed out after ${azConfig.timeoutMs ?? 60000}ms`,
|
|
126
129
|
command: fullCommand,
|
|
127
130
|
timeoutMs: azConfig.timeoutMs ?? 60000,
|
|
131
|
+
retryable: true,
|
|
132
|
+
hint: "The command took too long. Retry or increase timeoutMs in azure config.",
|
|
128
133
|
});
|
|
129
134
|
}
|
|
130
135
|
|
|
@@ -148,6 +153,7 @@ export class AzService extends ServiceMap.Service<
|
|
|
148
153
|
return yield* new AzSecurityError({
|
|
149
154
|
message: securityCheck.reason ?? "Invoke not allowed",
|
|
150
155
|
command: `invoke --area ${params.area} --resource ${params.resource}`,
|
|
156
|
+
hint: "Only allowed invoke areas/resources can be used. Check az-tool security config.",
|
|
151
157
|
});
|
|
152
158
|
}
|
|
153
159
|
|
|
@@ -183,6 +189,8 @@ export class AzService extends ServiceMap.Service<
|
|
|
183
189
|
message: `Invoke timed out after ${azConfig.timeoutMs ?? 60000}ms`,
|
|
184
190
|
command: fullCommand,
|
|
185
191
|
timeoutMs: azConfig.timeoutMs ?? 60000,
|
|
192
|
+
retryable: true,
|
|
193
|
+
hint: "The invoke took too long. Retry or increase timeoutMs in azure config.",
|
|
186
194
|
});
|
|
187
195
|
}
|
|
188
196
|
|
|
@@ -203,6 +211,7 @@ export class AzService extends ServiceMap.Service<
|
|
|
203
211
|
new AzParseError({
|
|
204
212
|
message: `Failed to parse JSON response from invoke`,
|
|
205
213
|
rawOutput: result.stdout.slice(0, 500),
|
|
214
|
+
hint: "The az CLI returned non-JSON output. Ensure --output json is used.",
|
|
206
215
|
}),
|
|
207
216
|
});
|
|
208
217
|
return jsonData;
|
package/src/config/index.ts
CHANGED
|
@@ -9,4 +9,10 @@ export type {
|
|
|
9
9
|
CredentialGuardConfig,
|
|
10
10
|
} from "./types.ts";
|
|
11
11
|
|
|
12
|
-
export {
|
|
12
|
+
export {
|
|
13
|
+
ConfigService,
|
|
14
|
+
ConfigServiceLayer,
|
|
15
|
+
getToolConfig,
|
|
16
|
+
getDefaultEnvironment,
|
|
17
|
+
loadConfig,
|
|
18
|
+
} from "./loader";
|
package/src/config/loader.ts
CHANGED
|
@@ -65,6 +65,7 @@ const AgentToolsConfigSchema = Schema.Struct({
|
|
|
65
65
|
}),
|
|
66
66
|
),
|
|
67
67
|
credentialGuard: Schema.optionalKey(CredentialGuardConfigSchema),
|
|
68
|
+
defaultEnvironment: Schema.optionalKey(Schema.String),
|
|
68
69
|
});
|
|
69
70
|
|
|
70
71
|
async function findConfigFile(startDirectory: string = process.cwd()): Promise<string | undefined> {
|
|
@@ -168,3 +169,7 @@ export function getToolConfig<T>(
|
|
|
168
169
|
`Multiple ${section} profiles found: [${keys.join(", ")}]. Use --profile <name> to select one.`,
|
|
169
170
|
);
|
|
170
171
|
}
|
|
172
|
+
|
|
173
|
+
export function getDefaultEnvironment(config: AgentToolsConfig | undefined): string | undefined {
|
|
174
|
+
return config?.defaultEnvironment;
|
|
175
|
+
}
|
package/src/config/types.ts
CHANGED
|
@@ -79,4 +79,6 @@ export type AgentToolsConfig = {
|
|
|
79
79
|
};
|
|
80
80
|
/** Global credential guard config (merged with built-in defaults, not per-profile) */
|
|
81
81
|
credentialGuard?: CredentialGuardConfig;
|
|
82
|
+
/** Optional default environment name (local|test|prod) used by tools when no --env flag is provided */
|
|
83
|
+
defaultEnvironment?: string;
|
|
82
84
|
};
|