@blogic-cz/agent-tools 1.5.2 → 1.5.4

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 (60) hide show
  1. package/README.md +4 -4
  2. package/dist/audit-tool/main.d.ts +3 -0
  3. package/dist/audit-tool/main.d.ts.map +1 -0
  4. package/dist/az-tool/main.d.ts +3 -0
  5. package/dist/az-tool/main.d.ts.map +1 -0
  6. package/dist/azdo-tool/main.d.ts +3 -0
  7. package/dist/azdo-tool/main.d.ts.map +1 -0
  8. package/dist/credential-guard/index.d.ts.map +1 -1
  9. package/dist/db-tool/main.d.ts +3 -0
  10. package/dist/db-tool/main.d.ts.map +1 -0
  11. package/dist/gh-tool/api.d.ts.map +1 -1
  12. package/dist/gh-tool/gist.d.ts.map +1 -1
  13. package/dist/gh-tool/main.d.ts +3 -0
  14. package/dist/gh-tool/main.d.ts.map +1 -0
  15. package/dist/gh-tool/pr/core.d.ts.map +1 -1
  16. package/dist/gh-tool/service.d.ts.map +1 -1
  17. package/dist/gh-tool/text-input.d.ts +4 -0
  18. package/dist/gh-tool/text-input.d.ts.map +1 -1
  19. package/dist/gh-tool/workflow.d.ts.map +1 -1
  20. package/dist/k8s-tool/main.d.ts +3 -0
  21. package/dist/k8s-tool/main.d.ts.map +1 -0
  22. package/dist/logs-tool/index.d.ts +1 -19
  23. package/dist/logs-tool/index.d.ts.map +1 -1
  24. package/dist/logs-tool/main.d.ts +21 -0
  25. package/dist/logs-tool/main.d.ts.map +1 -0
  26. package/dist/observability-tool/main.d.ts +3 -0
  27. package/dist/observability-tool/main.d.ts.map +1 -0
  28. package/dist/session-tool/index.d.ts +1 -11
  29. package/dist/session-tool/index.d.ts.map +1 -1
  30. package/dist/session-tool/main.d.ts +13 -0
  31. package/dist/session-tool/main.d.ts.map +1 -0
  32. package/dist/shared/effect-version.d.ts +4 -0
  33. package/dist/shared/effect-version.d.ts.map +1 -0
  34. package/package.json +2 -2
  35. package/src/audit-tool/index.ts +3 -109
  36. package/src/audit-tool/main.ts +111 -0
  37. package/src/az-tool/index.ts +3 -151
  38. package/src/az-tool/main.ts +153 -0
  39. package/src/azdo-tool/index.ts +3 -220
  40. package/src/azdo-tool/main.ts +222 -0
  41. package/src/credential-guard/index.ts +102 -4
  42. package/src/db-tool/index.ts +3 -260
  43. package/src/db-tool/main.ts +262 -0
  44. package/src/gh-tool/api.ts +21 -1
  45. package/src/gh-tool/gist.ts +7 -1
  46. package/src/gh-tool/index.ts +3 -256
  47. package/src/gh-tool/main.ts +258 -0
  48. package/src/gh-tool/pr/core.ts +52 -2
  49. package/src/gh-tool/service.ts +9 -5
  50. package/src/gh-tool/text-input.ts +63 -4
  51. package/src/gh-tool/workflow.ts +1 -0
  52. package/src/k8s-tool/index.ts +3 -487
  53. package/src/k8s-tool/main.ts +489 -0
  54. package/src/logs-tool/index.ts +3 -247
  55. package/src/logs-tool/main.ts +249 -0
  56. package/src/observability-tool/index.ts +3 -34
  57. package/src/observability-tool/main.ts +36 -0
  58. package/src/session-tool/index.ts +3 -337
  59. package/src/session-tool/main.ts +339 -0
  60. package/src/shared/effect-version.ts +32 -0
@@ -0,0 +1,258 @@
1
+ #!/usr/bin/env bun
2
+ import { Command } from "effect/unstable/cli";
3
+ import { BunRuntime, BunServices } from "@effect/platform-bun";
4
+ import { Effect, Layer } from "effect";
5
+
6
+ import { makeSchemaCommand, renderCauseToStderr, VERSION } from "#shared";
7
+ import { AuditServiceLayer, withAudit } from "#shared/audit";
8
+ import { ConfigServiceLayer } from "#config";
9
+ import {
10
+ issueCloseCommand,
11
+ issueCommentCommand,
12
+ issueCommentsCommand,
13
+ issueCreateCommand,
14
+ issueEditCommand,
15
+ issueListCommand,
16
+ issueReopenCommand,
17
+ issueSnapshotBatchCommand,
18
+ issueTriageCommand,
19
+ issueViewCommand,
20
+ } from "./issue";
21
+ import {
22
+ prViewCommand,
23
+ prStatusCommand,
24
+ prListCommand,
25
+ prCreateCommand,
26
+ prCloseCommand,
27
+ prEditCommand,
28
+ prMergeCommand,
29
+ prReadyCommand,
30
+ prStackCommand,
31
+ prRequestReviewCommand,
32
+ prThreadsCommand,
33
+ prCommentsCommand,
34
+ prIssueCommentsCommand,
35
+ prIssueCommentsLatestCommand,
36
+ prCommentCommand,
37
+ prDiscussionSummaryCommand,
38
+ prFeedbackCommand,
39
+ prReplyCommand,
40
+ prResolveCommand,
41
+ prReviewCommand,
42
+ prReviewsCommand,
43
+ prLastHumanReviewerCommand,
44
+ prSubmitReviewCommand,
45
+ prChecksCommand,
46
+ prChecksFailedCommand,
47
+ prRerunChecksCommand,
48
+ prTriggerChecksCommand,
49
+ prReplyAndResolveCommand,
50
+ prReviewTriageBatchCommand,
51
+ prReviewTriageCommand,
52
+ prWaitMergeableCommand,
53
+ prWatchCommand,
54
+ } from "./pr/index";
55
+ import { branchRenameCommand } from "./branch";
56
+ import {
57
+ gistCreateCommand,
58
+ gistDeleteCommand,
59
+ gistEditCommand,
60
+ gistListCommand,
61
+ gistViewCommand,
62
+ } from "./gist";
63
+ import {
64
+ releaseCreateCommand,
65
+ releaseDeleteCommand,
66
+ releaseEditCommand,
67
+ releaseListCommand,
68
+ releaseStatusCommand,
69
+ releaseViewCommand,
70
+ } from "./release";
71
+ import { repoInfoCommand, repoListCommand, repoSearchCodeCommand } from "./repo";
72
+ import { GitHubService } from "./service";
73
+ import {
74
+ workflowAnnotationsCommand,
75
+ workflowCancelCommand,
76
+ workflowJobLogsCommand,
77
+ workflowJobsCommand,
78
+ workflowListCommand,
79
+ workflowLogsCommand,
80
+ workflowRerunCommand,
81
+ workflowRunCommand,
82
+ workflowViewCommand,
83
+ workflowWatchCommand,
84
+ } from "./workflow";
85
+
86
+ const prCommand = Command.make("pr", {}).pipe(
87
+ Command.withDescription("Pull request operations (view, create, merge, reviews, checks)"),
88
+ Command.withSubcommands([
89
+ prViewCommand,
90
+ prStatusCommand,
91
+ prListCommand,
92
+ prCreateCommand,
93
+ prCloseCommand,
94
+ prEditCommand,
95
+ prMergeCommand,
96
+ prReadyCommand,
97
+ prStackCommand,
98
+ prRequestReviewCommand,
99
+ prWaitMergeableCommand,
100
+ prThreadsCommand,
101
+ prCommentsCommand,
102
+ prReviewsCommand,
103
+ prLastHumanReviewerCommand,
104
+ prFeedbackCommand,
105
+ prIssueCommentsCommand,
106
+ prIssueCommentsLatestCommand,
107
+ prCommentCommand,
108
+ prDiscussionSummaryCommand,
109
+ prReplyCommand,
110
+ prResolveCommand,
111
+ prReviewCommand,
112
+ prSubmitReviewCommand,
113
+ prChecksCommand,
114
+ prChecksFailedCommand,
115
+ prWatchCommand,
116
+ prRerunChecksCommand,
117
+ prTriggerChecksCommand,
118
+ prReplyAndResolveCommand,
119
+ prReviewTriageCommand,
120
+ prReviewTriageBatchCommand,
121
+ ]),
122
+ );
123
+
124
+ const issueCommand = Command.make("issue", {}).pipe(
125
+ Command.withDescription(
126
+ "Issue operations (list, view, comments, triage, snapshot-batch, create, close, reopen, comment, edit)",
127
+ ),
128
+ Command.withSubcommands([
129
+ issueListCommand,
130
+ issueViewCommand,
131
+ issueCommentsCommand,
132
+ issueTriageCommand,
133
+ issueSnapshotBatchCommand,
134
+ issueCreateCommand,
135
+ issueCloseCommand,
136
+ issueReopenCommand,
137
+ issueCommentCommand,
138
+ issueEditCommand,
139
+ ]),
140
+ );
141
+
142
+ const branchCommand = Command.make("branch", {}).pipe(
143
+ Command.withDescription("Branch operations (rename)"),
144
+ Command.withSubcommands([branchRenameCommand]),
145
+ );
146
+
147
+ const repoCommand = Command.make("repo", {}).pipe(
148
+ Command.withDescription("Repository operations"),
149
+ Command.withSubcommands([repoInfoCommand, repoListCommand, repoSearchCodeCommand]),
150
+ );
151
+
152
+ const workflowCommand = Command.make("workflow", {}).pipe(
153
+ Command.withDescription(
154
+ "GitHub Actions workflow operations (run, list, view, jobs, logs, job-logs, annotations, rerun, cancel, watch)",
155
+ ),
156
+ Command.withSubcommands([
157
+ workflowRunCommand,
158
+ workflowListCommand,
159
+ workflowViewCommand,
160
+ workflowJobsCommand,
161
+ workflowLogsCommand,
162
+ workflowJobLogsCommand,
163
+ workflowAnnotationsCommand,
164
+ workflowRerunCommand,
165
+ workflowCancelCommand,
166
+ workflowWatchCommand,
167
+ ]),
168
+ );
169
+
170
+ const releaseCommand = Command.make("release", {}).pipe(
171
+ Command.withDescription("Release operations (create, list, view, edit, delete, status)"),
172
+ Command.withSubcommands([
173
+ releaseCreateCommand,
174
+ releaseListCommand,
175
+ releaseViewCommand,
176
+ releaseEditCommand,
177
+ releaseDeleteCommand,
178
+ releaseStatusCommand,
179
+ ]),
180
+ );
181
+
182
+ const gistCommand = Command.make("gist", {}).pipe(
183
+ Command.withDescription("Gist operations (list, view, create, edit, delete)"),
184
+ Command.withSubcommands([
185
+ gistListCommand,
186
+ gistViewCommand,
187
+ gistCreateCommand,
188
+ gistEditCommand,
189
+ gistDeleteCommand,
190
+ ]),
191
+ );
192
+
193
+ const commandsCommand = makeSchemaCommand(() => mainCommand);
194
+
195
+ const mainCommand = Command.make("gh-tool", {}).pipe(
196
+ Command.withDescription(
197
+ `GitHub CLI Tool for Coding Agents
198
+
199
+ Wraps the GitHub CLI (gh) with structured output for AI agents.
200
+ Supports PR management, issue management, reviews, CI checks, and repo info.
201
+
202
+ WORKFLOW FOR AI AGENTS:
203
+ 1. Use 'pr view' to inspect current PR
204
+ 2. Use 'pr discussion-summary' for overview (counts + latest discussion comment)
205
+ 3. Use 'pr threads' and 'pr issue-comments-latest --author <username> --body-contains "Review"' for review context
206
+ 4. Use 'pr reply', 'pr comment' and 'pr resolve' to handle feedback
207
+ 4b. Use 'pr review --event request-changes|approve|comment --confirm' to post a verdict, or 'pr submit-review' for a pending one
208
+ 5. Use 'pr checks' to monitor CI status; 'pr trigger-checks --workflow <file.yml>' when zero checks were reported
209
+ 6. Use 'pr merge' to merge (dry-run by default)
210
+ 7. Use 'issue list' to list open/closed issues
211
+ 8. Use 'issue triage --issue N --verbosity full' to inspect one issue in one call
212
+ 9. Use 'issue comments --issue N' to read issue discussion comments separately
213
+ 10. Use 'issue close --issue N --comment "reason"' to close issues
214
+ 11. Use 'issue comment --issue N --body "text"' to comment on issues
215
+ 12. Use 'repo info' to get repository metadata
216
+ 13. Use 'workflow run' to dispatch workflow_dispatch workflows
217
+ 14. Use 'workflow list' to list recent workflow runs
218
+ 15. Use 'workflow view --run N' to inspect a specific run with jobs/steps
219
+ 16. Use 'workflow logs --run N' to get logs (failed jobs by default)
220
+ 17. Use 'workflow job-logs --run N --job "build-web-app"' to get clean parsed logs for a specific job
221
+ 18. Use 'workflow annotations --run N' to list CI annotations (errors, warnings, notices)
222
+ 19. Use 'workflow watch --run N' to watch until completion
223
+ 20. Use 'release status' to inspect latest release + repository context
224
+ 21. Use 'release create --tag vX.Y.Z --generate-notes' to publish a release
225
+ 22. Use 'release edit/view/list/delete' to maintain existing releases
226
+ 23. Use 'branch rename --old-name X --new-name Y --confirm' to rename a branch
227
+ 24. Use 'gist list/view' to read gists and 'gist create --files a.ts --desc "..."' to share snippets`,
228
+ ),
229
+ Command.withSubcommands([
230
+ prCommand,
231
+ issueCommand,
232
+ repoCommand,
233
+ branchCommand,
234
+ workflowCommand,
235
+ releaseCommand,
236
+ gistCommand,
237
+ commandsCommand,
238
+ ]),
239
+ );
240
+
241
+ const cli = Command.run(mainCommand, {
242
+ version: VERSION,
243
+ });
244
+
245
+ const MainLayer = GitHubService.layer.pipe(
246
+ Layer.provideMerge(BunServices.layer),
247
+ Layer.provideMerge(AuditServiceLayer),
248
+ Layer.provideMerge(ConfigServiceLayer),
249
+ );
250
+
251
+ const program = withAudit("gh", cli).pipe(
252
+ Effect.provide(MainLayer),
253
+ Effect.tapCause(renderCauseToStderr),
254
+ );
255
+
256
+ BunRuntime.runMain(program, {
257
+ disableErrorReporting: true,
258
+ });
@@ -40,6 +40,8 @@ const CHECK_JSON_FIELDS = "name,state,bucket,link";
40
40
  const LONG_LIVED_BRANCHES = new Set(["main", "master", "develop", "staging", "production"]);
41
41
  const STABLE_SNAPSHOT_ATTEMPTS = 3;
42
42
  const GITHUB_ACTIONS_RUN_ID_RE = /github\.com\/[^/]+\/[^/]+\/actions\/runs\/(\d+)/;
43
+ const MERGE_SHA_POLL_INTERVAL_MS = 1000;
44
+ const MERGE_SHA_WAIT_SECONDS = 5;
43
45
 
44
46
  const validatePRTitle = Effect.fn("pr.validatePRTitle")(function* (title: string) {
45
47
  const gh = yield* GitHubService;
@@ -80,6 +82,52 @@ const validatePRTitle = Effect.fn("pr.validatePRTitle")(function* (title: string
80
82
  });
81
83
  });
82
84
 
85
+ const resolveMergeSha = Effect.fn("pr.resolveMergeSha")(function* (
86
+ pr: number,
87
+ knownSha: string | null,
88
+ ) {
89
+ if (knownSha !== null) {
90
+ return knownSha;
91
+ }
92
+
93
+ const gh = yield* GitHubService;
94
+ const readMergeSha = () =>
95
+ gh
96
+ .runGhJson<{ mergeCommit: { oid: string } | null }>([
97
+ "pr",
98
+ "view",
99
+ String(pr),
100
+ "--json",
101
+ "mergeCommit",
102
+ ])
103
+ .pipe(Effect.map((result) => result.mergeCommit?.oid ?? null));
104
+ const result = yield* readMergeSha().pipe(
105
+ Effect.andThen((initial) =>
106
+ pollUntilResolved({
107
+ initial,
108
+ isPending: (value) => value === null,
109
+ fetchLatest: readMergeSha,
110
+ intervalMs: MERGE_SHA_POLL_INTERVAL_MS,
111
+ budgetSeconds: MERGE_SHA_WAIT_SECONDS,
112
+ }),
113
+ ),
114
+ Effect.map((sha) => ({ sha, failed: false as const })),
115
+ Effect.catch((error) =>
116
+ Console.error(
117
+ `Warning: PR #${pr} was merged, but its merge commit SHA could not be read: ${error.message}`,
118
+ ).pipe(Effect.as({ sha: null, failed: true as const })),
119
+ ),
120
+ );
121
+
122
+ if (result.sha === null && !result.failed) {
123
+ yield* Console.error(
124
+ `Warning: PR #${pr} was merged, but its merge commit SHA is still unknown after ${MERGE_SHA_WAIT_SECONDS}s.`,
125
+ );
126
+ }
127
+
128
+ return result.sha;
129
+ });
130
+
83
131
  const buildChecksCommand = (pr: number | null, includeWatch: boolean): string =>
84
132
  `bun agent-tools-gh pr checks${pr !== null ? ` --pr ${pr}` : ""}${includeWatch ? " --watch" : ""}`;
85
133
 
@@ -1005,10 +1053,10 @@ export const mergePR = Effect.fn("pr.mergePR")(function* (opts: {
1005
1053
 
1006
1054
  const mergeArgs = ["pr", "merge", String(opts.pr), `--${opts.strategy}`];
1007
1055
 
1008
- const mergedSha = yield* gh
1056
+ const shaFromAsyncMerge = yield* gh
1009
1057
  .runGh(mergeArgs)
1010
1058
  .pipe(
1011
- Effect.map((result) => result.stdout.match(/([0-9a-f]{7,40})/)?.[1] ?? null),
1059
+ Effect.map(() => null),
1012
1060
  Effect.catchTag("GitHubCommandError", (error) =>
1013
1061
  ASYNC_MERGE_REQUIRED_RE.test(error.stderr)
1014
1062
  ? mergeViaAsyncApi({ pr: opts.pr, strategy: opts.strategy })
@@ -1092,6 +1140,8 @@ export const mergePR = Effect.fn("pr.mergePR")(function* (opts: {
1092
1140
  ),
1093
1141
  );
1094
1142
 
1143
+ const mergedSha = yield* resolveMergeSha(opts.pr, shaFromAsyncMerge);
1144
+
1095
1145
  // `gh pr merge --delete-branch` aborts its remote delete when the head branch is checked out in a
1096
1146
  // worktree; deleting the remote ref explicitly is worktree-independent. Local cleanup is separate.
1097
1147
  if (willDeleteBranch && repo && info.headRefName) {
@@ -10,6 +10,7 @@ import { retryTransient } from "#shared/retry-transient";
10
10
  import { githubApi } from "./api";
11
11
  import type { GitHubApiRequest, GitHubApiResponse } from "./api";
12
12
  import { ConfigService, getGitHubConfig, resolveGitHubRepoTarget } from "#config";
13
+ import { validateOutboundText } from "#gh/text-input";
13
14
 
14
15
  // Transient GitHub-side failures worth a silent retry (vs. a hard error the agent must act on).
15
16
  const NETWORK_ERROR_RE =
@@ -267,11 +268,14 @@ export class GitHubService extends Context.Service<
267
268
  // Auto-retry transient failures, but only for idempotent reads (never replay a mutation).
268
269
  const runGh = (args: string[]): Effect.Effect<GhResult, GhError> => {
269
270
  const canRetry = isSafeRetryRead(args);
270
- return retryTransient({
271
- attempt: () => runGhAttempt(args),
272
- isTransient: (err) =>
273
- err instanceof GitHubCommandError && err.retryable === true && canRetry,
274
- maxRetries: MAX_GH_RETRIES,
271
+ return Effect.gen(function* () {
272
+ yield* validateOutboundText(args.join("\n"), "gh-tool");
273
+ return yield* retryTransient({
274
+ attempt: () => runGhAttempt(args),
275
+ isTransient: (err) =>
276
+ err instanceof GitHubCommandError && err.retryable === true && canRetry,
277
+ maxRetries: MAX_GH_RETRIES,
278
+ });
275
279
  });
276
280
  };
277
281
 
@@ -1,6 +1,7 @@
1
1
  import { Effect, Schema } from "effect";
2
2
 
3
3
  import { GitHubCommandError } from "#gh/errors";
4
+ import { detectSecrets } from "#guard";
4
5
 
5
6
  const STDIN_SENTINEL = "-";
6
7
  const SENSITIVE_PATH_PATTERNS = [
@@ -16,6 +17,62 @@ const readTextFromStdin = () => Bun.stdin.text();
16
17
  export const isSensitivePath = (filePath: string) =>
17
18
  SENSITIVE_PATH_PATTERNS.some((pattern) => pattern.test(filePath));
18
19
 
20
+ /** Inspect the final text, after shell expansion and after reading files or stdin. */
21
+ export function unsafeOutboundTextReason(
22
+ text: string,
23
+ environment: Record<string, string | undefined> = process.env,
24
+ ): string | null {
25
+ if (detectSecrets(text)) return "a credential pattern";
26
+
27
+ const sensitiveName =
28
+ /(?:KEY|TOKEN|SECRET|PASS(?:WORD)?|PWD|CREDENTIAL|AUTH|COOKIE|SESSION|PSK)/i;
29
+ const isSensitiveName = (name: string) =>
30
+ name !== "PWD" && name !== "OLDPWD" && sensitiveName.test(name);
31
+ for (const [name, value] of Object.entries(environment)) {
32
+ if (isSensitiveName(name) && value && value.length >= 8 && text.includes(value)) {
33
+ return "a credential from the process environment";
34
+ }
35
+ }
36
+
37
+ const assignments = text.match(/^[A-Za-z_][A-Za-z0-9_]*=.*$/gm) ?? [];
38
+ if (
39
+ assignments.length >= 5 &&
40
+ assignments.some((line) => isSensitiveName(line.split("=", 1)[0] ?? ""))
41
+ ) {
42
+ return "an environment dump";
43
+ }
44
+ return null;
45
+ }
46
+
47
+ export const validateOutboundText = (text: string, command: string) => {
48
+ const reason = unsafeOutboundTextReason(text);
49
+ return reason === null
50
+ ? Effect.succeed(text)
51
+ : Effect.fail(
52
+ new GitHubCommandError({
53
+ command,
54
+ exitCode: 1,
55
+ stderr: `Refusing to publish text containing ${reason}`,
56
+ message: `Refusing to publish text containing ${reason}`,
57
+ }),
58
+ );
59
+ };
60
+
61
+ export const validateOutboundFile = (filePath: string, command: string) =>
62
+ Effect.tryPromise({
63
+ try: () => readTextFile(filePath),
64
+ catch: () =>
65
+ new GitHubCommandError({
66
+ command,
67
+ exitCode: 1,
68
+ stderr: `Refusing to publish unreadable or sensitive file: ${filePath}`,
69
+ message: `Refusing to publish unreadable or sensitive file: ${filePath}`,
70
+ }),
71
+ }).pipe(
72
+ Effect.flatMap((text) => validateOutboundText(text, command)),
73
+ Effect.asVoid,
74
+ );
75
+
19
76
  const readTextFile = (filePath: string) => {
20
77
  if (isSensitivePath(filePath)) {
21
78
  return Promise.reject(new Error(`Refusing to read sensitive file: ${filePath}`));
@@ -80,13 +137,13 @@ const resolveTextInputInternal = Effect.fn("gh.resolveTextInputInternal")(functi
80
137
  }
81
138
 
82
139
  if (value !== null) {
83
- return value;
140
+ return yield* validateOutboundText(value, command);
84
141
  }
85
142
 
86
143
  if (fileValue !== null) {
87
144
  const source = fileValue === STDIN_SENTINEL ? "stdin" : fileValue;
88
145
 
89
- return yield* Effect.tryPromise({
146
+ const text = yield* Effect.tryPromise({
90
147
  try: () => (fileValue === STDIN_SENTINEL ? readTextFromStdin() : readTextFile(fileValue)),
91
148
  catch: (error) =>
92
149
  new GitHubCommandError({
@@ -96,10 +153,11 @@ const resolveTextInputInternal = Effect.fn("gh.resolveTextInputInternal")(functi
96
153
  message: `Failed to read ${label} from ${source}: ${error instanceof Error ? error.message : String(error)}`,
97
154
  }),
98
155
  });
156
+ return yield* validateOutboundText(text, command);
99
157
  }
100
158
 
101
159
  if (stdin) {
102
- return yield* Effect.tryPromise({
160
+ const text = yield* Effect.tryPromise({
103
161
  try: () => readTextFromStdin(),
104
162
  catch: (error) =>
105
163
  new GitHubCommandError({
@@ -109,6 +167,7 @@ const resolveTextInputInternal = Effect.fn("gh.resolveTextInputInternal")(functi
109
167
  message: `Failed to read ${label} from stdin: ${error instanceof Error ? error.message : String(error)}`,
110
168
  }),
111
169
  });
170
+ return yield* validateOutboundText(text, command);
112
171
  }
113
172
 
114
173
  if (missingMode === "null") {
@@ -116,7 +175,7 @@ const resolveTextInputInternal = Effect.fn("gh.resolveTextInputInternal")(functi
116
175
  }
117
176
 
118
177
  if (missingMode === "default") {
119
- return missingValue ?? "";
178
+ return yield* validateOutboundText(missingValue ?? "", command);
120
179
  }
121
180
 
122
181
  return yield* Effect.fail(
@@ -877,6 +877,7 @@ export const workflowWatchCommand = Command.make(
877
877
  Flag.withDescription(
878
878
  "Include the raw watch progress frames (large); omitted by default — final status/conclusion/jobs are always returned",
879
879
  ),
880
+ Flag.withDefault(false),
880
881
  ),
881
882
  repo: repoOption,
882
883
  run: Flag.Int("run").pipe(Flag.withDescription("Workflow run ID to watch")),