@blogic-cz/agent-tools 0.14.42 → 0.14.44
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 +5 -5
- 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 +4 -1
- package/src/gh-tool/pr/commands.ts +8 -2
- package/src/gh-tool/pr/core.ts +5 -7
- package/src/gh-tool/workflow.ts +23 -8
- 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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blogic-cz/agent-tools",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.44",
|
|
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",
|
|
@@ -137,13 +137,13 @@
|
|
|
137
137
|
"test": "vitest run"
|
|
138
138
|
},
|
|
139
139
|
"dependencies": {
|
|
140
|
-
"@effect/platform-bun": "4.0.0-beta.
|
|
140
|
+
"@effect/platform-bun": "4.0.0-beta.90",
|
|
141
141
|
"@toon-format/toon": "2.1.0",
|
|
142
|
-
"effect": "4.0.0-beta.
|
|
142
|
+
"effect": "4.0.0-beta.90"
|
|
143
143
|
},
|
|
144
144
|
"devDependencies": {
|
|
145
145
|
"@effect/language-service": "0.86.2",
|
|
146
|
-
"@effect/vitest": "4.0.0-beta.
|
|
146
|
+
"@effect/vitest": "4.0.0-beta.90",
|
|
147
147
|
"@types/bun": "1.3.12",
|
|
148
148
|
"oxfmt": "0.44.0",
|
|
149
149
|
"oxlint": "1.59.0",
|
|
@@ -151,7 +151,7 @@
|
|
|
151
151
|
"vitest": "^4.1.4"
|
|
152
152
|
},
|
|
153
153
|
"overrides": {
|
|
154
|
-
"@effect/platform-node-shared": "4.0.0-beta.
|
|
154
|
+
"@effect/platform-node-shared": "4.0.0-beta.90"
|
|
155
155
|
},
|
|
156
156
|
"engines": {
|
|
157
157
|
"bun": ">=1.0.0"
|
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 {
|
|
@@ -150,6 +150,8 @@ const releaseCommand = Command.make("release", {}).pipe(
|
|
|
150
150
|
]),
|
|
151
151
|
);
|
|
152
152
|
|
|
153
|
+
const commandsCommand = makeSchemaCommand(() => mainCommand);
|
|
154
|
+
|
|
153
155
|
const mainCommand = Command.make("gh-tool", {}).pipe(
|
|
154
156
|
Command.withDescription(
|
|
155
157
|
`GitHub CLI Tool for Coding Agents
|
|
@@ -188,6 +190,7 @@ WORKFLOW FOR AI AGENTS:
|
|
|
188
190
|
branchCommand,
|
|
189
191
|
workflowCommand,
|
|
190
192
|
releaseCommand,
|
|
193
|
+
commandsCommand,
|
|
191
194
|
]),
|
|
192
195
|
);
|
|
193
196
|
|
|
@@ -452,7 +452,11 @@ export const prChecksCommand = Command.make(
|
|
|
452
452
|
repo: repoOption,
|
|
453
453
|
timeout: Flag.integer("timeout").pipe(
|
|
454
454
|
Flag.withDefault(CI_CHECK_WATCH_TIMEOUT_MS / 1000),
|
|
455
|
-
Flag.withDescription("Timeout in seconds for watch mode (default: 600)"),
|
|
455
|
+
Flag.withDescription("Timeout in seconds for watch mode (default: 600, minimum 1)"),
|
|
456
|
+
Flag.filter(
|
|
457
|
+
(n) => n >= 1,
|
|
458
|
+
() => "--timeout must be at least 1 second",
|
|
459
|
+
),
|
|
456
460
|
),
|
|
457
461
|
watch: Flag.boolean("watch").pipe(
|
|
458
462
|
Flag.withDefault(false),
|
|
@@ -876,8 +880,10 @@ export const prReviewTriageBatchCommand = Command.make(
|
|
|
876
880
|
withRepo(
|
|
877
881
|
repo,
|
|
878
882
|
Effect.gen(function* () {
|
|
883
|
+
const numbers = parsePrNumbers(prs);
|
|
884
|
+
if (numbers.length === 0) return yield* emptyBatchError(prs);
|
|
879
885
|
const results = yield* Effect.all(
|
|
880
|
-
|
|
886
|
+
numbers.map((prNumber) => fetchReviewTriage(prNumber)),
|
|
881
887
|
{ concurrency: "unbounded" },
|
|
882
888
|
);
|
|
883
889
|
yield* logFormatted(results, format);
|
package/src/gh-tool/pr/core.ts
CHANGED
|
@@ -20,9 +20,6 @@ import { runLocalCommand } from "./helpers";
|
|
|
20
20
|
|
|
21
21
|
const CHECK_JSON_FIELDS = "name,state,bucket,link";
|
|
22
22
|
const GITHUB_ACTIONS_RUN_ID_RE = /github\.com\/[^/]+\/[^/]+\/actions\/runs\/(\d+)/;
|
|
23
|
-
// A single blocking `--watch` is capped here so an agent never loses a whole turn to a 30-min
|
|
24
|
-
// foreground wait. On hitting the cap we return the partial snapshot, not a failure (H1).
|
|
25
|
-
const MAX_WATCH_SECONDS = 120;
|
|
26
23
|
|
|
27
24
|
const validatePRTitle = Effect.fn("pr.validatePRTitle")(function* (title: string) {
|
|
28
25
|
const gh = yield* GitHubService;
|
|
@@ -874,11 +871,12 @@ export const fetchChecks = Effect.fn("pr.fetchChecks")(function* (
|
|
|
874
871
|
watchArgs.push("--fail-fast");
|
|
875
872
|
}
|
|
876
873
|
|
|
877
|
-
//
|
|
878
|
-
|
|
874
|
+
// Block for the caller's requested --timeout (no artificial cap — blocking isn't the problem;
|
|
875
|
+
// --timeout is validated >= 1s at the CLI boundary). On timeout return a snapshot, never
|
|
876
|
+
// nothing — that was the actual token-wasting bug.
|
|
879
877
|
const watchOutcome = yield* gh.runGh(watchArgs).pipe(
|
|
880
878
|
Effect.timeoutOrElse({
|
|
881
|
-
duration:
|
|
879
|
+
duration: timeoutSeconds * 1000,
|
|
882
880
|
orElse: () => Effect.succeed(null),
|
|
883
881
|
}),
|
|
884
882
|
);
|
|
@@ -887,7 +885,7 @@ export const fetchChecks = Effect.fn("pr.fetchChecks")(function* (
|
|
|
887
885
|
if (watchOutcome === null && results.some((c) => c.bucket === "pending")) {
|
|
888
886
|
const pending = results.filter((c) => c.bucket === "pending").length;
|
|
889
887
|
yield* Console.warn(
|
|
890
|
-
`ℹ️ Watch
|
|
888
|
+
`ℹ️ Watch timed out after ${timeoutSeconds}s; ${pending} check(s) still pending (snapshot returned). ` +
|
|
891
889
|
`Re-run to keep watching:\n ${buildChecksCommand(pr, true)}`,
|
|
892
890
|
);
|
|
893
891
|
}
|
package/src/gh-tool/workflow.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Command, Flag } from "effect/unstable/cli";
|
|
|
2
2
|
import { Console, Effect, Option } from "effect";
|
|
3
3
|
|
|
4
4
|
import { formatOption, logFormatted } from "#shared";
|
|
5
|
+
import { CI_CHECK_WATCH_TIMEOUT_MS } from "#gh/config";
|
|
5
6
|
import { GitHubCommandError, GitHubNotFoundError } from "./errors";
|
|
6
7
|
import { GitHubService } from "./service";
|
|
7
8
|
import type { CheckRunAnnotation, JobAnnotations } from "./types";
|
|
@@ -212,11 +213,15 @@ const cancelRun = Effect.fn("workflow.cancelRun")(function* (runId: number, repo
|
|
|
212
213
|
};
|
|
213
214
|
});
|
|
214
215
|
|
|
215
|
-
// `gh run watch` has no native timeout
|
|
216
|
-
// fall back to a one-shot snapshot
|
|
217
|
-
const
|
|
216
|
+
// `gh run watch` has no native timeout (observed hanging 36 min). Block for the caller's --timeout,
|
|
217
|
+
// then fall back to a one-shot snapshot so a timeout never returns nothing.
|
|
218
|
+
const DEFAULT_WATCH_RUN_TIMEOUT_SECONDS = CI_CHECK_WATCH_TIMEOUT_MS / 1000;
|
|
218
219
|
|
|
219
|
-
const watchRun = Effect.fn("workflow.watchRun")(function* (
|
|
220
|
+
const watchRun = Effect.fn("workflow.watchRun")(function* (
|
|
221
|
+
runId: number,
|
|
222
|
+
repo: string | null,
|
|
223
|
+
timeoutSeconds: number,
|
|
224
|
+
) {
|
|
220
225
|
const gh = yield* GitHubService;
|
|
221
226
|
|
|
222
227
|
const watchArgs = ["run", "watch", String(runId), "--exit-status"];
|
|
@@ -237,7 +242,7 @@ const watchRun = Effect.fn("workflow.watchRun")(function* (runId: number, repo:
|
|
|
237
242
|
return Effect.fail(error);
|
|
238
243
|
}),
|
|
239
244
|
Effect.timeoutOrElse({
|
|
240
|
-
duration:
|
|
245
|
+
duration: timeoutSeconds * 1000,
|
|
241
246
|
orElse: () => Effect.succeed(null),
|
|
242
247
|
}),
|
|
243
248
|
);
|
|
@@ -255,7 +260,7 @@ const watchRun = Effect.fn("workflow.watchRun")(function* (runId: number, repo:
|
|
|
255
260
|
})),
|
|
256
261
|
watchOutput:
|
|
257
262
|
result === null
|
|
258
|
-
? `(watch
|
|
263
|
+
? `(watch timed out after ${timeoutSeconds}s; status taken from snapshot — re-run to keep watching)`
|
|
259
264
|
: result.stdout,
|
|
260
265
|
};
|
|
261
266
|
});
|
|
@@ -650,11 +655,21 @@ export const workflowWatchCommand = Command.make(
|
|
|
650
655
|
format: formatOption,
|
|
651
656
|
repo: repoOption,
|
|
652
657
|
run: Flag.integer("run").pipe(Flag.withDescription("Workflow run ID to watch")),
|
|
658
|
+
timeout: Flag.integer("timeout").pipe(
|
|
659
|
+
Flag.withDescription(
|
|
660
|
+
`Max seconds to block before returning a snapshot (default: ${DEFAULT_WATCH_RUN_TIMEOUT_SECONDS}, minimum 1)`,
|
|
661
|
+
),
|
|
662
|
+
Flag.withDefault(DEFAULT_WATCH_RUN_TIMEOUT_SECONDS),
|
|
663
|
+
Flag.filter(
|
|
664
|
+
(n) => n >= 1,
|
|
665
|
+
() => "--timeout must be at least 1 second",
|
|
666
|
+
),
|
|
667
|
+
),
|
|
653
668
|
},
|
|
654
|
-
({ format, repo, run }) =>
|
|
669
|
+
({ format, repo, run, timeout }) =>
|
|
655
670
|
Effect.gen(function* () {
|
|
656
671
|
const resolvedRepo = yield* resolveRepoArg(repo);
|
|
657
|
-
const result = yield* watchRun(run, resolvedRepo);
|
|
672
|
+
const result = yield* watchRun(run, resolvedRepo, timeout);
|
|
658
673
|
yield* logFormatted(result, format);
|
|
659
674
|
}),
|
|
660
675
|
).pipe(Command.withDescription("Watch a workflow run until it completes, then show final status"));
|
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
|
+
);
|