@blogic-cz/agent-tools 0.14.41 → 0.14.43
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 +1 -1
- package/src/audit-tool/index.ts +10 -2
- package/src/az-tool/index.ts +11 -2
- package/src/db-tool/index.ts +13 -3
- package/src/db-tool/service.ts +1 -3
- package/src/gh-tool/index.ts +6 -1
- package/src/gh-tool/pr/commands.ts +96 -10
- package/src/gh-tool/pr/core.ts +36 -1
- package/src/gh-tool/pr/index.ts +1 -0
- package/src/k8s-tool/index.ts +10 -1
- package/src/logs-tool/index.ts +10 -2
- package/src/observability-tool/index.ts +4 -2
- package/src/session-tool/index.ts +4 -2
- package/src/shared/index.ts +2 -0
- package/src/shared/schema-dump.ts +94 -0
package/package.json
CHANGED
package/src/audit-tool/index.ts
CHANGED
|
@@ -3,7 +3,13 @@ import { Command, Flag } from "effect/unstable/cli";
|
|
|
3
3
|
import { BunRuntime, BunServices } from "@effect/platform-bun";
|
|
4
4
|
import { Console, Effect, Layer, Option } from "effect";
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
makeSchemaCommand,
|
|
8
|
+
formatOption,
|
|
9
|
+
formatOutput,
|
|
10
|
+
renderCauseToStderr,
|
|
11
|
+
VERSION,
|
|
12
|
+
} from "#shared";
|
|
7
13
|
import { AuditService, AuditServiceLayer, withAudit } from "#shared/audit";
|
|
8
14
|
|
|
9
15
|
type AuditToolResult<T> = {
|
|
@@ -81,9 +87,11 @@ const purgeCommand = Command.make(
|
|
|
81
87
|
}),
|
|
82
88
|
).pipe(Command.withDescription("Delete old audit log entries"));
|
|
83
89
|
|
|
90
|
+
const commandsCommand = makeSchemaCommand(() => mainCommand);
|
|
91
|
+
|
|
84
92
|
const mainCommand = Command.make("audit-tool", {}).pipe(
|
|
85
93
|
Command.withDescription("Audit log inspection and maintenance for agent-tools"),
|
|
86
|
-
Command.withSubcommands([listCommand, purgeCommand]),
|
|
94
|
+
Command.withSubcommands([listCommand, purgeCommand, commandsCommand]),
|
|
87
95
|
);
|
|
88
96
|
|
|
89
97
|
const cli = Command.run(mainCommand, {
|
package/src/az-tool/index.ts
CHANGED
|
@@ -3,7 +3,14 @@ import { Command, Flag } from "effect/unstable/cli";
|
|
|
3
3
|
import { BunRuntime, BunServices } from "@effect/platform-bun";
|
|
4
4
|
import { Console, Effect, Layer, Option } from "effect";
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
makeSchemaCommand,
|
|
8
|
+
formatAny,
|
|
9
|
+
formatOption,
|
|
10
|
+
formatOutput,
|
|
11
|
+
renderCauseToStderr,
|
|
12
|
+
VERSION,
|
|
13
|
+
} from "#shared";
|
|
7
14
|
import { AuditServiceLayer, withAudit } from "#shared/audit";
|
|
8
15
|
import {
|
|
9
16
|
findFailedJobs,
|
|
@@ -173,6 +180,8 @@ EXAMPLES:
|
|
|
173
180
|
// Main command with subcommands
|
|
174
181
|
// ---------------------------------------------------------------------------
|
|
175
182
|
|
|
183
|
+
const commandsCommand = makeSchemaCommand(() => mainCommand);
|
|
184
|
+
|
|
176
185
|
const mainCommand = Command.make("az-tool", {}).pipe(
|
|
177
186
|
Command.withDescription(
|
|
178
187
|
`Azure CLI Tool for Coding Agents (READ-ONLY)
|
|
@@ -187,7 +196,7 @@ Typed build subcommands:
|
|
|
187
196
|
Raw az wrapper:
|
|
188
197
|
az-tool cmd --cmd "pipelines list"`,
|
|
189
198
|
),
|
|
190
|
-
Command.withSubcommands([buildCommand, cmdCommand]),
|
|
199
|
+
Command.withSubcommands([buildCommand, cmdCommand, commandsCommand]),
|
|
191
200
|
);
|
|
192
201
|
|
|
193
202
|
const cli = Command.run(mainCommand, {
|
package/src/db-tool/index.ts
CHANGED
|
@@ -5,7 +5,13 @@ import { Console, Effect, Layer, Option } from "effect";
|
|
|
5
5
|
|
|
6
6
|
import type { SchemaMode } from "./types";
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
makeSchemaCommand,
|
|
10
|
+
formatOption,
|
|
11
|
+
formatOutput,
|
|
12
|
+
renderCauseToStderr,
|
|
13
|
+
VERSION,
|
|
14
|
+
} from "#shared";
|
|
9
15
|
import { AuditServiceLayer, withAudit } from "#shared/audit";
|
|
10
16
|
import { ConfigService, ConfigServiceLayer, getDefaultEnvironment } from "#config";
|
|
11
17
|
import { DbConfigService, makeDbConfigLayer } from "./config-service";
|
|
@@ -133,9 +139,11 @@ const envsCommand = Command.make(
|
|
|
133
139
|
Command.withDescription("List configured database environments and the default (no network)"),
|
|
134
140
|
);
|
|
135
141
|
|
|
142
|
+
const commandsCommand = makeSchemaCommand(() => mainCommand);
|
|
143
|
+
|
|
136
144
|
const mainCommand = Command.make("db-tool", {}).pipe(
|
|
137
145
|
Command.withDescription("Database Query Tool for Coding Agents"),
|
|
138
|
-
Command.withSubcommands([sqlCommand, schemaCommand, envsCommand]),
|
|
146
|
+
Command.withSubcommands([sqlCommand, schemaCommand, envsCommand, commandsCommand]),
|
|
139
147
|
);
|
|
140
148
|
|
|
141
149
|
const cli = Command.run(mainCommand, {
|
|
@@ -145,7 +153,9 @@ const cli = Command.run(mainCommand, {
|
|
|
145
153
|
const dbConfigLayer = makeDbConfigLayer(profileArg);
|
|
146
154
|
|
|
147
155
|
const MainLayer = DbService.layer.pipe(
|
|
148
|
-
|
|
156
|
+
// provideMerge (not provide) so DbConfigService stays in the program context for the `envs` command,
|
|
157
|
+
// which reads it directly rather than going through DbService.
|
|
158
|
+
Layer.provideMerge(dbConfigLayer),
|
|
149
159
|
Layer.provideMerge(ConfigServiceLayer),
|
|
150
160
|
Layer.provideMerge(BunServices.layer),
|
|
151
161
|
Layer.provideMerge(AuditServiceLayer),
|
package/src/db-tool/service.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { resolveEnvTemplate } from "#shared/env-template";
|
|
|
9
9
|
import { resolveEnvironmentScopedPrerequisites } from "#shared/prerequisites/config";
|
|
10
10
|
import { runWithProfilePrerequisites } from "#shared/prerequisites/runtime";
|
|
11
11
|
import { buildApiProbeArgs } from "#shared/k8s-probe";
|
|
12
|
-
import { DbConfigService,
|
|
12
|
+
import { DbConfigService, TUNNEL_CHECK_INTERVAL_MS } from "./config-service";
|
|
13
13
|
import {
|
|
14
14
|
DbConnectionError,
|
|
15
15
|
DbMutationBlockedError,
|
|
@@ -829,5 +829,3 @@ export class DbService extends Context.Service<
|
|
|
829
829
|
),
|
|
830
830
|
);
|
|
831
831
|
}
|
|
832
|
-
|
|
833
|
-
export const DbServiceLayer = DbService.layer.pipe(Layer.provide(DbConfigServiceLayer));
|
package/src/gh-tool/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { Command } from "effect/unstable/cli";
|
|
|
3
3
|
import { BunRuntime, BunServices } from "@effect/platform-bun";
|
|
4
4
|
import { Effect, Layer } from "effect";
|
|
5
5
|
|
|
6
|
-
import { renderCauseToStderr, VERSION } from "#shared";
|
|
6
|
+
import { makeSchemaCommand, renderCauseToStderr, VERSION } from "#shared";
|
|
7
7
|
import { AuditServiceLayer, withAudit } from "#shared/audit";
|
|
8
8
|
import { ConfigServiceLayer } from "#config";
|
|
9
9
|
import {
|
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
prReplyAndResolveCommand,
|
|
41
41
|
prReviewTriageBatchCommand,
|
|
42
42
|
prReviewTriageCommand,
|
|
43
|
+
prWaitMergeableCommand,
|
|
43
44
|
} from "./pr/index";
|
|
44
45
|
import { branchRenameCommand } from "./branch";
|
|
45
46
|
import {
|
|
@@ -74,6 +75,7 @@ const prCommand = Command.make("pr", {}).pipe(
|
|
|
74
75
|
prCloseCommand,
|
|
75
76
|
prEditCommand,
|
|
76
77
|
prMergeCommand,
|
|
78
|
+
prWaitMergeableCommand,
|
|
77
79
|
prThreadsCommand,
|
|
78
80
|
prCommentsCommand,
|
|
79
81
|
prIssueCommentsCommand,
|
|
@@ -148,6 +150,8 @@ const releaseCommand = Command.make("release", {}).pipe(
|
|
|
148
150
|
]),
|
|
149
151
|
);
|
|
150
152
|
|
|
153
|
+
const commandsCommand = makeSchemaCommand(() => mainCommand);
|
|
154
|
+
|
|
151
155
|
const mainCommand = Command.make("gh-tool", {}).pipe(
|
|
152
156
|
Command.withDescription(
|
|
153
157
|
`GitHub CLI Tool for Coding Agents
|
|
@@ -186,6 +190,7 @@ WORKFLOW FOR AI AGENTS:
|
|
|
186
190
|
branchCommand,
|
|
187
191
|
workflowCommand,
|
|
188
192
|
releaseCommand,
|
|
193
|
+
commandsCommand,
|
|
189
194
|
]),
|
|
190
195
|
);
|
|
191
196
|
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import { Command, Flag } from "effect/unstable/cli";
|
|
2
|
-
import { Effect, Option } from "effect";
|
|
2
|
+
import { Console, Effect, Option } from "effect";
|
|
3
3
|
|
|
4
4
|
import type { CheckResult, PRStatusResult } from "#gh/types";
|
|
5
5
|
|
|
6
6
|
import { formatOption, logFormatted } from "#shared";
|
|
7
7
|
import { GitHubService } from "#gh/service";
|
|
8
|
+
import { GitHubCommandError } from "#gh/errors";
|
|
9
|
+
|
|
10
|
+
const emptyBatchError = (batch: string) =>
|
|
11
|
+
new GitHubCommandError({
|
|
12
|
+
message: `--prs received no valid PR numbers: ${JSON.stringify(batch)}`,
|
|
13
|
+
command: "gh pr --prs",
|
|
14
|
+
exitCode: 1,
|
|
15
|
+
stderr: "",
|
|
16
|
+
hint: "Pass comma-separated positive integers, e.g. --prs 1,2,3.",
|
|
17
|
+
});
|
|
8
18
|
import {
|
|
9
19
|
resolveDefaultTextInput,
|
|
10
20
|
resolveOptionalTextInput,
|
|
@@ -29,6 +39,7 @@ import {
|
|
|
29
39
|
mergePR,
|
|
30
40
|
rerunChecks,
|
|
31
41
|
viewPR,
|
|
42
|
+
waitForMergeable,
|
|
32
43
|
} from "./core";
|
|
33
44
|
import {
|
|
34
45
|
fetchComments,
|
|
@@ -127,18 +138,34 @@ export const prViewCommand = Command.make(
|
|
|
127
138
|
Flag.withDescription("PR number (default: current branch PR)"),
|
|
128
139
|
Flag.optional,
|
|
129
140
|
),
|
|
141
|
+
prs: Flag.string("prs").pipe(
|
|
142
|
+
Flag.withDescription("Comma-separated PR numbers to view in one call (overrides --pr)"),
|
|
143
|
+
Flag.optional,
|
|
144
|
+
),
|
|
130
145
|
repo: repoOption,
|
|
131
146
|
},
|
|
132
|
-
({ format, pr, repo }) =>
|
|
147
|
+
({ format, pr, prs, repo }) =>
|
|
133
148
|
withRepo(
|
|
134
149
|
repo,
|
|
135
150
|
Effect.gen(function* () {
|
|
136
|
-
const
|
|
137
|
-
|
|
151
|
+
const batch = Option.getOrNull(prs);
|
|
152
|
+
if (batch !== null) {
|
|
153
|
+
const numbers = parsePrNumbers(batch);
|
|
154
|
+
if (numbers.length === 0) return yield* emptyBatchError(batch);
|
|
155
|
+
const results = yield* Effect.all(
|
|
156
|
+
numbers.map((n) => viewPR(n).pipe(Effect.map((info) => ({ pr: n, info })))),
|
|
157
|
+
{ concurrency: 5 },
|
|
158
|
+
);
|
|
159
|
+
yield* logFormatted({ count: results.length, prs: results }, format);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const info = yield* viewPR(Option.getOrNull(pr));
|
|
138
163
|
yield* logFormatted(info, format);
|
|
139
164
|
}),
|
|
140
165
|
),
|
|
141
|
-
).pipe(
|
|
166
|
+
).pipe(
|
|
167
|
+
Command.withDescription("View PR information (use --prs 1,2,3 to view several in one call)"),
|
|
168
|
+
);
|
|
142
169
|
|
|
143
170
|
export const prStatusCommand = Command.make(
|
|
144
171
|
"status",
|
|
@@ -200,6 +227,36 @@ export const prListCommand = Command.make(
|
|
|
200
227
|
),
|
|
201
228
|
);
|
|
202
229
|
|
|
230
|
+
export const prWaitMergeableCommand = Command.make(
|
|
231
|
+
"wait-mergeable",
|
|
232
|
+
{
|
|
233
|
+
format: formatOption,
|
|
234
|
+
pr: Flag.integer("pr").pipe(
|
|
235
|
+
Flag.withDescription("PR number (default: current branch PR)"),
|
|
236
|
+
Flag.optional,
|
|
237
|
+
),
|
|
238
|
+
timeout: Flag.integer("timeout").pipe(
|
|
239
|
+
Flag.withDescription(
|
|
240
|
+
"Max seconds to wait for a definitive mergeable verdict (capped at 180)",
|
|
241
|
+
),
|
|
242
|
+
Flag.withDefault(60),
|
|
243
|
+
),
|
|
244
|
+
repo: repoOption,
|
|
245
|
+
},
|
|
246
|
+
({ format, pr, timeout, repo }) =>
|
|
247
|
+
withRepo(
|
|
248
|
+
repo,
|
|
249
|
+
Effect.gen(function* () {
|
|
250
|
+
const info = yield* waitForMergeable(Option.getOrNull(pr), timeout);
|
|
251
|
+
yield* logFormatted(info, format);
|
|
252
|
+
}),
|
|
253
|
+
),
|
|
254
|
+
).pipe(
|
|
255
|
+
Command.withDescription(
|
|
256
|
+
"Poll until GitHub reports a definitive mergeable verdict (MERGEABLE/CONFLICTING) or timeout",
|
|
257
|
+
),
|
|
258
|
+
);
|
|
259
|
+
|
|
203
260
|
export const prCreateCommand = Command.make(
|
|
204
261
|
"create",
|
|
205
262
|
{
|
|
@@ -388,6 +445,10 @@ export const prChecksCommand = Command.make(
|
|
|
388
445
|
Flag.withDescription("PR number (default: current branch PR)"),
|
|
389
446
|
Flag.optional,
|
|
390
447
|
),
|
|
448
|
+
prs: Flag.string("prs").pipe(
|
|
449
|
+
Flag.withDescription("Comma-separated PR numbers for a one-shot batch snapshot (no --watch)"),
|
|
450
|
+
Flag.optional,
|
|
451
|
+
),
|
|
391
452
|
repo: repoOption,
|
|
392
453
|
timeout: Flag.integer("timeout").pipe(
|
|
393
454
|
Flag.withDefault(CI_CHECK_WATCH_TIMEOUT_MS / 1000),
|
|
@@ -398,16 +459,39 @@ export const prChecksCommand = Command.make(
|
|
|
398
459
|
Flag.withDescription("Watch until checks complete or timeout"),
|
|
399
460
|
),
|
|
400
461
|
},
|
|
401
|
-
({ failFast, format, pr, repo, timeout, watch }) =>
|
|
462
|
+
({ failFast, format, pr, prs, repo, timeout, watch }) =>
|
|
402
463
|
withRepo(
|
|
403
464
|
repo,
|
|
404
465
|
Effect.gen(function* () {
|
|
405
|
-
const
|
|
406
|
-
|
|
466
|
+
const batch = Option.getOrNull(prs);
|
|
467
|
+
if (batch !== null) {
|
|
468
|
+
if (watch) {
|
|
469
|
+
yield* Console.warn(
|
|
470
|
+
"ℹ️ --watch is ignored with --prs; batch mode returns a one-shot snapshot per PR.",
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
const numbers = parsePrNumbers(batch);
|
|
474
|
+
if (numbers.length === 0) return yield* emptyBatchError(batch);
|
|
475
|
+
const results = yield* Effect.all(
|
|
476
|
+
numbers.map((n) =>
|
|
477
|
+
fetchChecks(n, false, failFast, timeout).pipe(
|
|
478
|
+
Effect.map((checks) => ({ pr: n, checks })),
|
|
479
|
+
),
|
|
480
|
+
),
|
|
481
|
+
{ concurrency: 5 },
|
|
482
|
+
);
|
|
483
|
+
yield* logFormatted({ count: results.length, prs: results }, format);
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
const checks = yield* fetchChecksForCommand(Option.getOrNull(pr), watch, failFast, timeout);
|
|
407
487
|
yield* logFormatted(checks, format);
|
|
408
488
|
}),
|
|
409
489
|
),
|
|
410
|
-
).pipe(
|
|
490
|
+
).pipe(
|
|
491
|
+
Command.withDescription(
|
|
492
|
+
"Fetch CI check status for a PR (--watch to block; --prs 1,2,3 for a batch snapshot)",
|
|
493
|
+
),
|
|
494
|
+
);
|
|
411
495
|
|
|
412
496
|
export const prChecksFailedCommand = Command.make(
|
|
413
497
|
"checks-failed",
|
|
@@ -792,8 +876,10 @@ export const prReviewTriageBatchCommand = Command.make(
|
|
|
792
876
|
withRepo(
|
|
793
877
|
repo,
|
|
794
878
|
Effect.gen(function* () {
|
|
879
|
+
const numbers = parsePrNumbers(prs);
|
|
880
|
+
if (numbers.length === 0) return yield* emptyBatchError(prs);
|
|
795
881
|
const results = yield* Effect.all(
|
|
796
|
-
|
|
882
|
+
numbers.map((prNumber) => fetchReviewTriage(prNumber)),
|
|
797
883
|
{ concurrency: "unbounded" },
|
|
798
884
|
);
|
|
799
885
|
yield* logFormatted(results, format);
|
package/src/gh-tool/pr/core.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Console, Effect, Option, Result } from "effect";
|
|
1
|
+
import { Clock, Console, Duration, Effect, Option, Result } from "effect";
|
|
2
2
|
|
|
3
3
|
import type {
|
|
4
4
|
BranchPRDetail,
|
|
@@ -482,6 +482,41 @@ export const listPRs = Effect.fn("pr.listPRs")(function* (opts: {
|
|
|
482
482
|
return yield* gh.runGhJson<PRInfo[]>(args);
|
|
483
483
|
});
|
|
484
484
|
|
|
485
|
+
// GitHub recomputes mergeability asynchronously after CI; poll until it settles out of "UNKNOWN".
|
|
486
|
+
const MAX_MERGEABLE_WAIT_SECONDS = 180;
|
|
487
|
+
const MERGEABLE_POLL_INTERVAL_MS = 3000;
|
|
488
|
+
|
|
489
|
+
export const waitForMergeable = Effect.fn("pr.waitForMergeable")(function* (
|
|
490
|
+
pr: number | null,
|
|
491
|
+
timeoutSeconds: number,
|
|
492
|
+
) {
|
|
493
|
+
const cappedSeconds = Math.min(timeoutSeconds, MAX_MERGEABLE_WAIT_SECONDS);
|
|
494
|
+
const start = yield* Clock.currentTimeMillis;
|
|
495
|
+
const deadlineMs = Number(start) + cappedSeconds * 1000;
|
|
496
|
+
|
|
497
|
+
// Effect.whileLoop (not recursion) so TestClock.adjust can advance Effect.sleep without real waits.
|
|
498
|
+
let latest = yield* viewPR(pr);
|
|
499
|
+
let timedOut = false;
|
|
500
|
+
yield* Effect.whileLoop({
|
|
501
|
+
while: () => latest.mergeable === "UNKNOWN" && !timedOut,
|
|
502
|
+
body: () =>
|
|
503
|
+
Effect.gen(function* () {
|
|
504
|
+
const now = yield* Clock.currentTimeMillis;
|
|
505
|
+
if (Number(now) >= deadlineMs) {
|
|
506
|
+
timedOut = true;
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
// Cap the sleep to the remaining budget so the total wait doesn't overshoot the deadline.
|
|
510
|
+
const remaining = deadlineMs - Number(now);
|
|
511
|
+
yield* Effect.sleep(Duration.millis(Math.min(MERGEABLE_POLL_INTERVAL_MS, remaining)));
|
|
512
|
+
latest = yield* viewPR(pr);
|
|
513
|
+
}),
|
|
514
|
+
step: () => undefined,
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
return latest;
|
|
518
|
+
});
|
|
519
|
+
|
|
485
520
|
export const createPR = Effect.fn("pr.createPR")(function* (opts: {
|
|
486
521
|
base: string | null;
|
|
487
522
|
title: string;
|
package/src/gh-tool/pr/index.ts
CHANGED
package/src/k8s-tool/index.ts
CHANGED
|
@@ -5,7 +5,13 @@ import { Console, Effect, Layer, Option } from "effect";
|
|
|
5
5
|
|
|
6
6
|
import type { CommandResult } from "./types";
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
makeSchemaCommand,
|
|
10
|
+
formatOption,
|
|
11
|
+
formatOutput,
|
|
12
|
+
renderCauseToStderr,
|
|
13
|
+
VERSION,
|
|
14
|
+
} from "#shared";
|
|
9
15
|
import { AuditServiceLayer, withAudit } from "#shared/audit";
|
|
10
16
|
import { K8sService, K8sServiceLayer } from "./service";
|
|
11
17
|
import { ConfigService, ConfigServiceLayer, getDefaultEnvironment, getToolConfig } from "#config";
|
|
@@ -415,6 +421,8 @@ const topCommand = Command.make(
|
|
|
415
421
|
}),
|
|
416
422
|
).pipe(Command.withDescription("Show pod CPU/memory usage (kubectl top pod)"));
|
|
417
423
|
|
|
424
|
+
const commandsCommand = makeSchemaCommand(() => mainCommand);
|
|
425
|
+
|
|
418
426
|
const mainCommand = Command.make("k8s-tool", {}).pipe(
|
|
419
427
|
Command.withDescription("Kubernetes CLI Tool for Coding Agents"),
|
|
420
428
|
Command.withSubcommands([
|
|
@@ -424,6 +432,7 @@ const mainCommand = Command.make("k8s-tool", {}).pipe(
|
|
|
424
432
|
describeCommand,
|
|
425
433
|
execCommand,
|
|
426
434
|
topCommand,
|
|
435
|
+
commandsCommand,
|
|
427
436
|
]),
|
|
428
437
|
);
|
|
429
438
|
|
package/src/logs-tool/index.ts
CHANGED
|
@@ -20,7 +20,13 @@ import { Console, Effect, Layer, Option, Result } from "effect";
|
|
|
20
20
|
|
|
21
21
|
import type { Environment, LogResult, ReadOptions } from "./types";
|
|
22
22
|
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
makeSchemaCommand,
|
|
25
|
+
formatOption,
|
|
26
|
+
formatOutput,
|
|
27
|
+
renderCauseToStderr,
|
|
28
|
+
VERSION,
|
|
29
|
+
} from "#shared";
|
|
24
30
|
import { AuditServiceLayer, withAudit } from "#shared/audit";
|
|
25
31
|
import { ConfigService, ConfigServiceLayer, getDefaultEnvironment } from "#config";
|
|
26
32
|
import { LogsConfigError, LogsNotFoundError, LogsReadError, LogsTimeoutError } from "./errors";
|
|
@@ -214,9 +220,11 @@ const readCommand = Command.make(
|
|
|
214
220
|
}),
|
|
215
221
|
).pipe(Command.withDescription("Read application logs"));
|
|
216
222
|
|
|
223
|
+
const commandsCommand = makeSchemaCommand(() => mainCommand);
|
|
224
|
+
|
|
217
225
|
const mainCommand = Command.make("logs-tool", {}).pipe(
|
|
218
226
|
Command.withDescription("Application Logs Tool for Coding Agents"),
|
|
219
|
-
Command.withSubcommands([listCommand, readCommand]),
|
|
227
|
+
Command.withSubcommands([listCommand, readCommand, commandsCommand]),
|
|
220
228
|
);
|
|
221
229
|
|
|
222
230
|
const cli = Command.run(mainCommand, {
|
|
@@ -7,7 +7,7 @@ import { Command } from "effect/unstable/cli";
|
|
|
7
7
|
|
|
8
8
|
import { ConfigServiceLayer } from "#config";
|
|
9
9
|
import { AuditServiceLayer, withAudit } from "#shared/audit";
|
|
10
|
-
import { VERSION } from "#shared";
|
|
10
|
+
import { makeSchemaCommand, VERSION } from "#shared";
|
|
11
11
|
|
|
12
12
|
import { metricsCommand } from "./metrics";
|
|
13
13
|
import { logsCommand } from "./logs";
|
|
@@ -15,11 +15,13 @@ import { traceCommand } from "./trace";
|
|
|
15
15
|
|
|
16
16
|
const renderCauseToStderr = (cause: Cause.Cause<unknown>) => Console.error(cause.toString());
|
|
17
17
|
|
|
18
|
+
const commandsCommand = makeSchemaCommand(() => mainCommand);
|
|
19
|
+
|
|
18
20
|
const mainCommand = Command.make("observability-tool", {}).pipe(
|
|
19
21
|
Command.withDescription(
|
|
20
22
|
"LGTM observability queries — Tempo traces, Loki logs, Prometheus metrics",
|
|
21
23
|
),
|
|
22
|
-
Command.withSubcommands([traceCommand, metricsCommand, logsCommand]),
|
|
24
|
+
Command.withSubcommands([traceCommand, metricsCommand, logsCommand, commandsCommand]),
|
|
23
25
|
);
|
|
24
26
|
|
|
25
27
|
const cli = Command.run(mainCommand, { version: VERSION });
|
|
@@ -13,7 +13,7 @@ import { Console, Effect, Layer, Result } from "effect";
|
|
|
13
13
|
|
|
14
14
|
import type { MessageSummary, SessionResult, SessionSource } from "./types";
|
|
15
15
|
|
|
16
|
-
import { formatOption, formatOutput, VERSION } from "#shared";
|
|
16
|
+
import { makeSchemaCommand, formatOption, formatOutput, VERSION } from "#shared";
|
|
17
17
|
import { AuditServiceLayer, withAudit } from "#shared/audit";
|
|
18
18
|
import { ResolvedPaths, ResolvedPathsLayer } from "./config";
|
|
19
19
|
import { SessionStorageNotFoundError } from "./errors";
|
|
@@ -289,9 +289,11 @@ const readCommand = Command.make(
|
|
|
289
289
|
}),
|
|
290
290
|
).pipe(Command.withDescription("Read all messages from a session"));
|
|
291
291
|
|
|
292
|
+
const commandsCommand = makeSchemaCommand(() => mainCommand);
|
|
293
|
+
|
|
292
294
|
const mainCommand = Command.make("session-tool", {}).pipe(
|
|
293
295
|
Command.withDescription("OpenCode session history tool"),
|
|
294
|
-
Command.withSubcommands([listCommand, readCommand, searchCommand]),
|
|
296
|
+
Command.withSubcommands([listCommand, readCommand, searchCommand, commandsCommand]),
|
|
295
297
|
);
|
|
296
298
|
|
|
297
299
|
const cli = Command.run(mainCommand, {
|
package/src/shared/index.ts
CHANGED
|
@@ -8,6 +8,8 @@ export { commonArgOptions, parseCommonArgs } from "./cli";
|
|
|
8
8
|
|
|
9
9
|
export { renderCauseToStderr } from "./error-renderer";
|
|
10
10
|
|
|
11
|
+
export { dumpCommandSchema, makeSchemaCommand, type CommandSchema } from "./schema-dump";
|
|
12
|
+
|
|
11
13
|
// eslint-disable-next-line import/no-relative-parent-imports -- package.json lives at project root, outside src/
|
|
12
14
|
import pkg from "../../package.json" with { type: "json" };
|
|
13
15
|
export const VERSION = pkg.version;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Command } from "effect/unstable/cli";
|
|
2
|
+
import { Option } from "effect";
|
|
3
|
+
|
|
4
|
+
import { formatOption, logFormatted } from "./format";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Machine-readable command-tree dump so an agent can discover the whole CLI surface in ONE call
|
|
8
|
+
* instead of repeated `--help` round-trips (the top remaining discovery friction in the usage audit).
|
|
9
|
+
*/
|
|
10
|
+
export type FlagSchema = {
|
|
11
|
+
readonly name: string;
|
|
12
|
+
readonly type?: string;
|
|
13
|
+
readonly choices?: readonly string[];
|
|
14
|
+
readonly description?: string;
|
|
15
|
+
readonly aliases?: readonly string[];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type CommandSchema = {
|
|
19
|
+
readonly name: string;
|
|
20
|
+
readonly description?: string;
|
|
21
|
+
readonly flags: readonly FlagSchema[];
|
|
22
|
+
readonly subcommands: readonly CommandSchema[];
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Leaf parameter from effect/unstable/cli; carries the user-facing flag name/description/type.
|
|
26
|
+
type SingleParam = {
|
|
27
|
+
readonly _tag: "Single";
|
|
28
|
+
readonly name: string;
|
|
29
|
+
readonly description: Option.Option<string>;
|
|
30
|
+
readonly aliases?: readonly string[];
|
|
31
|
+
readonly primitiveType?: { readonly _tag?: string; readonly choiceKeys?: readonly string[] };
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Minimal view of the (partly internal) Command runtime shape we walk. Public fields: name,
|
|
35
|
+
// description, subcommands; `config.flags` is internal but stable, read defensively.
|
|
36
|
+
type CommandNode = {
|
|
37
|
+
readonly name: string;
|
|
38
|
+
readonly description?: string;
|
|
39
|
+
readonly subcommands?: ReadonlyArray<{ readonly commands?: ReadonlyArray<unknown> }>;
|
|
40
|
+
readonly config?: { readonly flags?: ReadonlyArray<unknown> };
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const isObject = (value: unknown): value is Record<string, unknown> =>
|
|
44
|
+
typeof value === "object" && value !== null;
|
|
45
|
+
|
|
46
|
+
// Flags wrap as Optional/Map/Transform/Variadic around a leaf Single; descend via `.param`.
|
|
47
|
+
const unwrapToSingle = (param: unknown): SingleParam | undefined => {
|
|
48
|
+
let current: unknown = param;
|
|
49
|
+
for (let depth = 0; depth < 16 && isObject(current); depth++) {
|
|
50
|
+
if (current._tag === "Single" && typeof current.name === "string") {
|
|
51
|
+
return current as unknown as SingleParam;
|
|
52
|
+
}
|
|
53
|
+
current = current.param;
|
|
54
|
+
}
|
|
55
|
+
return undefined;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const flagToSchema = (param: unknown): FlagSchema | undefined => {
|
|
59
|
+
const single = unwrapToSingle(param);
|
|
60
|
+
if (!single) return undefined;
|
|
61
|
+
const primitive = single.primitiveType;
|
|
62
|
+
const choices = primitive?.choiceKeys;
|
|
63
|
+
return {
|
|
64
|
+
name: single.name,
|
|
65
|
+
type: primitive?._tag,
|
|
66
|
+
choices: choices && choices.length > 0 ? choices : undefined,
|
|
67
|
+
description: Option.getOrUndefined(single.description),
|
|
68
|
+
aliases: single.aliases && single.aliases.length > 0 ? single.aliases : undefined,
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const dumpCommandSchema = (command: unknown): CommandSchema => {
|
|
73
|
+
const node = command as CommandNode;
|
|
74
|
+
const flags = (node.config?.flags ?? [])
|
|
75
|
+
.map(flagToSchema)
|
|
76
|
+
.filter((flag): flag is FlagSchema => flag !== undefined);
|
|
77
|
+
const subcommands = (node.subcommands ?? [])
|
|
78
|
+
.flatMap((group) => group.commands ?? [])
|
|
79
|
+
.map(dumpCommandSchema);
|
|
80
|
+
return { name: node.name, description: node.description, flags, subcommands };
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Builds the `commands` subcommand for a tool. Pass a thunk returning the tool's root command so the
|
|
85
|
+
* dump reflects the fully-assembled tree (the root is defined after its subcommands, incl. this one).
|
|
86
|
+
*/
|
|
87
|
+
export const makeSchemaCommand = (getRoot: () => unknown) =>
|
|
88
|
+
Command.make("commands", { format: formatOption }, ({ format }) =>
|
|
89
|
+
logFormatted(dumpCommandSchema(getRoot()), format),
|
|
90
|
+
).pipe(
|
|
91
|
+
Command.withDescription(
|
|
92
|
+
"Dump the full command tree (names, descriptions, flags, types) as structured output — fetch once instead of repeated --help.",
|
|
93
|
+
),
|
|
94
|
+
);
|