@markjaquith/agency 2.52.1 → 2.52.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/cli-main.ts ADDED
@@ -0,0 +1,854 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Effect, Either, Layer } from "effect"
4
+ import { join, resolve } from "node:path"
5
+ import { parseCli } from "./src/cli-parser"
6
+ import { init, help as initHelp } from "./src/commands/init"
7
+ import { task, help as taskHelp } from "./src/commands/task"
8
+ import { pr, help as prHelp } from "./src/commands/pr"
9
+ import { work, workPrepare, help as workHelp } from "./src/commands/work"
10
+ import { worktree, help as worktreeHelp } from "./src/commands/worktree"
11
+ import { status, help as statusHelp } from "./src/commands/status"
12
+ import { doctor, help as doctorHelp } from "./src/commands/doctor"
13
+ import { validate, help as validateHelp } from "./src/commands/validate"
14
+ import { context, help as contextHelp } from "./src/commands/context"
15
+ import { graph, help as graphHelp } from "./src/commands/graph"
16
+ import { next, help as nextHelp } from "./src/commands/next"
17
+ import { sync, help as syncHelp } from "./src/commands/sync"
18
+ import { repo, help as repoHelp } from "./src/commands/repo"
19
+ import { epic, help as epicHelp } from "./src/commands/epic"
20
+ import { phase, help as phaseHelp } from "./src/commands/phase"
21
+ import { archive, help as archiveHelp } from "./src/commands/archive"
22
+ import { restore, help as restoreHelp } from "./src/commands/restore"
23
+ import { workbase, help as workbaseHelp } from "./src/commands/workbase"
24
+ import {
25
+ integration,
26
+ help as integrationHelp,
27
+ } from "./src/commands/integration"
28
+ import type { Command } from "./src/types"
29
+ import { FileSystemService } from "./src/services/FileSystemService"
30
+ import { WorkbaseService } from "./src/services/WorkbaseService"
31
+ import { RepositoryService } from "./src/services/RepositoryService"
32
+ import { EpicService } from "./src/services/EpicService"
33
+ import { TaskService } from "./src/services/TaskService"
34
+ import { PhaseService } from "./src/services/PhaseService"
35
+ import { WorktreeService } from "./src/services/WorktreeService"
36
+ import { PullRequestService } from "./src/services/PullRequestService"
37
+ import { ArchiveService } from "./src/services/ArchiveService"
38
+ import { IntegrationService } from "./src/services/IntegrationService"
39
+ import { ContextService } from "./src/services/ContextService"
40
+ import { GraphService } from "./src/services/GraphService"
41
+ import { ClaimService } from "./src/services/ClaimService"
42
+ import { SyncService } from "./src/services/SyncService"
43
+ import { ReadinessService } from "./src/services/ReadinessService"
44
+ import { GraphMutationService } from "./src/services/GraphMutationService"
45
+ import { DoctorService } from "./src/services/DoctorService"
46
+ import { ReviewService } from "./src/services/ReviewService"
47
+ import {
48
+ GitVersionControlService,
49
+ JjVersionControlService,
50
+ VersionControlService,
51
+ } from "./src/services/VersionControlService"
52
+ import { review, help as reviewHelp } from "./src/commands/review"
53
+ import { vcs, help as vcsHelp } from "./src/commands/vcs"
54
+ import { VcsMigrationService } from "./src/services/VcsMigrationService"
55
+ import {
56
+ claimCommand,
57
+ claimHelp,
58
+ releaseHelp,
59
+ finishHelp,
60
+ } from "./src/commands/claim"
61
+ import {
62
+ collectCommandResult,
63
+ errorEnvelope,
64
+ successEnvelope,
65
+ writeEnvelope,
66
+ } from "./src/protocol"
67
+
68
+ // Create CLI layer with all services
69
+ const CliLayer = Layer.mergeAll(
70
+ FileSystemService.Default,
71
+ WorkbaseService.Default,
72
+ GitVersionControlService.Default,
73
+ JjVersionControlService.Default,
74
+ VersionControlService.Default,
75
+ VcsMigrationService.Default,
76
+ RepositoryService.Default,
77
+ EpicService.Default,
78
+ TaskService.Default,
79
+ PhaseService.Default,
80
+ WorktreeService.Default,
81
+ PullRequestService.Default,
82
+ ArchiveService.Default,
83
+ IntegrationService.Default,
84
+ ContextService.Default,
85
+ GraphService.Default,
86
+ ClaimService.Default,
87
+ SyncService.Default,
88
+ ReadinessService.Default,
89
+ GraphMutationService.Default,
90
+ DoctorService.Default,
91
+ ReviewService.Default,
92
+ )
93
+
94
+ /**
95
+ * Run a command Effect with all services provided
96
+ */
97
+ async function runEffect<A, E>(effect: Effect.Effect<A, E, any>): Promise<A> {
98
+ const providedEffect = Effect.provide(effect, CliLayer) as Effect.Effect<
99
+ A,
100
+ E,
101
+ never
102
+ >
103
+
104
+ const toError = (error: unknown) => {
105
+ if (error instanceof Error) {
106
+ return error
107
+ }
108
+ if (
109
+ typeof error === "object" &&
110
+ error !== null &&
111
+ "message" in error &&
112
+ typeof error.message === "string"
113
+ ) {
114
+ return new Error(error.message)
115
+ }
116
+ return new Error(String(error))
117
+ }
118
+
119
+ const result = await Effect.runPromise(
120
+ providedEffect.pipe(
121
+ Effect.catchAllDefect((defect) => Effect.fail(toError(defect))),
122
+ Effect.either,
123
+ ),
124
+ )
125
+ if (Either.isLeft(result)) throw result.left
126
+ return result.right
127
+ }
128
+
129
+ const runCommand = <E>(effect: Effect.Effect<void, E, any>) => runEffect(effect)
130
+
131
+ const resolveInvocationCwd = (
132
+ commandName: string,
133
+ options: Record<string, any>,
134
+ ) =>
135
+ runEffect(
136
+ Effect.gen(function* () {
137
+ if (
138
+ options.help ||
139
+ commandName === "init" ||
140
+ commandName === "workbase"
141
+ ) {
142
+ return resolve(options.cwd ?? process.cwd())
143
+ }
144
+ const workbases = yield* WorkbaseService
145
+ if (options.workbase) {
146
+ return yield* workbases.resolveRegistered(options.workbase)
147
+ }
148
+ if (options.cwd) {
149
+ const selectedCwd = resolve(options.cwd)
150
+ yield* workbases.discover(selectedCwd)
151
+ return selectedCwd
152
+ }
153
+ return yield* workbases.discover(process.cwd()).pipe(
154
+ Effect.as(process.cwd()),
155
+ Effect.catchTag("WorkbaseNotFoundError", () =>
156
+ workbases
157
+ .getDefault()
158
+ .pipe(Effect.map((entry) => entry?.path ?? process.cwd())),
159
+ ),
160
+ )
161
+ }),
162
+ )
163
+
164
+ // Read version from package.json
165
+ const packageJson = await Bun.file(
166
+ new URL("./package.json", import.meta.url),
167
+ ).json()
168
+ const VERSION = packageJson.version
169
+
170
+ // Define commands
171
+ const commands: Record<string, Command> = {
172
+ claim: {
173
+ run: async (args: string[], options: Record<string, any>) => {
174
+ if (options.help) return console.log(claimHelp)
175
+ await runCommand(
176
+ claimCommand({
177
+ operation: "claim",
178
+ taskId: args[0],
179
+ phaseId: args[1],
180
+ claimant: options.claimant,
181
+ runner: options.runner,
182
+ sessionId: options["session-id"],
183
+ revision: options.revision,
184
+ expiresAt: options["expires-at"],
185
+ json: options.json,
186
+ silent: options.silent,
187
+ verbose: options.verbose,
188
+ cwd: options.cwd,
189
+ }),
190
+ )
191
+ },
192
+ },
193
+ release: {
194
+ run: async (args: string[], options: Record<string, any>) => {
195
+ if (options.help) return console.log(releaseHelp)
196
+ await runCommand(
197
+ claimCommand({
198
+ operation: "release",
199
+ taskId: args[0],
200
+ phaseId: args[1],
201
+ sessionId: options["session-id"],
202
+ revision: options.revision,
203
+ json: options.json,
204
+ silent: options.silent,
205
+ verbose: options.verbose,
206
+ cwd: options.cwd,
207
+ }),
208
+ )
209
+ },
210
+ },
211
+ finish: {
212
+ run: async (args: string[], options: Record<string, any>) => {
213
+ if (options.help) return console.log(finishHelp)
214
+ await runCommand(
215
+ claimCommand({
216
+ operation: "finish",
217
+ taskId: args[0],
218
+ phaseId: args[1],
219
+ sessionId: options["session-id"],
220
+ revision: options.revision,
221
+ outcome: options.outcome,
222
+ noPullRequest: options["no-pull-request"],
223
+ summary: options.summary,
224
+ evidenceUrl: options["evidence-url"],
225
+ json: options.json,
226
+ silent: options.silent,
227
+ verbose: options.verbose,
228
+ cwd: options.cwd,
229
+ }),
230
+ )
231
+ },
232
+ },
233
+ init: {
234
+ run: async (args: string[], options: Record<string, any>) => {
235
+ if (options.help) {
236
+ console.log(initHelp)
237
+ return
238
+ }
239
+ await runCommand(
240
+ init({
241
+ path: args[0],
242
+ json: options.json,
243
+ silent: options.silent,
244
+ verbose: options.verbose,
245
+ cwd: options.cwd,
246
+ }),
247
+ )
248
+ },
249
+ },
250
+ epic: {
251
+ run: async (args: string[], options: Record<string, any>) => {
252
+ if (options.help) {
253
+ console.log(epicHelp)
254
+ return
255
+ }
256
+ await runCommand(
257
+ epic({
258
+ subcommand: args[0],
259
+ args: args.slice(1),
260
+ ticketUrl: options["ticket-url"],
261
+ description: options.description,
262
+ clearDescription: options["clear-description"],
263
+ ifRevision: options["if-revision"],
264
+ repos: options.repo,
265
+ json: options.json,
266
+ statuses: options.status,
267
+ repositories: options.repository,
268
+ ready: options.ready,
269
+ blocked: options.blocked,
270
+ pr: options.pr ? true : options["no-pr"] ? false : undefined,
271
+ work: options.work,
272
+ auto: options.auto,
273
+ silent: options.silent,
274
+ verbose: options.verbose,
275
+ inputAllowed: options.inputAllowed,
276
+ cwd: options.cwd,
277
+ }),
278
+ )
279
+ },
280
+ },
281
+ pr: {
282
+ run: async (args: string[], options: Record<string, any>) => {
283
+ if (options.help) {
284
+ console.log(prHelp)
285
+ return
286
+ }
287
+ await runCommand(
288
+ pr({
289
+ subcommand: args[0],
290
+ taskId: args[1],
291
+ phaseId: args[2],
292
+ draft: options.draft,
293
+ force: options.force,
294
+ json: options.json,
295
+ silent: options.silent,
296
+ verbose: options.verbose,
297
+ cwd: options.cwd,
298
+ }),
299
+ )
300
+ },
301
+ },
302
+ phase: {
303
+ run: async (args: string[], options: Record<string, any>) => {
304
+ if (options.help) return console.log(phaseHelp)
305
+ await runCommand(
306
+ phase({
307
+ subcommand: args[0],
308
+ args: args.slice(1),
309
+ description: options.description,
310
+ clearDescription: options["clear-description"],
311
+ repo: options.repo?.[0],
312
+ references: options.reference,
313
+ branch: options.branch,
314
+ base: options.base,
315
+ clearReferences: options["clear-references"],
316
+ prUrl: options["pr-url"],
317
+ clearPr: options["clear-pr"],
318
+ noPullRequest: options["no-pull-request"],
319
+ summary: options.summary,
320
+ evidenceUrl: options["evidence-url"],
321
+ ifRevision: options["if-revision"],
322
+ dependsOn: options["depends-on"],
323
+ firstPhase: options["first-phase"],
324
+ json: options.json,
325
+ statuses: options.status,
326
+ repositories: options.repository,
327
+ ready: options.ready,
328
+ blocked: options.blocked,
329
+ pr: options.pr ? true : options["no-pr"] ? false : undefined,
330
+ work: options.work,
331
+ auto: options.auto,
332
+ silent: options.silent,
333
+ verbose: options.verbose,
334
+ inputAllowed: options.inputAllowed,
335
+ cwd: options.cwd,
336
+ }),
337
+ )
338
+ },
339
+ },
340
+ archive: {
341
+ run: async (args: string[], options: Record<string, any>) => {
342
+ if (options.help) {
343
+ console.log(archiveHelp)
344
+ return
345
+ }
346
+ await runCommand(
347
+ archive({
348
+ type: args[0],
349
+ args: args.slice(1),
350
+ json: options.json,
351
+ dryRun: options["dry-run"],
352
+ kinds: options.kind,
353
+ statuses: options.status,
354
+ repositories: options.repository,
355
+ silent: options.silent,
356
+ verbose: options.verbose,
357
+ cwd: options.cwd,
358
+ }),
359
+ )
360
+ },
361
+ },
362
+ restore: {
363
+ run: async (args: string[], options: Record<string, any>) => {
364
+ if (options.help) {
365
+ console.log(restoreHelp)
366
+ return
367
+ }
368
+ await runCommand(
369
+ restore({
370
+ type: args[0],
371
+ args: args.slice(1),
372
+ json: options.json,
373
+ dryRun: options["dry-run"],
374
+ silent: options.silent,
375
+ verbose: options.verbose,
376
+ cwd: options.cwd,
377
+ }),
378
+ )
379
+ },
380
+ },
381
+ workbase: {
382
+ run: async (args: string[], options: Record<string, any>) => {
383
+ if (options.help) {
384
+ console.log(workbaseHelp)
385
+ return
386
+ }
387
+ if (args[0] === "init") {
388
+ await runCommand(
389
+ init({
390
+ path: args[1],
391
+ json: options.json,
392
+ silent: options.silent,
393
+ verbose: options.verbose,
394
+ cwd: options.cwd,
395
+ }),
396
+ )
397
+ return
398
+ }
399
+ await runCommand(
400
+ workbase({
401
+ subcommand: args[0],
402
+ args: args.slice(1),
403
+ json: options.json,
404
+ name: options.name,
405
+ clear: options.clear,
406
+ silent: options.silent,
407
+ verbose: options.verbose,
408
+ cwd: options.cwd,
409
+ }),
410
+ )
411
+ },
412
+ },
413
+ integration: {
414
+ run: async (args: string[], options: Record<string, any>) => {
415
+ if (options.help) {
416
+ console.log(integrationHelp)
417
+ return
418
+ }
419
+ await runCommand(
420
+ integration({
421
+ subcommand: args[0],
422
+ json: options.json,
423
+ silent: options.silent,
424
+ verbose: options.verbose,
425
+ cwd: options.cwd,
426
+ }),
427
+ )
428
+ },
429
+ },
430
+ repo: {
431
+ run: async (args: string[], options: Record<string, any>) => {
432
+ if (options.help) {
433
+ console.log(repoHelp)
434
+ return
435
+ }
436
+ await runCommand(
437
+ repo({
438
+ subcommand: args[0],
439
+ args: args.slice(1),
440
+ silent: options.silent,
441
+ verbose: options.verbose,
442
+ json: options.json,
443
+ dryRun: options["dry-run"],
444
+ apply: options.apply,
445
+ cwd: options.cwd,
446
+ }),
447
+ )
448
+ },
449
+ },
450
+ task: {
451
+ run: async (args: string[], options: Record<string, any>) => {
452
+ if (options.help) {
453
+ console.log(taskHelp)
454
+ return
455
+ }
456
+ await runCommand(
457
+ task({
458
+ subcommand: args[0],
459
+ args: args.slice(1),
460
+ ticketUrl: options["ticket-url"],
461
+ clearTicket: options["clear-ticket"],
462
+ description: options.description,
463
+ clearDescription: options["clear-description"],
464
+ epic: options.epic,
465
+ repo: options.repo?.[0],
466
+ review: options.review,
467
+ pullRequest: options["pull-request"],
468
+ ref: options.ref,
469
+ references: options.reference,
470
+ branch: options.branch,
471
+ base: options.base,
472
+ clearReferences: options["clear-references"],
473
+ prUrl: options["pr-url"],
474
+ clearPr: options["clear-pr"],
475
+ noPullRequest: options["no-pull-request"],
476
+ summary: options.summary,
477
+ evidenceUrl: options["evidence-url"],
478
+ ifRevision: options["if-revision"],
479
+ noEpic: options["no-epic"],
480
+ multiPhase: options["multi-phase"],
481
+ json: options.json,
482
+ statuses: options.status,
483
+ repositories: options.repository,
484
+ ready: options.ready,
485
+ blocked: options.blocked,
486
+ pr: options.pr ? true : options["no-pr"] ? false : undefined,
487
+ work: options.work,
488
+ auto: options.auto,
489
+ silent: options.silent,
490
+ verbose: options.verbose,
491
+ inputAllowed: options.inputAllowed,
492
+ cwd: options.cwd,
493
+ }),
494
+ )
495
+ },
496
+ },
497
+ review: {
498
+ run: async (args: string[], options: Record<string, any>) => {
499
+ if (options.help) return console.log(reviewHelp)
500
+ await runCommand(
501
+ review({
502
+ subcommand: args[0],
503
+ taskId: args[1],
504
+ ifRevision: options["if-revision"],
505
+ json: options.json,
506
+ silent: options.silent,
507
+ verbose: options.verbose,
508
+ cwd: options.cwd,
509
+ }),
510
+ )
511
+ },
512
+ },
513
+ work: {
514
+ run: async (args: string[], options: Record<string, any>) => {
515
+ if (options.help) {
516
+ console.log(workHelp)
517
+ return
518
+ }
519
+ const preparing = args[0] === "prepare"
520
+ await runCommand(
521
+ (preparing ? workPrepare : work)({
522
+ directory: args[preparing ? 1 : 0],
523
+ epicId: options.epic,
524
+ json: options.json,
525
+ dryRun: options["dry-run"],
526
+ silent: options.silent,
527
+ verbose: options.verbose,
528
+ opencode: options.opencode,
529
+ claude: options.claude,
530
+ runner: options.runner,
531
+ auto: options.auto,
532
+ printCommand: options["print-command"],
533
+ force: options.force,
534
+ inputAllowed: options.inputAllowed,
535
+ cwd: options.cwd,
536
+ taskId: options.task,
537
+ phaseId: options.phase,
538
+ }),
539
+ )
540
+ },
541
+ },
542
+ worktree: {
543
+ run: async (args: string[], options: Record<string, any>) => {
544
+ if (options.help) {
545
+ console.log(worktreeHelp)
546
+ return
547
+ }
548
+ await runCommand(
549
+ worktree({
550
+ subcommand: args[0],
551
+ args: args.slice(1),
552
+ dryRun: options["dry-run"],
553
+ json: options.json,
554
+ silent: options.silent,
555
+ verbose: options.verbose,
556
+ cwd: options.cwd,
557
+ }),
558
+ )
559
+ },
560
+ },
561
+ vcs: {
562
+ run: async (args: string[], options: Record<string, any>) => {
563
+ if (options.help) {
564
+ console.log(vcsHelp)
565
+ return
566
+ }
567
+ await runCommand(
568
+ vcs({
569
+ subcommand: args[0],
570
+ target: args[1],
571
+ apply: options.apply,
572
+ dryRun: options["dry-run"],
573
+ json: options.json,
574
+ silent: options.silent,
575
+ verbose: options.verbose,
576
+ cwd: options.cwd,
577
+ }),
578
+ )
579
+ },
580
+ },
581
+ next: {
582
+ run: async (_args: string[], options: Record<string, any>) => {
583
+ if (options.help) return console.log(nextHelp)
584
+ await runCommand(
585
+ next({
586
+ select: options.select,
587
+ json: options.json,
588
+ silent: options.silent,
589
+ verbose: options.verbose,
590
+ cwd: options.cwd,
591
+ }),
592
+ )
593
+ },
594
+ },
595
+ status: {
596
+ run: async (_args: string[], options: Record<string, any>) => {
597
+ if (options.help) {
598
+ console.log(statusHelp)
599
+ return
600
+ }
601
+ await runCommand(
602
+ status({
603
+ silent: options.silent,
604
+ verbose: options.verbose,
605
+ json: options.json,
606
+ statuses: options.status,
607
+ repositories: options.repository,
608
+ ready: options.ready,
609
+ blocked: options.blocked,
610
+ pr: options.pr ? true : options["no-pr"] ? false : undefined,
611
+ cwd: options.cwd,
612
+ }),
613
+ )
614
+ },
615
+ },
616
+ doctor: {
617
+ run: async (_args: string[], options: Record<string, any>) => {
618
+ if (options.help) {
619
+ console.log(doctorHelp)
620
+ return
621
+ }
622
+ await runCommand(
623
+ doctor({
624
+ silent: options.silent,
625
+ verbose: options.verbose,
626
+ json: options.json,
627
+ cwd: options.cwd,
628
+ }),
629
+ )
630
+ },
631
+ },
632
+ validate: {
633
+ run: async (args: string[], options: Record<string, any>) => {
634
+ if (options.help) {
635
+ console.log(validateHelp)
636
+ return
637
+ }
638
+ await runCommand(
639
+ validate({
640
+ path: args[0],
641
+ silent: options.silent,
642
+ verbose: options.verbose,
643
+ json: options.json,
644
+ inputAllowed: options.inputAllowed,
645
+ cwd: options.cwd,
646
+ }),
647
+ )
648
+ },
649
+ },
650
+ context: {
651
+ run: async (args: string[], options: Record<string, any>) => {
652
+ if (options.help) {
653
+ console.log(contextHelp)
654
+ return
655
+ }
656
+ await runCommand(
657
+ context({
658
+ target: options.epic
659
+ ? join("epics", options.epic)
660
+ : options.phase
661
+ ? join("tasks", options.task, "phases", options.phase)
662
+ : options.task
663
+ ? join("tasks", options.task)
664
+ : args[0],
665
+ compact: options.compact,
666
+ full: options.full,
667
+ json: options.json,
668
+ silent: options.silent,
669
+ verbose: options.verbose,
670
+ cwd: options.cwd,
671
+ }),
672
+ )
673
+ },
674
+ },
675
+ graph: {
676
+ run: async (_args: string[], options: Record<string, any>) => {
677
+ if (options.help) {
678
+ console.log(graphHelp)
679
+ return
680
+ }
681
+ await runCommand(
682
+ graph({
683
+ json: options.json,
684
+ jsonl: options.jsonl,
685
+ ready: options.ready,
686
+ blocked: options.blocked,
687
+ statuses: options.status,
688
+ repositories: options.repository,
689
+ kinds: options.kind,
690
+ include: options.include,
691
+ silent: options.silent,
692
+ verbose: options.verbose,
693
+ cwd: options.cwd,
694
+ }),
695
+ )
696
+ },
697
+ },
698
+ sync: {
699
+ run: async (_args: string[], options: Record<string, any>) => {
700
+ if (options.help) {
701
+ console.log(syncHelp)
702
+ return
703
+ }
704
+ await runCommand(
705
+ sync({
706
+ apply: options.apply,
707
+ dryRun: options["dry-run"],
708
+ json: options.json,
709
+ silent: options.silent,
710
+ verbose: options.verbose,
711
+ cwd: options.cwd,
712
+ }),
713
+ )
714
+ },
715
+ },
716
+ }
717
+
718
+ function showMainHelp() {
719
+ console.log(`
720
+ agency v${VERSION}
721
+
722
+ Usage: agency <command> [options]
723
+
724
+ Commands:
725
+ init [path] Initialize an Agency workbase
726
+ workbase <subcommand> Manage registered workbases
727
+ integration <command> Inspect or sync managed integration files
728
+ epic <subcommand> Manage epics
729
+ phase <subcommand> Manage task phases
730
+ claim <task> [phase] Claim an execution unit
731
+ release <task> [phase] Release an execution unit
732
+ finish <task> [phase] Finish an execution unit
733
+ archive <type> Archive a work item
734
+ task <subcommand> Manage tasks
735
+ work [directory|task] Work on an epic, task, or phase
736
+ worktree <subcommand> Inspect and maintain managed workspaces
737
+ vcs <subcommand> Inspect or migrate the version-control backend
738
+ next List or select ready execution units
739
+ pr create Create a pull request for an execution unit
740
+ review refresh Explicitly refresh a pinned review task
741
+ repo <subcommand> Manage workbase repositories
742
+ status Show status for the current workbase
743
+ doctor Diagnose workbase health and integrations
744
+ validate [path] Validate a workbase
745
+ context [target] Return complete target context
746
+ graph Export the complete workbase graph
747
+ sync Reconcile declarations with external state
748
+
749
+ Global Options:
750
+ -h, --help Show help for a command
751
+ -V, --version Show version number
752
+ -s, --silent Suppress output messages
753
+ -v, --verbose Show verbose output including detailed debugging info
754
+ --no-input Never open an interactive prompt or selector
755
+ --workbase <selector> Use a registered workbase ID, name, or path
756
+ --cwd <path> Resolve context from this directory
757
+
758
+ Examples:
759
+ agency init # Initialize the current directory
760
+ agency task list # List tasks
761
+ agency work tasks/refresh-cli-copy # Start working on a task
762
+
763
+ For more information about a command, run:
764
+ agency <command> --help
765
+ `)
766
+ }
767
+
768
+ const machineMode = process.argv
769
+ .slice(2)
770
+ .some((argument) => argument === "--json" || argument === "--jsonl")
771
+
772
+ try {
773
+ const args = process.argv.slice(2)
774
+ const { commandName, args: commandArgs, values } = parseCli(args)
775
+
776
+ // Handle global flags
777
+ if (values.version) {
778
+ if (machineMode) {
779
+ writeEnvelope(successEnvelope({ version: VERSION }))
780
+ } else {
781
+ console.log(`v${VERSION}`)
782
+ }
783
+ process.exit(0)
784
+ }
785
+
786
+ // Get command
787
+ // Show help if no command
788
+ if (!commandName) {
789
+ showMainHelp()
790
+ process.exit(values.help ? 0 : 1)
791
+ }
792
+
793
+ const command = commands[commandName]!
794
+ const inputAllowed =
795
+ !values.json &&
796
+ !values["no-input"] &&
797
+ Boolean(process.stdin.isTTY && process.stdout.isTTY)
798
+ const cwd = await resolveInvocationCwd(commandName, values)
799
+ if (values.json || (values.jsonl && values.help)) {
800
+ const result = await collectCommandResult(() =>
801
+ command.run(commandArgs, { ...values, cwd, inputAllowed }),
802
+ )
803
+ writeEnvelope(successEnvelope(result))
804
+ } else if (values.jsonl) {
805
+ await command.run(commandArgs, { ...values, cwd, inputAllowed: false })
806
+ } else {
807
+ await command.run(commandArgs, { ...values, cwd, inputAllowed })
808
+ }
809
+ } catch (error) {
810
+ if (machineMode) {
811
+ writeEnvelope(errorEnvelope(error))
812
+ process.exit(1)
813
+ }
814
+ if (error instanceof Error) {
815
+ let message = error.message
816
+ let details: any = error
817
+
818
+ // Handle Effect FiberFailure errors that wrap tagged errors
819
+ // When the message is generic "An error has occurred", try to extract the actual error
820
+ if (message === "An error has occurred") {
821
+ // Try to extract the actual error from Effect's Cause structure
822
+ const causeSymbol = Object.getOwnPropertySymbols(error).find((s) =>
823
+ s.toString().includes("Cause"),
824
+ )
825
+ if (causeSymbol) {
826
+ const cause = (error as any)[causeSymbol]
827
+ if (cause && cause._tag === "Fail" && cause.failure) {
828
+ const failure = cause.failure
829
+ details = failure
830
+ // Try common error message patterns
831
+ message =
832
+ failure.message ||
833
+ failure.stderr ||
834
+ (failure._tag
835
+ ? `${failure._tag}: ${JSON.stringify(failure)}`
836
+ : JSON.stringify(failure))
837
+ }
838
+ }
839
+ }
840
+ for (const [field, label] of [
841
+ ["completed", "Completed"],
842
+ ["rolledBack", "Rolled back"],
843
+ ["manualRecovery", "Manual recovery"],
844
+ ] as const) {
845
+ if (Array.isArray(details[field]) && details[field].length > 0)
846
+ message += `\n${label}: ${details[field].join("; ")}`
847
+ }
848
+
849
+ console.error(`ⓘ ${message}`)
850
+ } else {
851
+ console.error("An unexpected error occurred:", error)
852
+ }
853
+ process.exit(1)
854
+ }