@blogic-cz/agent-tools 0.18.1 → 1.0.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 +23 -6
- package/dist/az-tool/config.d.ts +60 -12
- package/dist/az-tool/config.d.ts.map +1 -1
- package/dist/az-tool/errors.d.ts +10 -1
- package/dist/az-tool/errors.d.ts.map +1 -1
- package/dist/az-tool/profile.d.ts +16 -0
- package/dist/az-tool/profile.d.ts.map +1 -0
- package/dist/az-tool/security.d.ts +20 -3
- package/dist/az-tool/security.d.ts.map +1 -1
- package/dist/az-tool/service.d.ts +9 -4
- package/dist/az-tool/service.d.ts.map +1 -1
- package/dist/az-tool/types.d.ts +10 -60
- package/dist/az-tool/types.d.ts.map +1 -1
- package/dist/{az-tool → azdo-tool}/build.d.ts +9 -9
- package/dist/azdo-tool/build.d.ts.map +1 -0
- package/dist/azdo-tool/config.d.ts +13 -0
- package/dist/azdo-tool/config.d.ts.map +1 -0
- package/dist/azdo-tool/errors.d.ts +43 -0
- package/dist/azdo-tool/errors.d.ts.map +1 -0
- package/dist/azdo-tool/extract-option-value.d.ts.map +1 -0
- package/dist/azdo-tool/index.d.ts +3 -0
- package/dist/azdo-tool/index.d.ts.map +1 -0
- package/dist/azdo-tool/security.d.ts +4 -0
- package/dist/azdo-tool/security.d.ts.map +1 -0
- package/dist/azdo-tool/service.d.ts +15 -0
- package/dist/azdo-tool/service.d.ts.map +1 -0
- package/dist/azdo-tool/transformers.d.ts.map +1 -0
- package/dist/azdo-tool/types.d.ts +64 -0
- package/dist/azdo-tool/types.d.ts.map +1 -0
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/loader.d.ts +1 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/types.d.ts +18 -1
- package/dist/config/types.d.ts.map +1 -1
- package/dist/credential-guard/index.d.ts.map +1 -1
- package/dist/shared/azure-credentials.d.ts +20 -0
- package/dist/shared/azure-credentials.d.ts.map +1 -0
- package/dist/shared/binary-preflight.d.ts +1 -1
- package/package.json +8 -2
- package/schemas/agent-tools.schema.json +42 -1
- package/src/az-tool/config.ts +135 -25
- package/src/az-tool/errors.ts +14 -1
- package/src/az-tool/index.ts +67 -134
- package/src/az-tool/profile.ts +48 -0
- package/src/az-tool/security.ts +248 -89
- package/src/az-tool/service.ts +137 -242
- package/src/az-tool/types.ts +11 -64
- package/src/{az-tool → azdo-tool}/build.ts +11 -11
- package/src/azdo-tool/config.ts +33 -0
- package/src/azdo-tool/errors.ts +41 -0
- package/src/azdo-tool/index.ts +222 -0
- package/src/azdo-tool/security.ts +157 -0
- package/src/azdo-tool/service.ts +322 -0
- package/src/azdo-tool/types.ts +67 -0
- package/src/config/index.ts +1 -0
- package/src/config/loader.ts +10 -1
- package/src/config/types.ts +19 -1
- package/src/credential-guard/index.ts +7 -2
- package/src/shared/azure-credentials.ts +50 -0
- package/src/shared/binary-preflight.ts +1 -1
- package/dist/az-tool/build.d.ts.map +0 -1
- package/dist/az-tool/extract-option-value.d.ts.map +0 -1
- package/dist/az-tool/transformers.d.ts.map +0 -1
- /package/dist/{az-tool → azdo-tool}/extract-option-value.d.ts +0 -0
- /package/dist/{az-tool → azdo-tool}/transformers.d.ts +0 -0
- /package/src/{az-tool → azdo-tool}/extract-option-value.ts +0 -0
- /package/src/{az-tool → azdo-tool}/transformers.ts +0 -0
package/src/az-tool/index.ts
CHANGED
|
@@ -7,197 +7,130 @@ import {
|
|
|
7
7
|
makeSchemaCommand,
|
|
8
8
|
formatAny,
|
|
9
9
|
formatOption,
|
|
10
|
-
formatOutput,
|
|
11
10
|
logText,
|
|
12
11
|
renderCauseToStderr,
|
|
13
12
|
VERSION,
|
|
14
13
|
} from "#shared";
|
|
15
14
|
import { AuditServiceLayer, withAudit } from "#shared/audit";
|
|
16
|
-
import {
|
|
17
|
-
findFailedJobs,
|
|
18
|
-
getBuildJobSummary,
|
|
19
|
-
getBuildLogContent,
|
|
20
|
-
getBuildLogs,
|
|
21
|
-
getBuildTimeline,
|
|
22
|
-
} from "./build";
|
|
23
15
|
import { AzService, AzServiceLayer } from "./service";
|
|
24
16
|
import { ConfigServiceLayer } from "#config";
|
|
25
17
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
18
|
+
const profileFlag = Flag.optional(Flag.string("profile")).pipe(
|
|
19
|
+
Flag.withDescription("Azure platform profile name (from agent-tools config)"),
|
|
20
|
+
);
|
|
29
21
|
|
|
30
|
-
const
|
|
22
|
+
const commonFlags = {
|
|
31
23
|
format: formatOption,
|
|
32
|
-
profile:
|
|
33
|
-
Flag.withDescription("Azure DevOps profile name (from agent-tools config)"),
|
|
34
|
-
),
|
|
24
|
+
profile: profileFlag,
|
|
35
25
|
};
|
|
36
26
|
|
|
27
|
+
/** Run a fixed read-only command through the same security gate as `cmd`. */
|
|
28
|
+
const runFixed = (cmd: string, profile: Option.Option<string>, format: "toon" | "json") =>
|
|
29
|
+
Effect.gen(function* () {
|
|
30
|
+
const az = yield* AzService;
|
|
31
|
+
const result = yield* az.runCommand(cmd, Option.getOrUndefined(profile));
|
|
32
|
+
yield* logText(formatAny(result.data, format));
|
|
33
|
+
});
|
|
34
|
+
|
|
37
35
|
// ---------------------------------------------------------------------------
|
|
38
|
-
//
|
|
36
|
+
// Typed subcommands
|
|
39
37
|
// ---------------------------------------------------------------------------
|
|
40
38
|
|
|
41
|
-
const
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
...commonBuildFlags,
|
|
45
|
-
buildId: Flag.integer("build-id").pipe(Flag.withDescription("Build ID")),
|
|
46
|
-
},
|
|
47
|
-
({ buildId, format, profile: _profile }) =>
|
|
48
|
-
Effect.gen(function* () {
|
|
49
|
-
const result = yield* getBuildTimeline(buildId);
|
|
50
|
-
yield* logText(formatAny(result, format));
|
|
51
|
-
}),
|
|
52
|
-
).pipe(Command.withDescription("Get build timeline with all records (jobs, stages, tasks)"));
|
|
39
|
+
const subscriptionCommand = Command.make("subscription", commonFlags, ({ format, profile }) =>
|
|
40
|
+
runFixed("account show", profile, format),
|
|
41
|
+
).pipe(Command.withDescription("Show the subscription every command in this profile is pinned to"));
|
|
53
42
|
|
|
54
|
-
const
|
|
55
|
-
"
|
|
56
|
-
|
|
57
|
-
...commonBuildFlags,
|
|
58
|
-
buildId: Flag.integer("build-id").pipe(Flag.withDescription("Build ID")),
|
|
59
|
-
},
|
|
60
|
-
({ buildId, format, profile: _profile }) =>
|
|
61
|
-
Effect.gen(function* () {
|
|
62
|
-
const failedJobs = yield* findFailedJobs(buildId);
|
|
63
|
-
yield* logText(formatAny({ buildId, failedJobs }, format));
|
|
64
|
-
}),
|
|
65
|
-
).pipe(Command.withDescription("Find failed or canceled jobs in a build"));
|
|
66
|
-
|
|
67
|
-
const buildLogsCommand = Command.make(
|
|
68
|
-
"logs",
|
|
69
|
-
{
|
|
70
|
-
...commonBuildFlags,
|
|
71
|
-
buildId: Flag.integer("build-id").pipe(Flag.withDescription("Build ID")),
|
|
72
|
-
},
|
|
73
|
-
({ buildId, format, profile: _profile }) =>
|
|
74
|
-
Effect.gen(function* () {
|
|
75
|
-
const result = yield* getBuildLogs(buildId);
|
|
76
|
-
yield* logText(formatAny(result, format));
|
|
77
|
-
}),
|
|
78
|
-
).pipe(Command.withDescription("Get list of build logs"));
|
|
43
|
+
const groupsCommand = Command.make("groups", commonFlags, ({ format, profile }) =>
|
|
44
|
+
runFixed("group list", profile, format),
|
|
45
|
+
).pipe(Command.withDescription("List resource groups in the pinned subscription"));
|
|
79
46
|
|
|
80
|
-
const
|
|
81
|
-
"
|
|
47
|
+
const resourcesCommand = Command.make(
|
|
48
|
+
"resources",
|
|
82
49
|
{
|
|
83
|
-
...
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
({ buildId, format, logId, profile: _profile }) =>
|
|
88
|
-
Effect.gen(function* () {
|
|
89
|
-
const content = yield* getBuildLogContent(buildId, logId);
|
|
90
|
-
yield* logText(formatAny({ buildId, logId, content }, format));
|
|
91
|
-
}),
|
|
92
|
-
).pipe(Command.withDescription("Get specific log content by log ID"));
|
|
93
|
-
|
|
94
|
-
const buildSummaryCommand = Command.make(
|
|
95
|
-
"summary",
|
|
96
|
-
{
|
|
97
|
-
...commonBuildFlags,
|
|
98
|
-
buildId: Flag.integer("build-id").pipe(Flag.withDescription("Build ID")),
|
|
50
|
+
...commonFlags,
|
|
51
|
+
group: Flag.optional(Flag.string("group")).pipe(
|
|
52
|
+
Flag.withDescription("Limit the listing to one resource group"),
|
|
53
|
+
),
|
|
99
54
|
},
|
|
100
|
-
({
|
|
55
|
+
({ format, group, profile }) =>
|
|
101
56
|
Effect.gen(function* () {
|
|
102
|
-
const
|
|
103
|
-
|
|
57
|
+
const groupName = Option.getOrUndefined(group);
|
|
58
|
+
const cmd = groupName ? `resource list --resource-group ${groupName}` : "resource list";
|
|
59
|
+
yield* runFixed(cmd, profile, format);
|
|
104
60
|
}),
|
|
105
|
-
).pipe(Command.withDescription("
|
|
106
|
-
|
|
107
|
-
// ---------------------------------------------------------------------------
|
|
108
|
-
// Build parent command
|
|
109
|
-
// ---------------------------------------------------------------------------
|
|
110
|
-
|
|
111
|
-
const buildCommand = Command.make("build", {}).pipe(
|
|
112
|
-
Command.withDescription("Build helpers for Azure DevOps pipelines"),
|
|
113
|
-
Command.withSubcommands([
|
|
114
|
-
buildTimelineCommand,
|
|
115
|
-
buildFailedJobsCommand,
|
|
116
|
-
buildLogsCommand,
|
|
117
|
-
buildLogContentCommand,
|
|
118
|
-
buildSummaryCommand,
|
|
119
|
-
]),
|
|
120
|
-
);
|
|
61
|
+
).pipe(Command.withDescription("List resources, optionally scoped to one resource group"));
|
|
121
62
|
|
|
122
63
|
// ---------------------------------------------------------------------------
|
|
123
|
-
// Raw command
|
|
64
|
+
// Raw command passthrough
|
|
124
65
|
// ---------------------------------------------------------------------------
|
|
125
66
|
|
|
126
67
|
const cmdCommand = Command.make(
|
|
127
68
|
"cmd",
|
|
128
69
|
{
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
),
|
|
132
|
-
project: Flag.optional(Flag.string("project")).pipe(
|
|
133
|
-
Flag.withDescription("Azure DevOps project name (overrides config default)"),
|
|
134
|
-
),
|
|
135
|
-
cmd: Flag.string("cmd").pipe(Flag.withDescription("az command (without 'az' prefix)")),
|
|
70
|
+
...commonFlags,
|
|
71
|
+
cmd: Flag.string("cmd").pipe(Flag.withDescription("az command (without the 'az' prefix)")),
|
|
136
72
|
dryRun: Flag.boolean("dry-run").pipe(
|
|
137
|
-
Flag.withDescription("Show command
|
|
73
|
+
Flag.withDescription("Show the command that would run, after security checks"),
|
|
138
74
|
Flag.withDefault(false),
|
|
139
75
|
),
|
|
140
|
-
format: formatOption,
|
|
141
76
|
},
|
|
142
|
-
({
|
|
77
|
+
({ cmd, dryRun, format, profile }) =>
|
|
143
78
|
Effect.gen(function* () {
|
|
144
|
-
const
|
|
79
|
+
const az = yield* AzService;
|
|
80
|
+
const profileName = Option.getOrUndefined(profile);
|
|
145
81
|
|
|
146
82
|
if (dryRun) {
|
|
147
|
-
const
|
|
148
|
-
yield* logText(`[DRY-RUN] Would execute: ${
|
|
83
|
+
const rendered = yield* az.renderCommand(cmd, profileName);
|
|
84
|
+
yield* logText(`[DRY-RUN] Would execute: ${rendered}`);
|
|
149
85
|
return;
|
|
150
86
|
}
|
|
151
87
|
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
const startTime = Date.now();
|
|
155
|
-
const output = yield* az.runCommand(cmd, projectName);
|
|
156
|
-
const executionTimeMs = Date.now() - startTime;
|
|
157
|
-
|
|
158
|
-
yield* logText(
|
|
159
|
-
formatOutput(
|
|
160
|
-
{
|
|
161
|
-
success: true,
|
|
162
|
-
data: output,
|
|
163
|
-
executionTimeMs,
|
|
164
|
-
},
|
|
165
|
-
format,
|
|
166
|
-
),
|
|
167
|
-
);
|
|
88
|
+
const result = yield* az.runCommand(cmd, profileName);
|
|
89
|
+
yield* logText(formatAny(result.data, format));
|
|
168
90
|
}),
|
|
169
91
|
).pipe(
|
|
170
92
|
Command.withDescription(
|
|
171
|
-
`Execute
|
|
93
|
+
`Execute a read-only Azure platform command.
|
|
94
|
+
|
|
95
|
+
The subscription is pinned by the selected profile — passing --subscription is rejected.
|
|
96
|
+
Mutating verbs, unknown verbs, credential reads, and shell syntax are all blocked.
|
|
172
97
|
|
|
173
98
|
EXAMPLES:
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
99
|
+
az-tool cmd --cmd "vm list"
|
|
100
|
+
az-tool cmd --cmd "webapp show --name my-app --resource-group my-rg"
|
|
101
|
+
az-tool cmd --cmd "aks list --query \\"[].{name:name,version:kubernetesVersion}\\""
|
|
102
|
+
az-tool cmd --cmd "storage account list" --profile prod --dry-run`,
|
|
177
103
|
),
|
|
178
104
|
);
|
|
179
105
|
|
|
180
106
|
// ---------------------------------------------------------------------------
|
|
181
|
-
// Main command
|
|
107
|
+
// Main command
|
|
182
108
|
// ---------------------------------------------------------------------------
|
|
183
109
|
|
|
184
110
|
const commandsCommand = makeSchemaCommand(() => mainCommand);
|
|
185
111
|
|
|
186
112
|
const mainCommand = Command.make("az-tool", {}).pipe(
|
|
187
113
|
Command.withDescription(
|
|
188
|
-
`Azure
|
|
114
|
+
`Azure Platform Tool for Coding Agents (READ-ONLY)
|
|
115
|
+
|
|
116
|
+
Inspects Azure PaaS resources — vm, webapp, storage, aks, acr, monitor, and the rest.
|
|
117
|
+
For Azure DevOps pipelines, builds, and repos use azdo-tool instead.
|
|
189
118
|
|
|
190
|
-
Typed
|
|
191
|
-
az-tool
|
|
192
|
-
az-tool
|
|
193
|
-
az-tool
|
|
194
|
-
az-tool build log-content --build-id 123 --log-id 45
|
|
195
|
-
az-tool build summary --build-id 123
|
|
119
|
+
Typed subcommands:
|
|
120
|
+
az-tool subscription
|
|
121
|
+
az-tool groups
|
|
122
|
+
az-tool resources --group my-rg
|
|
196
123
|
|
|
197
124
|
Raw az wrapper:
|
|
198
|
-
az-tool cmd --cmd "
|
|
125
|
+
az-tool cmd --cmd "webapp list"`,
|
|
199
126
|
),
|
|
200
|
-
Command.withSubcommands([
|
|
127
|
+
Command.withSubcommands([
|
|
128
|
+
subscriptionCommand,
|
|
129
|
+
groupsCommand,
|
|
130
|
+
resourcesCommand,
|
|
131
|
+
cmdCommand,
|
|
132
|
+
commandsCommand,
|
|
133
|
+
]),
|
|
201
134
|
);
|
|
202
135
|
|
|
203
136
|
const cli = Command.run(mainCommand, {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { AzurePlatformConfig } from "#config/types";
|
|
2
|
+
|
|
3
|
+
/** Profile keys treated as production unless the profile says otherwise. */
|
|
4
|
+
export const PRODUCTION_PROFILE_NAMES = ["prod", "production"] as const;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The profile key getToolConfig will resolve to, so callers can reason about
|
|
8
|
+
* which subscription they are about to touch. Mirrors getToolConfig's rules:
|
|
9
|
+
* explicit name wins, then a lone profile, then the "default" key.
|
|
10
|
+
*/
|
|
11
|
+
export function selectAzProfileName(
|
|
12
|
+
section: Record<string, unknown> | undefined,
|
|
13
|
+
explicitProfile: string | undefined,
|
|
14
|
+
): string | undefined {
|
|
15
|
+
if (explicitProfile) {
|
|
16
|
+
return explicitProfile;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!section) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const keys = Object.keys(section);
|
|
24
|
+
if (keys.length === 1) {
|
|
25
|
+
return keys[0];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return Object.hasOwn(section, "default") ? "default" : undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Production profiles require --profile to be named explicitly. An explicit
|
|
33
|
+
* `production` flag wins; otherwise the profile key carries the convention, so
|
|
34
|
+
* a profile keyed "prod" is guarded without extra configuration.
|
|
35
|
+
*/
|
|
36
|
+
export function isProductionProfile(
|
|
37
|
+
profileName: string | undefined,
|
|
38
|
+
config: AzurePlatformConfig,
|
|
39
|
+
): boolean {
|
|
40
|
+
if (config.production !== undefined) {
|
|
41
|
+
return config.production;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
profileName !== undefined &&
|
|
46
|
+
(PRODUCTION_PROFILE_NAMES as readonly string[]).includes(profileName.toLowerCase())
|
|
47
|
+
);
|
|
48
|
+
}
|