@markjaquith/agency 3.5.2 → 3.6.1

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/README.md CHANGED
@@ -7,7 +7,7 @@ read or write.
7
7
 
8
8
  ## Requirements
9
9
 
10
- - [Bun](https://bun.sh) 1.0 or newer
10
+ - [Bun](https://bun.sh) 1.3 or newer (1.4 or newer on Windows ARM64)
11
11
  - Git
12
12
  - [GitHub CLI](https://cli.github.com/) for `agency pr`
13
13
  - OpenCode, Claude Code, or a configured agent for `agency work`
@@ -20,6 +20,11 @@ bun install -g @markjaquith/agency
20
20
 
21
21
  For development, run `bun link` from this repository.
22
22
 
23
+ Package with lifecycle scripts enabled (`bun pm pack` or `npm pack`). The
24
+ `prepack` hook compiles the interactive Solid UI because OpenTUI's runtime JSX
25
+ transform skips files installed under `node_modules`. The `postpack` hook removes
26
+ the generated sibling so subsequent development runs use the source UI.
27
+
23
28
  ## Local Usage Logging
24
29
 
25
30
  Agency records privacy-safe CLI usage events locally so command journeys,
@@ -558,23 +563,123 @@ agency validate
558
563
 
559
564
  ### Interactive Actions
560
565
 
561
- `agency act` opens a filterable work-item chooser followed by an action palette
562
- derived from the selected item's current state and readiness. It offers only
563
- actions that can use existing lifecycle semantics, such as working, creating a
564
- pull request, dropping, reopening, or archiving. Cancelling either chooser makes
565
- no changes, and the command refreshes the graph before dispatch so a stale
566
- selection cannot act on changed work.
566
+ `agency` with no subcommand (or `agency act`) opens on **Workstream**, a flat list
567
+ of non-archived tasks and phases; select an item to see its available actions.
568
+ Press **Tab** to cycle to **Workbase** for workbase actions. Both tabs have a blank
569
+ row above a chevron-prefixed filter with the placeholder “type to filter”.
570
+ Each tab remembers its filter and selection. The active
571
+ tab shares its background with the panel beneath it. Creation
572
+ works even in an empty workbase. The guided flow collects required inputs,
573
+ selects the sole repository automatically, and suggests IDs, base branches, and
574
+ phase branches. Suggestions remain editable; choosing a base never adds a
575
+ completion dependency.
576
+
577
+ Splitting a task / adding a phase and turning an investigation into implementation
578
+ start in **Workstream**: select the source item, then choose its action.
579
+
580
+ Workstream items occupy two rows: a muted type icon and ID on the upper left,
581
+ the main repository and colored status below, and the description wrapping across
582
+ both rows on the right. Selection highlights the entire two-row item. Other item
583
+ pickers retain their compact, flat rows with the same Nerd Font icons.
584
+ Blue is the shared focus/active accent. Type and ordinary action icons are muted;
585
+ status uses green for done, yellow for blocked, and red for dropped. Destructive
586
+ actions use red. Icons and labels convey the meaning independently of color.
587
+ Filtering matches full IDs and metadata even when a displayed name is shortened.
588
+
589
+ The built-in guided flow keeps one full-screen session across menus, text inputs,
590
+ and execution, with the tabs visible throughout. Item actions use colored icons
591
+ and return to the same item's refreshed action menu, with the latest outcome
592
+ above the prompt. Text-entry prompts support Shift-Return for a newline and
593
+ Return to submit, including multiline outcomes and completion summaries.
594
+ Wizard editors fill the available height and keep an editing hint visible below
595
+ the input; longer content scrolls with the cursor as the terminal resizes.
596
+ Submitting a blank required field retries that prompt with an explanation and
597
+ keeps earlier answers. Only input validation is retried; execution failures are
598
+ reported without automatically repeating a mutation.
599
+ Task, phase, review, and handoff ID prompts also check Agency's ID format and
600
+ currently known duplicate IDs before continuing. Native commands still perform
601
+ the final validation when executing, including concurrent changes.
602
+ In wizard inputs, Escape clears entered text first; with an
603
+ empty input it returns one prompt, retaining earlier answers and recomputing later
604
+ defaults. Escape from the first input returns to the menu; Escape from an item's
605
+ action menu returns to Workstream. Tab switches sections from any step. On
606
+ the front screen, Escape clears a filter first. With an empty filter it exits
607
+ `agency act`, but keeps the TUI open when entered through bare `agency`. Ctrl-C quits from any
608
+ screen. On exit it restores the shell and leaves a compact recap of
609
+ completed actions, affected items, and commands. Dry runs are labeled as previews;
610
+ completed steps remain in the recap if a later step is cancelled or fails. Work
611
+ handoffs restore the terminal before starting the interactive worker, then reopen
612
+ the TUI when the worker returns. A configured
613
+ external chooser continues to offer goals and **Browse items** through that chooser.
614
+
615
+ | Goal | Choose in `act` | Discovery/action ID |
616
+ | ------------------------------------ | ------------------------------------------------------------------------------------------------- | ------------------------------------- |
617
+ | Add a repository | Add from a remote, or link an existing local repository | `repo-add`, `repo-link` |
618
+ | Create a task | Create a task → standard task or investigation; describe the outcome | `task-create`, `investigation-create` |
619
+ | Split work | Add a phase / split this task; name the existing work's first phase | `split` |
620
+ | Work on a task or phase | Work on this item | `work` |
621
+ | Move from investigation to execution | Create implementation follow-up | `handoff` |
622
+ | Review someone else's work | Review a PR, or a remote branch/commit | `review`, `review-ref` |
623
+ | Close finished work | Close or reopen work → complete without a PR, drop abandoned work, refresh a merged PR, or reopen | `complete`, `drop`, `sync`, `reopen` |
624
+ | Update PR status | Refresh Agency state from the provider, create a PR, mark a GitHub PR ready, or close it | `sync`, `pr`, `pr-ready`, `pr-close` |
625
+ | Archive terminal work | Archive | `archive` |
626
+ | See current work | See current work | `current-work` |
627
+
628
+ After creating a task, phase, review, or implementation follow-up, choose **Work
629
+ on the new item now** or **Finish**. Only choosing Work prepares checkouts and
630
+ launches the configured runner; creation alone leaves the item for later.
631
+ Investigation handoff creates a distinct implementation item with source and
632
+ revision provenance. Completing a non-PR outcome requires a durable summary.
633
+ PR-backed completion comes from provider reconciliation after merge.
634
+
635
+ **Refresh Agency state** reads the provider and reconciles local records. The
636
+ explicit **GitHub PR ready/close** actions mutate the recorded GitHub URL, then
637
+ refresh Agency state. They are offered only for recorded GitHub PR URLs; actual
638
+ provider state and authorization are checked by the underlying command. Native
639
+ provider-aware PR creation remains available. Existing lifecycle services own
640
+ validation, preparation, revision guards, and archive protections.
641
+
642
+ Pass a task/phase directory, a file inside it (including `TASK.md` or `PHASE.md`),
643
+ or a positional task ID to open that item's actions immediately. Absolute and
644
+ relative paths work; a workbase-root path opens Workbase actions. Explicit
645
+ `--epic <id>`, `--task <id>`, and `--task <id> --phase <id>` also skip item selection.
646
+ Use `--action <id>` to start
647
+ at a specific scenario. `--dry-run` collects inputs and prints exact commands,
648
+ including automatic bookkeeping commands, without executing or offering Work.
649
+ Cancelling before dispatch makes no changes; cancelling after creation leaves
650
+ the newly created item. The graph is refreshed before item actions are dispatched.
567
651
 
568
- Use an existing directory, a positional task ID, `--epic <id>`, `--task <id>`,
569
- or `--task <id> --phase <id>` to skip work-item selection. For example,
570
- `agency act .` selects the current task or phase. `--dry-run` still prompts for
571
- an action but prints the exact Agency command instead of executing it. `--json`
572
- never prompts or executes; it returns matching targets, status and readiness
573
- details, document revisions, and each available action's command argv. With no
574
- selector, JSON includes every active work item.
652
+ ```bash
653
+ agency act .
654
+ agency act --action task-create --dry-run
655
+ agency act --task investigate --action handoff --json
656
+ ```
575
657
 
576
- `--auto` is included in generated or executed work commands, and `--draft` is
577
- included in generated or executed pull request commands.
658
+ For agents, `--json` never prompts or executes. Its compact, runtime-validated
659
+ result includes workbase actions and repository aliases, current working items,
660
+ and matching targets with readiness, document revisions, available `actions`,
661
+ and `blockedActions` with reasons. `--action` filters discovery to one scenario.
662
+ Targets and current working items share the same identity and metadata fields:
663
+ `id`, `kind`, `key`, `status`, `description`, `repo` (the declared main repository,
664
+ when present), `repositories`, `readiness`, and `revision`. Descriptions retain
665
+ their original line breaks rather than the compact Workstream display wrapping.
666
+ Input descriptors follow wizard collection order; narrative fields advertise
667
+ `multiline: true`. Pass a multiline value as one argv element, preserving newlines.
668
+ `command` is exact argv only when no inputs are missing and the action is
669
+ available. Otherwise substitute the required `inputs` into `commandTemplate`'s
670
+ `<input-id>` placeholders. The resulting argv is ready to run: standard task
671
+ creation has no `--purpose` flag, and investigation creation includes a fixed
672
+ `--purpose investigation`. Optional inputs are excluded from the template;
673
+ append their native `option` and value only when wanted (for example,
674
+ `--depends-on <phase-id>`). Human prompting and suggestions are not part of the
675
+ machine protocol. Run commands from the
676
+ returned workbase root. `followUpCommands` are automatic bookkeeping;
677
+ `nextActions` require a separate explicit choice and are never implied by
678
+ creation. Re-discover after mutations rather than reusing old revisions.
679
+
680
+ `--auto` applies to Work, including the post-creation choice. `--draft` applies
681
+ to PR creation. Discovery is based on local state and does not fetch GitHub or
682
+ promise that remote operations or checkout preparation will succeed.
578
683
 
579
684
  ### Target Context
580
685
 
@@ -1244,6 +1349,45 @@ bun run build
1244
1349
 
1245
1350
  Run focused tests with `bun test <test-file>`. Run formatting with `bun format`.
1246
1351
 
1352
+ The TUI uses `@opentui/core` and `@opentui/solid`, pinned together at 0.5.11,
1353
+ with the binding's required `solid-js` 1.9.12. Follow the upstream
1354
+ [Solid bindings](https://github.com/anomalyco/opentui/blob/v0.5.11/packages/web/src/content/docs/bindings/solid.mdx) and
1355
+ [lifecycle guidance](https://github.com/anomalyco/opentui/blob/v0.5.11/packages/web/src/content/docs/core-concepts/lifecycle.mdx):
1356
+
1357
+ - Use Solid signals and OpenTUI hooks for reactive state, keys, and dimensions.
1358
+ - Use declarative `focused` and component `keyBindings`/`onSubmit` for local
1359
+ input behavior; reserve `useKeyboard` for navigation and shared shortcuts.
1360
+ - The renderer creator owns cleanup. `destroy()` restores terminal modes and
1361
+ output streams and disposes the Solid root; keep it in a cleanup/finalizer path.
1362
+ - Keep the lazy Solid preload in `interactive-loader.ts` so non-TUI commands
1363
+ avoid initializing the renderer and runtime-loaded TSX uses the right transform.
1364
+ - Verify upgrades with the UI and PTY tests, including worker handoffs and
1365
+ terminal restoration.
1366
+
1367
+ The `@babel/core` override selects 7.29.6 to fix
1368
+ [GHSA-4x5r-pxfx-6jf8](https://github.com/advisories/GHSA-4x5r-pxfx-6jf8);
1369
+ OpenTUI Solid 0.5.11 otherwise pins vulnerable 7.28.0. Remove the override when
1370
+ the upstream dependency resolves to a patched version, after running `bun audit`
1371
+ and the TUI tests. Effect's minimum is 3.20.0 for
1372
+ [GHSA-38f7-945m-qr2g](https://github.com/advisories/GHSA-38f7-945m-qr2g).
1373
+
1374
+ ### Guided-flow ownership
1375
+
1376
+ The `act` implementation has four boundaries:
1377
+
1378
+ | Module | Responsibility |
1379
+ | ----------------------------- | ------------------------------------------------------------------------------------------------------------ |
1380
+ | `src/commands/act-actions.ts` | Defines availability, discovery argv, and executable plans using existing lifecycle commands. |
1381
+ | `src/commands/act-prompts.ts` | Collects inputs and owns wizard replay. Preparation must be replayable; only `plan.run` performs operations. |
1382
+ | `src/commands/act.ts` | Coordinates navigation, checks the selected item's freshness, executes plans, and records the recap. |
1383
+ | `src/utils/interactive.tsx` | Owns rendering, focus, keyboard handling, and terminal restoration; it has no workbase lifecycle logic. |
1384
+
1385
+ The Effect scope owns the interactive session. A worker handoff explicitly closes
1386
+ that session and permits a new one afterward. External renderer destruction
1387
+ (such as SIGTERM) requests application shutdown and interrupts in-flight work;
1388
+ it must never be interpreted as Back or reopen the interface. PTY regressions
1389
+ exercise both paths against real processes and check terminal restoration.
1390
+
1247
1391
  ## License
1248
1392
 
1249
1393
  MIT
package/cli-main.ts CHANGED
@@ -179,6 +179,7 @@ const commands: Record<string, Command> = {
179
179
  if (options.help) return console.log(actHelp)
180
180
  await runCommand(
181
181
  act({
182
+ action: options.action,
182
183
  directory: args[0],
183
184
  auto: options.auto,
184
185
  draft: options.draft,
@@ -188,6 +189,7 @@ const commands: Record<string, Command> = {
188
189
  taskId: options.task,
189
190
  phaseId: options.phase,
190
191
  inputAllowed: options.inputAllowed,
192
+ exitOnEscape: options.exitOnEscape,
191
193
  silent: options.silent,
192
194
  verbose: options.verbose,
193
195
  cwd: options.cwd,
@@ -707,8 +709,10 @@ agency v${VERSION}
707
709
 
708
710
  Usage: agency <command> [options]
709
711
 
712
+ Run agency without a command to open the TUI.
713
+
710
714
  Commands:
711
- act Interactively choose and act on work
715
+ act Open the interactive workbase
712
716
  init [path] Initialize an Agency workbase
713
717
  workbase <subcommand> Manage registered workbases
714
718
  integration <command> Inspect or sync managed integration files
@@ -796,13 +800,19 @@ const pushUsageDetails = (error?: unknown) => {
796
800
  }
797
801
 
798
802
  try {
803
+ const parsed = parseCli(rawArguments)
799
804
  const {
800
805
  commandName,
801
806
  commandPath,
802
807
  args: commandArgs,
803
808
  passthrough,
804
809
  values,
805
- } = parseCli(rawArguments)
810
+ } = {
811
+ ...parsed,
812
+ ...(!parsed.commandName && !parsed.values.help && !parsed.values.version
813
+ ? { commandName: "act", commandPath: "act" }
814
+ : {}),
815
+ }
806
816
  usageCommandPath = commandPath
807
817
  usageFlagNames = Object.entries(values)
808
818
  .filter(([, value]) => value !== undefined && value !== false)
@@ -874,6 +884,7 @@ try {
874
884
  ...values,
875
885
  cwd,
876
886
  inputAllowed,
887
+ exitOnEscape: Boolean(parsed.commandName),
877
888
  passthrough,
878
889
  })
879
890
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markjaquith/agency",
3
- "version": "3.5.2",
3
+ "version": "3.6.1",
4
4
  "description": "Manage agentic work across repositories with durable workbases",
5
5
  "keywords": [
6
6
  "agents",
@@ -22,6 +22,7 @@
22
22
  "cli.ts",
23
23
  "cli-main.ts",
24
24
  "src",
25
+ "!src/utils/interactive.tsx",
25
26
  "!src/**/*.test.ts",
26
27
  "!src/**/*.test.tsx",
27
28
  "!src/test-utils.ts",
@@ -69,6 +70,8 @@
69
70
  "tag": "latest"
70
71
  },
71
72
  "scripts": {
73
+ "prepack": "bun scripts/build-interactive.ts",
74
+ "postpack": "bun scripts/build-interactive.ts --clean",
72
75
  "postinstall": "bun scripts/install-pi-extension.ts install",
73
76
  "preuninstall": "bun scripts/install-pi-extension.ts uninstall",
74
77
  "benchmark:act": "bun scripts/benchmark-act.ts",
@@ -103,9 +106,9 @@
103
106
  },
104
107
  "dependencies": {
105
108
  "@effect/schema": "^0.75.5",
106
- "@opentui/core": "0.4.5",
107
- "@opentui/solid": "0.4.5",
108
- "effect": "^3.19.15",
109
+ "@opentui/core": "0.5.11",
110
+ "@opentui/solid": "0.5.11",
111
+ "effect": "^3.20.0",
109
112
  "solid-js": "1.9.12",
110
113
  "yaml": "^2.9.0"
111
114
  },
@@ -115,7 +118,10 @@
115
118
  "oxfmt": "^0.27.0",
116
119
  "typescript": "^7.0.2"
117
120
  },
121
+ "overrides": {
122
+ "@babel/core": "7.29.6"
123
+ },
118
124
  "engines": {
119
- "bun": ">=1.0.0"
125
+ "bun": ">=1.3.0"
120
126
  }
121
127
  }
@@ -0,0 +1,62 @@
1
+ import { Schema } from "@effect/schema"
2
+ import { GraphReadiness } from "./graph-schema"
3
+ import { WorkStatus } from "./workbase/schemas"
4
+
5
+ const Argv = Schema.Array(Schema.String)
6
+ const Action = Schema.Struct({
7
+ id: Schema.String,
8
+ label: Schema.optional(Schema.String),
9
+ available: Schema.Boolean,
10
+ blockedReason: Schema.optional(Schema.NullOr(Schema.String)),
11
+ inputs: Schema.optional(
12
+ Schema.Array(
13
+ Schema.Struct({
14
+ id: Schema.String,
15
+ label: Schema.String,
16
+ required: Schema.Boolean,
17
+ multiline: Schema.optional(Schema.Boolean),
18
+ option: Schema.optional(Schema.String),
19
+ }),
20
+ ),
21
+ ),
22
+ command: Schema.optional(Schema.NullOr(Argv)),
23
+ commandTemplate: Schema.optional(Argv),
24
+ followUpCommands: Schema.optional(Schema.Array(Argv)),
25
+ nextActions: Schema.optional(
26
+ Schema.Array(
27
+ Schema.Struct({
28
+ id: Schema.String,
29
+ requiresSelection: Schema.Literal(true),
30
+ commandTemplate: Argv,
31
+ }),
32
+ ),
33
+ ),
34
+ })
35
+ const Kind = Schema.Literal("epic", "task", "phase")
36
+ const EntityFields = {
37
+ kind: Kind,
38
+ id: Schema.String,
39
+ key: Schema.String,
40
+ status: WorkStatus,
41
+ description: Schema.optional(Schema.String),
42
+ repo: Schema.optional(Schema.String),
43
+ repositories: Argv,
44
+ readiness: GraphReadiness,
45
+ revision: Schema.String,
46
+ }
47
+
48
+ export const ActDiscovery = Schema.Struct({
49
+ workbase: Schema.Struct({
50
+ root: Schema.String,
51
+ repositories: Argv,
52
+ actions: Schema.Array(Action),
53
+ }),
54
+ currentWork: Schema.Array(Schema.Struct(EntityFields)),
55
+ targets: Schema.Array(
56
+ Schema.Struct({
57
+ ...EntityFields,
58
+ actions: Schema.Array(Action),
59
+ blockedActions: Schema.Array(Action),
60
+ }),
61
+ ),
62
+ })
package/src/cli-parser.ts CHANGED
@@ -144,20 +144,30 @@ const commands = {
144
144
  },
145
145
  act: {
146
146
  usage:
147
- "agency act [<directory-or-task-id> | --epic <id> | --task <id> [--phase <id>]] [--dry-run | --json] [--auto] [--draft]",
147
+ "agency act [<directory-or-task-id> | --epic <id> | --task <id> [--phase <id>]] [--action <id>] [--dry-run | --json] [--auto] [--draft]",
148
148
  options: {
149
149
  ...outputOptions,
150
150
  ...entitySelectorOptions,
151
151
  "dry-run": { type: "boolean" },
152
152
  auto: { type: "boolean" },
153
153
  draft: { type: "boolean" },
154
+ action: { type: "string" },
154
155
  },
155
156
  command: {
156
157
  usage:
157
- "agency act [<directory-or-task-id> | --epic <id> | --task <id> [--phase <id>]] [--dry-run | --json] [--auto] [--draft]",
158
+ "agency act [<directory-or-task-id> | --epic <id> | --task <id> [--phase <id>]] [--action <id>] [--dry-run | --json] [--auto] [--draft]",
158
159
  minArgs: 0,
159
160
  maxArgs: 1,
160
- options: ["epic", "task", "phase", "dry-run", "json", "auto", "draft"],
161
+ options: [
162
+ "epic",
163
+ "task",
164
+ "phase",
165
+ "dry-run",
166
+ "json",
167
+ "auto",
168
+ "draft",
169
+ "action",
170
+ ],
161
171
  conflicts: [
162
172
  ["dry-run", "json"],
163
173
  ["epic", "task"],