@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.
Files changed (68) hide show
  1. package/README.md +23 -6
  2. package/dist/az-tool/config.d.ts +60 -12
  3. package/dist/az-tool/config.d.ts.map +1 -1
  4. package/dist/az-tool/errors.d.ts +10 -1
  5. package/dist/az-tool/errors.d.ts.map +1 -1
  6. package/dist/az-tool/profile.d.ts +16 -0
  7. package/dist/az-tool/profile.d.ts.map +1 -0
  8. package/dist/az-tool/security.d.ts +20 -3
  9. package/dist/az-tool/security.d.ts.map +1 -1
  10. package/dist/az-tool/service.d.ts +9 -4
  11. package/dist/az-tool/service.d.ts.map +1 -1
  12. package/dist/az-tool/types.d.ts +10 -60
  13. package/dist/az-tool/types.d.ts.map +1 -1
  14. package/dist/{az-tool → azdo-tool}/build.d.ts +9 -9
  15. package/dist/azdo-tool/build.d.ts.map +1 -0
  16. package/dist/azdo-tool/config.d.ts +13 -0
  17. package/dist/azdo-tool/config.d.ts.map +1 -0
  18. package/dist/azdo-tool/errors.d.ts +43 -0
  19. package/dist/azdo-tool/errors.d.ts.map +1 -0
  20. package/dist/azdo-tool/extract-option-value.d.ts.map +1 -0
  21. package/dist/azdo-tool/index.d.ts +3 -0
  22. package/dist/azdo-tool/index.d.ts.map +1 -0
  23. package/dist/azdo-tool/security.d.ts +4 -0
  24. package/dist/azdo-tool/security.d.ts.map +1 -0
  25. package/dist/azdo-tool/service.d.ts +15 -0
  26. package/dist/azdo-tool/service.d.ts.map +1 -0
  27. package/dist/azdo-tool/transformers.d.ts.map +1 -0
  28. package/dist/azdo-tool/types.d.ts +64 -0
  29. package/dist/azdo-tool/types.d.ts.map +1 -0
  30. package/dist/config/index.d.ts +1 -1
  31. package/dist/config/index.d.ts.map +1 -1
  32. package/dist/config/loader.d.ts +1 -1
  33. package/dist/config/loader.d.ts.map +1 -1
  34. package/dist/config/types.d.ts +18 -1
  35. package/dist/config/types.d.ts.map +1 -1
  36. package/dist/credential-guard/index.d.ts.map +1 -1
  37. package/dist/shared/azure-credentials.d.ts +20 -0
  38. package/dist/shared/azure-credentials.d.ts.map +1 -0
  39. package/dist/shared/binary-preflight.d.ts +1 -1
  40. package/package.json +8 -2
  41. package/schemas/agent-tools.schema.json +42 -1
  42. package/src/az-tool/config.ts +135 -25
  43. package/src/az-tool/errors.ts +14 -1
  44. package/src/az-tool/index.ts +67 -134
  45. package/src/az-tool/profile.ts +48 -0
  46. package/src/az-tool/security.ts +248 -89
  47. package/src/az-tool/service.ts +137 -242
  48. package/src/az-tool/types.ts +11 -64
  49. package/src/{az-tool → azdo-tool}/build.ts +11 -11
  50. package/src/azdo-tool/config.ts +33 -0
  51. package/src/azdo-tool/errors.ts +41 -0
  52. package/src/azdo-tool/index.ts +222 -0
  53. package/src/azdo-tool/security.ts +157 -0
  54. package/src/azdo-tool/service.ts +322 -0
  55. package/src/azdo-tool/types.ts +67 -0
  56. package/src/config/index.ts +1 -0
  57. package/src/config/loader.ts +10 -1
  58. package/src/config/types.ts +19 -1
  59. package/src/credential-guard/index.ts +7 -2
  60. package/src/shared/azure-credentials.ts +50 -0
  61. package/src/shared/binary-preflight.ts +1 -1
  62. package/dist/az-tool/build.d.ts.map +0 -1
  63. package/dist/az-tool/extract-option-value.d.ts.map +0 -1
  64. package/dist/az-tool/transformers.d.ts.map +0 -1
  65. /package/dist/{az-tool → azdo-tool}/extract-option-value.d.ts +0 -0
  66. /package/dist/{az-tool → azdo-tool}/transformers.d.ts +0 -0
  67. /package/src/{az-tool → azdo-tool}/extract-option-value.ts +0 -0
  68. /package/src/{az-tool → azdo-tool}/transformers.ts +0 -0
@@ -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
- // Common flags shared across build subcommands
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 commonBuildFlags = {
22
+ const commonFlags = {
31
23
  format: formatOption,
32
- profile: Flag.optional(Flag.string("profile")).pipe(
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
- // Build subcommands
36
+ // Typed subcommands
39
37
  // ---------------------------------------------------------------------------
40
38
 
41
- const buildTimelineCommand = Command.make(
42
- "timeline",
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 buildFailedJobsCommand = Command.make(
55
- "failed-jobs",
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 buildLogContentCommand = Command.make(
81
- "log-content",
47
+ const resourcesCommand = Command.make(
48
+ "resources",
82
49
  {
83
- ...commonBuildFlags,
84
- buildId: Flag.integer("build-id").pipe(Flag.withDescription("Build ID")),
85
- logId: Flag.integer("log-id").pipe(Flag.withDescription("Log ID")),
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
- ({ buildId, format, profile: _profile }) =>
55
+ ({ format, group, profile }) =>
101
56
  Effect.gen(function* () {
102
- const summary = yield* getBuildJobSummary(buildId);
103
- yield* logText(formatAny({ buildId, summary }, format));
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("Get job summaries with duration and status information"));
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 subcommand (preserves existing --cmd behavior)
64
+ // Raw command passthrough
124
65
  // ---------------------------------------------------------------------------
125
66
 
126
67
  const cmdCommand = Command.make(
127
68
  "cmd",
128
69
  {
129
- profile: Flag.optional(Flag.string("profile")).pipe(
130
- Flag.withDescription("Azure DevOps profile name (from agent-tools config)"),
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 without executing"),
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
- ({ profile: _profile, project, cmd, dryRun, format }) =>
77
+ ({ cmd, dryRun, format, profile }) =>
143
78
  Effect.gen(function* () {
144
- const projectName = project ? Option.getOrUndefined(project) : undefined;
79
+ const az = yield* AzService;
80
+ const profileName = Option.getOrUndefined(profile);
145
81
 
146
82
  if (dryRun) {
147
- const fullCommand = `az ${cmd}`;
148
- yield* logText(`[DRY-RUN] Would execute: ${fullCommand}`);
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 az = yield* AzService;
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 raw az CLI commands directly.
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
- agent-tools-az cmd --cmd "pipelines list"
175
- agent-tools-az cmd --cmd "repos list" --project my-project
176
- agent-tools-az cmd --cmd "pipelines runs list --output json"`,
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 with subcommands
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 CLI Tool for Coding Agents (READ-ONLY)
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 build subcommands:
191
- az-tool build timeline --build-id 123
192
- az-tool build failed-jobs --build-id 123
193
- az-tool build logs --build-id 123
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 "pipelines list"`,
125
+ az-tool cmd --cmd "webapp list"`,
199
126
  ),
200
- Command.withSubcommands([buildCommand, cmdCommand, commandsCommand]),
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
+ }