@blogic-cz/agent-tools 0.5.0 → 0.5.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blogic-cz/agent-tools",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
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",
@@ -48,6 +48,10 @@ type LogEntry = {
48
48
  message: string;
49
49
  };
50
50
 
51
+ const repoOption = Flag.string("repo").pipe(
52
+ Flag.withDescription("Target repository (owner/name). Defaults to current repo"),
53
+ Flag.optional,
54
+ );
51
55
  // ---------------------------------------------------------------------------
52
56
  // Internal handlers
53
57
  // ---------------------------------------------------------------------------
@@ -89,26 +93,34 @@ const listRuns = Effect.fn("workflow.listRuns")(function* (opts: {
89
93
  return yield* gh.runGhJson<WorkflowRun[]>(args);
90
94
  });
91
95
 
92
- const viewRun = Effect.fn("workflow.viewRun")(function* (runId: number) {
96
+ const viewRun = Effect.fn("workflow.viewRun")(function* (runId: number, repo: string | null) {
93
97
  const gh = yield* GitHubService;
94
98
 
95
- const run = yield* gh.runGhJson<WorkflowRunDetail>([
99
+ const args = [
96
100
  "run",
97
101
  "view",
98
102
  String(runId),
99
103
  "--json",
100
104
  "databaseId,displayTitle,status,conclusion,headBranch,createdAt,event,url,workflowName,jobs",
101
- ]);
105
+ ];
106
+ if (repo !== null) {
107
+ args.push("--repo", repo);
108
+ }
102
109
 
103
- return run;
110
+ return yield* gh.runGhJson<WorkflowRunDetail>(args);
104
111
  });
105
112
 
106
- const listJobs = Effect.fn("workflow.listJobs")(function* (runId: number) {
113
+ const listJobs = Effect.fn("workflow.listJobs")(function* (runId: number, repo: string | null) {
107
114
  const gh = yield* GitHubService;
108
115
 
116
+ const args = ["run", "view", String(runId), "--json", "jobs"];
117
+ if (repo !== null) {
118
+ args.push("--repo", repo);
119
+ }
120
+
109
121
  const run = yield* gh.runGhJson<{
110
122
  jobs: WorkflowJob[];
111
- }>(["run", "view", String(runId), "--json", "jobs"]);
123
+ }>(args);
112
124
 
113
125
  return run.jobs;
114
126
  });
@@ -117,10 +129,15 @@ const fetchLogs = Effect.fn("workflow.fetchLogs")(function* (
117
129
  runId: number,
118
130
  failedOnly: boolean,
119
131
  jobId: number | null = null,
132
+ repo: string | null = null,
120
133
  ) {
121
134
  const gh = yield* GitHubService;
122
135
  const args = ["run", "view", String(runId)];
123
136
 
137
+ if (repo !== null) {
138
+ args.push("--repo", repo);
139
+ }
140
+
124
141
  if (jobId !== null) {
125
142
  args.push("--log", "--job", String(jobId));
126
143
  } else if (failedOnly) {
@@ -164,10 +181,15 @@ const rerunWorkflow = Effect.fn("workflow.rerunWorkflow")(function* (
164
181
  };
165
182
  });
166
183
 
167
- const cancelRun = Effect.fn("workflow.cancelRun")(function* (runId: number) {
184
+ const cancelRun = Effect.fn("workflow.cancelRun")(function* (runId: number, repo: string | null) {
168
185
  const gh = yield* GitHubService;
169
186
 
170
- yield* gh.runGh(["run", "cancel", String(runId)]);
187
+ const args = ["run", "cancel", String(runId)];
188
+ if (repo !== null) {
189
+ args.push("--repo", repo);
190
+ }
191
+
192
+ yield* gh.runGh(args);
171
193
 
172
194
  return {
173
195
  cancelled: true as const,
@@ -176,10 +198,15 @@ const cancelRun = Effect.fn("workflow.cancelRun")(function* (runId: number) {
176
198
  };
177
199
  });
178
200
 
179
- const watchRun = Effect.fn("workflow.watchRun")(function* (runId: number) {
201
+ const watchRun = Effect.fn("workflow.watchRun")(function* (runId: number, repo: string | null) {
180
202
  const gh = yield* GitHubService;
181
203
 
182
- const result = yield* gh.runGh(["run", "watch", String(runId), "--exit-status"]).pipe(
204
+ const watchArgs = ["run", "watch", String(runId), "--exit-status"];
205
+ if (repo !== null) {
206
+ watchArgs.push("--repo", repo);
207
+ }
208
+
209
+ const result = yield* gh.runGh(watchArgs).pipe(
183
210
  Effect.catchTag("GitHubCommandError", (error) => {
184
211
  // exit-status returns non-zero if run failed, but we still want the output
185
212
  if (error.exitCode > 0 && error.stderr === "") {
@@ -193,7 +220,7 @@ const watchRun = Effect.fn("workflow.watchRun")(function* (runId: number) {
193
220
  }),
194
221
  );
195
222
 
196
- const finalState = yield* viewRun(runId);
223
+ const finalState = yield* viewRun(runId, repo);
197
224
 
198
225
  return {
199
226
  runId,
@@ -268,8 +295,12 @@ export function formatLogEntries(entries: LogEntry[]): string {
268
295
  // Job-level log handlers
269
296
  // ---------------------------------------------------------------------------
270
297
 
271
- const resolveJobId = Effect.fn("workflow.resolveJobId")(function* (runId: number, jobName: string) {
272
- const jobs = yield* listJobs(runId);
298
+ const resolveJobId = Effect.fn("workflow.resolveJobId")(function* (
299
+ runId: number,
300
+ jobName: string,
301
+ repo: string | null,
302
+ ) {
303
+ const jobs = yield* listJobs(runId, repo);
273
304
 
274
305
  // Exact match first
275
306
  const exact = jobs.find((j) => j.name === jobName);
@@ -305,8 +336,9 @@ const filterFailedStepEntries = Effect.fn("workflow.filterFailedStepEntries")(fu
305
336
  runId: number,
306
337
  jobId: number,
307
338
  entries: LogEntry[],
339
+ repo: string | null,
308
340
  ) {
309
- const jobs = yield* listJobs(runId);
341
+ const jobs = yield* listJobs(runId, repo);
310
342
  const job = jobs.find((j) => j.databaseId === jobId);
311
343
  if (!job) return entries;
312
344
 
@@ -324,25 +356,47 @@ const fetchJobLogs = Effect.fn("workflow.fetchJobLogs")(function* (opts: {
324
356
  job: string;
325
357
  failedStepsOnly: boolean;
326
358
  format: string;
359
+ repo: string | null;
327
360
  }) {
328
361
  const gh = yield* GitHubService;
329
- const { owner, name: repo } = yield* gh.getRepoInfo();
330
362
 
331
- const jobId = yield* resolveJobId(opts.runId, opts.job);
363
+ let owner: string;
364
+ let repoName: string;
365
+ if (opts.repo !== null) {
366
+ const parts = opts.repo.split("/");
367
+ if (parts.length !== 2 || !parts[0] || !parts[1]) {
368
+ return yield* new GitHubCommandError({
369
+ message: `Invalid --repo format: "${opts.repo}". Expected "owner/name" (e.g. "blogic-cz/agent-tools").`,
370
+ command: "workflow job-logs",
371
+ exitCode: 1,
372
+ stderr: "",
373
+ });
374
+ }
375
+ owner = parts[0];
376
+ repoName = parts[1];
377
+ } else {
378
+ const info = yield* gh.getRepoInfo();
379
+ owner = info.owner;
380
+ repoName = info.name;
381
+ }
382
+
383
+ const jobId = yield* resolveJobId(opts.runId, opts.job, opts.repo);
332
384
 
333
385
  // Fetch raw logs via API (follows 302 redirect automatically)
334
- const raw = yield* gh.runGh(["api", `repos/${owner}/${repo}/actions/jobs/${jobId}/logs`]).pipe(
335
- Effect.map((r) => r.stdout),
336
- Effect.catchTag("GitHubCommandError", () => {
337
- // Fallback: use gh run view --log --job
338
- return fetchLogs(opts.runId, false, jobId).pipe(Effect.map((r) => r.log));
339
- }),
340
- );
386
+ const raw = yield* gh
387
+ .runGh(["api", `repos/${owner}/${repoName}/actions/jobs/${jobId}/logs`])
388
+ .pipe(
389
+ Effect.map((r) => r.stdout),
390
+ Effect.catchTag("GitHubCommandError", () => {
391
+ // Fallback: use gh run view --log --job
392
+ return fetchLogs(opts.runId, false, jobId, opts.repo).pipe(Effect.map((r) => r.log));
393
+ }),
394
+ );
341
395
 
342
396
  let entries = parseRawJobLogs(raw);
343
397
 
344
398
  if (opts.failedStepsOnly) {
345
- entries = yield* filterFailedStepEntries(opts.runId, jobId, entries);
399
+ entries = yield* filterFailedStepEntries(opts.runId, jobId, entries, opts.repo);
346
400
  }
347
401
 
348
402
  if (opts.format === "json") {
@@ -420,11 +474,12 @@ export const workflowViewCommand = Command.make(
420
474
  "view",
421
475
  {
422
476
  format: formatOption,
477
+ repo: repoOption,
423
478
  run: Flag.integer("run").pipe(Flag.withDescription("Workflow run ID")),
424
479
  },
425
- ({ format, run }) =>
480
+ ({ format, repo, run }) =>
426
481
  Effect.gen(function* () {
427
- const detail = yield* viewRun(run);
482
+ const detail = yield* viewRun(run, Option.getOrNull(repo));
428
483
  yield* logFormatted(detail, format);
429
484
  }),
430
485
  ).pipe(Command.withDescription("View workflow run details including jobs and steps"));
@@ -433,11 +488,12 @@ export const workflowJobsCommand = Command.make(
433
488
  "jobs",
434
489
  {
435
490
  format: formatOption,
491
+ repo: repoOption,
436
492
  run: Flag.integer("run").pipe(Flag.withDescription("Workflow run ID")),
437
493
  },
438
- ({ format, run }) =>
494
+ ({ format, repo, run }) =>
439
495
  Effect.gen(function* () {
440
- const jobs = yield* listJobs(run);
496
+ const jobs = yield* listJobs(run, Option.getOrNull(repo));
441
497
  yield* logFormatted(jobs, format);
442
498
  }),
443
499
  ).pipe(Command.withDescription("List jobs and their steps for a workflow run"));
@@ -450,11 +506,12 @@ export const workflowLogsCommand = Command.make(
450
506
  Flag.withDefault(true),
451
507
  ),
452
508
  format: formatOption,
509
+ repo: repoOption,
453
510
  run: Flag.integer("run").pipe(Flag.withDescription("Workflow run ID")),
454
511
  },
455
- ({ failedOnly, format, run }) =>
512
+ ({ failedOnly, format, repo, run }) =>
456
513
  Effect.gen(function* () {
457
- const logs = yield* fetchLogs(run, failedOnly);
514
+ const logs = yield* fetchLogs(run, failedOnly, null, Option.getOrNull(repo));
458
515
 
459
516
  if (format === "toon" || format === "json") {
460
517
  yield* logFormatted(logs, format);
@@ -489,11 +546,12 @@ export const workflowCancelCommand = Command.make(
489
546
  "cancel",
490
547
  {
491
548
  format: formatOption,
549
+ repo: repoOption,
492
550
  run: Flag.integer("run").pipe(Flag.withDescription("Workflow run ID to cancel")),
493
551
  },
494
- ({ format, run }) =>
552
+ ({ format, repo, run }) =>
495
553
  Effect.gen(function* () {
496
- const result = yield* cancelRun(run);
554
+ const result = yield* cancelRun(run, Option.getOrNull(repo));
497
555
  yield* logFormatted(result, format);
498
556
  }),
499
557
  ).pipe(Command.withDescription("Cancel an in-progress workflow run"));
@@ -502,11 +560,12 @@ export const workflowWatchCommand = Command.make(
502
560
  "watch",
503
561
  {
504
562
  format: formatOption,
563
+ repo: repoOption,
505
564
  run: Flag.integer("run").pipe(Flag.withDescription("Workflow run ID to watch")),
506
565
  },
507
- ({ format, run }) =>
566
+ ({ format, repo, run }) =>
508
567
  Effect.gen(function* () {
509
- const result = yield* watchRun(run);
568
+ const result = yield* watchRun(run, Option.getOrNull(repo));
510
569
  yield* logFormatted(result, format);
511
570
  }),
512
571
  ).pipe(Command.withDescription("Watch a workflow run until it completes, then show final status"));
@@ -522,15 +581,17 @@ export const workflowJobLogsCommand = Command.make(
522
581
  job: Flag.string("job").pipe(
523
582
  Flag.withDescription("Job name to fetch logs for (exact or partial match)"),
524
583
  ),
584
+ repo: repoOption,
525
585
  run: Flag.integer("run").pipe(Flag.withDescription("Workflow run ID")),
526
586
  },
527
- ({ failedStepsOnly, format, job, run }) =>
587
+ ({ failedStepsOnly, format, job, repo, run }) =>
528
588
  Effect.gen(function* () {
529
589
  const result = yield* fetchJobLogs({
530
590
  runId: run,
531
591
  job,
532
592
  failedStepsOnly,
533
593
  format,
594
+ repo: Option.getOrNull(repo),
534
595
  });
535
596
 
536
597
  if ("formatted" in result) {