@markjaquith/agency 1.10.0 → 1.11.0

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
@@ -104,6 +104,31 @@ Toggle between source branch and emit branch. If on an emit branch (e.g., `foo--
104
104
 
105
105
  Switch to the source branch for the current emit branch.
106
106
 
107
+ ### `agency completions <bash|zsh>`
108
+
109
+ Generate shell completion scripts. The generated scripts are static, so shell startup and tab completion do not call `agency`.
110
+
111
+ **zsh:**
112
+
113
+ ```bash
114
+ mkdir -p ~/.zsh/completions
115
+ agency completions zsh > ~/.zsh/completions/_agency
116
+ ```
117
+
118
+ Then add this to `~/.zshrc` before `compinit`:
119
+
120
+ ```bash
121
+ fpath=(~/.zsh/completions $fpath)
122
+ autoload -Uz compinit && compinit
123
+ ```
124
+
125
+ **bash:**
126
+
127
+ ```bash
128
+ mkdir -p ~/.local/share/bash-completion/completions
129
+ agency completions bash > ~/.local/share/bash-completion/completions/agency
130
+ ```
131
+
107
132
  ## Requirements
108
133
 
109
134
  - [Bun](https://bun.sh) >= 1.0.0 (recommended)
package/cli.ts CHANGED
@@ -20,6 +20,10 @@ import { template, help as templateHelp } from "./src/commands/template"
20
20
  import { work, help as workHelp } from "./src/commands/work"
21
21
  import { loop, help as loopHelp } from "./src/commands/loop"
22
22
  import { status, help as statusHelp } from "./src/commands/status"
23
+ import {
24
+ completions,
25
+ help as completionsHelp,
26
+ } from "./src/commands/completions"
23
27
  import type { Command } from "./src/types"
24
28
  import { setColorsEnabled } from "./src/utils/colors"
25
29
  import { GitService } from "./src/services/GitService"
@@ -469,6 +473,18 @@ const commands: Record<string, Command> = {
469
473
  },
470
474
  help: loopHelp,
471
475
  },
476
+ completions: {
477
+ name: "completions",
478
+ description: "Generate shell completion scripts",
479
+ run: async (args: string[], options: Record<string, any>) => {
480
+ if (options.help) {
481
+ console.log(completionsHelp)
482
+ return
483
+ }
484
+ await runCommand(completions(args[0]))
485
+ },
486
+ help: completionsHelp,
487
+ },
472
488
  }
473
489
 
474
490
  function showMainHelp() {
@@ -502,6 +518,7 @@ Commands:
502
518
  source Switch to source branch from emitted branch
503
519
  merge Merge emitted branch into base branch
504
520
  status Show agency status for this repository
521
+ completions <shell> Generate bash or zsh completion scripts
505
522
 
506
523
  Global Options:
507
524
  -h, --help Show help for a command
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markjaquith/agency",
3
- "version": "1.10.0",
3
+ "version": "1.11.0",
4
4
  "description": "Manages personal agents files",
5
5
  "keywords": [
6
6
  "agents",
@@ -0,0 +1,51 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import { Effect } from "effect"
3
+ import { completions } from "./completions"
4
+
5
+ const captureCompletions = async (shell: string) => {
6
+ const originalLog = console.log
7
+ let output = ""
8
+ console.log = (message: string) => {
9
+ output += message
10
+ }
11
+
12
+ try {
13
+ await Effect.runPromise(completions(shell))
14
+ } finally {
15
+ console.log = originalLog
16
+ }
17
+
18
+ return output
19
+ }
20
+
21
+ describe("completions command", () => {
22
+ test("generates bash completions", async () => {
23
+ const output = await captureCompletions("bash")
24
+
25
+ expect(output).toContain("_agency_completions()")
26
+ expect(output).toContain("complete -F _agency_completions agency")
27
+ expect(output).toContain("completions")
28
+ expect(output).toContain("bash zsh")
29
+ })
30
+
31
+ test("generates zsh completions", async () => {
32
+ const output = await captureCompletions("zsh")
33
+
34
+ expect(output).toContain("#compdef agency")
35
+ expect(output).toContain("_agency()")
36
+ expect(output).toContain("agency_commands=(")
37
+ expect(output).toContain("bash\\:Generate\\ bash\\ completions")
38
+ })
39
+
40
+ test("prints the requested completion script", async () => {
41
+ const output = await captureCompletions("zsh")
42
+
43
+ expect(output).toContain("#compdef agency")
44
+ })
45
+
46
+ test("rejects unsupported shells", async () => {
47
+ await expect(Effect.runPromise(completions("fish"))).rejects.toThrow(
48
+ "Usage: agency completions <bash|zsh>",
49
+ )
50
+ })
51
+ })
@@ -0,0 +1,308 @@
1
+ import { Effect } from "effect"
2
+
3
+ const shells = ["bash", "zsh"] as const
4
+ type Shell = (typeof shells)[number]
5
+
6
+ const commands = [
7
+ "init",
8
+ "task",
9
+ "tasks",
10
+ "edit",
11
+ "work",
12
+ "template",
13
+ "emit",
14
+ "emitted",
15
+ "pr",
16
+ "push",
17
+ "pull",
18
+ "rebase",
19
+ "base",
20
+ "switch",
21
+ "source",
22
+ "merge",
23
+ "status",
24
+ "clean",
25
+ "loop",
26
+ "completions",
27
+ ] as const
28
+
29
+ const templateSubcommands = ["use", "save", "list", "view", "delete"] as const
30
+ const baseSubcommands = ["get", "set"] as const
31
+ const completionSubcommands = shells
32
+
33
+ const globalOptions = [
34
+ "--help",
35
+ "-h",
36
+ "--version",
37
+ "-v",
38
+ "--no-color",
39
+ "--silent",
40
+ "-s",
41
+ "--verbose",
42
+ ] as const
43
+
44
+ const commandOptions: Record<string, readonly string[]> = {
45
+ init: [
46
+ "--help",
47
+ "-h",
48
+ "--template",
49
+ "-t",
50
+ "--silent",
51
+ "-s",
52
+ "--verbose",
53
+ "-v",
54
+ ],
55
+ emit: [
56
+ "--help",
57
+ "-h",
58
+ "--emit",
59
+ "--branch",
60
+ "--silent",
61
+ "-s",
62
+ "--verbose",
63
+ "-v",
64
+ ],
65
+ push: [
66
+ "--help",
67
+ "-h",
68
+ "--emit",
69
+ "--branch",
70
+ "--force",
71
+ "-f",
72
+ "--no-verify",
73
+ "--pr",
74
+ "--silent",
75
+ "-s",
76
+ "--verbose",
77
+ "-v",
78
+ ],
79
+ pull: ["--help", "-h", "--remote", "-r", "--silent", "-s", "--verbose", "-v"],
80
+ rebase: [
81
+ "--help",
82
+ "-h",
83
+ "--emit",
84
+ "--branch",
85
+ "--silent",
86
+ "-s",
87
+ "--verbose",
88
+ "-v",
89
+ ],
90
+ task: [
91
+ "--help",
92
+ "-h",
93
+ "--emit",
94
+ "--branch",
95
+ "--task",
96
+ "--from",
97
+ "--from-current",
98
+ "--continue",
99
+ "--squash",
100
+ "--silent",
101
+ "-s",
102
+ "--verbose",
103
+ "-v",
104
+ ],
105
+ tasks: ["--help", "-h", "--json", "--silent", "-s", "--verbose", "-v"],
106
+ base: ["--help", "-h", "--repo", "--silent", "-s", "--verbose", "-v"],
107
+ template: [
108
+ "--help",
109
+ "-h",
110
+ "--template",
111
+ "-t",
112
+ "--silent",
113
+ "-s",
114
+ "--verbose",
115
+ "-v",
116
+ ],
117
+ work: [
118
+ "--help",
119
+ "-h",
120
+ "--opencode",
121
+ "--claude",
122
+ "--silent",
123
+ "-s",
124
+ "--verbose",
125
+ "-v",
126
+ ],
127
+ loop: [
128
+ "--help",
129
+ "-h",
130
+ "--min-loops",
131
+ "--max-loops",
132
+ "--opencode",
133
+ "--claude",
134
+ "--silent",
135
+ "-s",
136
+ "--verbose",
137
+ "-v",
138
+ ],
139
+ status: ["--help", "-h", "--json", "--silent", "-s", "--verbose", "-v"],
140
+ clean: [
141
+ "--help",
142
+ "-h",
143
+ "--dry-run",
144
+ "--merged-into",
145
+ "--silent",
146
+ "-s",
147
+ "--verbose",
148
+ "-v",
149
+ ],
150
+ completions: ["--help", "-h"],
151
+ }
152
+
153
+ const words = (values: readonly string[]) => values.join(" ")
154
+
155
+ const bashCase = (command: string, subcommands?: readonly string[]) => {
156
+ const options = commandOptions[command] ?? [
157
+ "--help",
158
+ "-h",
159
+ "--silent",
160
+ "-s",
161
+ "--verbose",
162
+ "-v",
163
+ ]
164
+ const completions = subcommands ? [...subcommands, ...options] : options
165
+ return `\t\t${command})\n\t\t\tCOMPREPLY=( $(compgen -W "${words(completions)}" -- "$cur") )\n\t\t\treturn 0\n\t\t\t;;`
166
+ }
167
+
168
+ const zshCommandSpec = (command: string, description: string) =>
169
+ ` '${command}:${description}'`
170
+
171
+ const generateBashCompletions = () => `# bash completion for agency
172
+ _agency_completions() {
173
+ local cur prev command
174
+ COMPREPLY=()
175
+ cur="\${COMP_WORDS[COMP_CWORD]}"
176
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
177
+
178
+ if [[ $COMP_CWORD -eq 1 ]]; then
179
+ COMPREPLY=( $(compgen -W "${words([...commands, ...globalOptions])}" -- "$cur") )
180
+ return 0
181
+ fi
182
+
183
+ command="\${COMP_WORDS[1]}"
184
+ case "$command" in
185
+ ${bashCase("template", templateSubcommands)}
186
+ ${bashCase("base", baseSubcommands)}
187
+ ${bashCase("completions", completionSubcommands)}
188
+ ${commands
189
+ .filter((command) => !["template", "base", "completions"].includes(command))
190
+ .map((command) => bashCase(command))
191
+ .join("\n")}
192
+ esac
193
+
194
+ case "$prev" in
195
+ --template|-t|--emit|--branch|--task|--from|--remote|-r|--merged-into)
196
+ return 0
197
+ ;;
198
+ esac
199
+ }
200
+
201
+ complete -F _agency_completions agency
202
+ `
203
+
204
+ const generateZshCompletions = () => `#compdef agency
205
+ # zsh completion for agency
206
+
207
+ _agency() {
208
+ local context state line
209
+ local -a agency_commands
210
+ typeset -A opt_args
211
+
212
+ agency_commands=(
213
+ ${zshCommandSpec("init", "Initialize agency with template selection")}
214
+ ${zshCommandSpec("task", "Initialize template files on a feature branch")}
215
+ ${zshCommandSpec("tasks", "List all task branches")}
216
+ ${zshCommandSpec("edit", "Open TASK.md in system editor")}
217
+ ${zshCommandSpec("work", "Start working on TASK.md with OpenCode")}
218
+ ${zshCommandSpec("template", "Template management commands")}
219
+ ${zshCommandSpec("emit", "Emit a branch with backpack files reverted")}
220
+ ${zshCommandSpec("emitted", "Get the name of the emitted branch")}
221
+ ${zshCommandSpec("pr", "Run gh pr with the emitted branch name")}
222
+ ${zshCommandSpec("push", "Emit, push to remote, return to source")}
223
+ ${zshCommandSpec("pull", "Pull commits from remote emit branch to source")}
224
+ ${zshCommandSpec("rebase", "Rebase source branch onto base branch")}
225
+ ${zshCommandSpec("base", "Get or set the base branch")}
226
+ ${zshCommandSpec("switch", "Toggle between source and emitted branch")}
227
+ ${zshCommandSpec("source", "Switch to source branch from emitted branch")}
228
+ ${zshCommandSpec("merge", "Merge emitted branch into base branch")}
229
+ ${zshCommandSpec("status", "Show agency status for this repository")}
230
+ ${zshCommandSpec("clean", "Delete branches merged into a specified branch")}
231
+ ${zshCommandSpec("loop", "Run a Ralph Wiggum loop to complete all tasks")}
232
+ ${zshCommandSpec("completions", "Generate shell completion scripts")}
233
+ )
234
+
235
+ _arguments -C \\
236
+ '(-h --help)'{-h,--help}'[show help]' \\
237
+ '(-v --version)'{-v,--version}'[show version]' \\
238
+ '--no-color[disable color output]' \\
239
+ '(-s --silent)'{-s,--silent}'[suppress output messages]' \\
240
+ '--verbose[show verbose output]' \\
241
+ '1:command:->command' \\
242
+ '*::arg:->args'
243
+
244
+ case $state in
245
+ command)
246
+ _describe 'agency command' agency_commands
247
+ ;;
248
+ args)
249
+ case $line[1] in
250
+ template)
251
+ _arguments \\
252
+ '(-h --help)'{-h,--help}'[show help]' \\
253
+ '(-t --template)'{-t,--template}'[template name]:template:' \\
254
+ '(-s --silent)'{-s,--silent}'[suppress output messages]' \\
255
+ '--verbose[show verbose output]' \\
256
+ '1:subcommand:((use\\:Set\\ template save\\:Save\\ files list\\:List\\ files view\\:View\\ file delete\\:Delete\\ files))' \\
257
+ '*:file:_files'
258
+ ;;
259
+ base)
260
+ _arguments \\
261
+ '(-h --help)'{-h,--help}'[show help]' \\
262
+ '--repo[use repository-local configuration]' \\
263
+ '(-s --silent)'{-s,--silent}'[suppress output messages]' \\
264
+ '--verbose[show verbose output]' \\
265
+ '1:subcommand:((get\\:Get\\ base\\ branch set\\:Set\\ base\\ branch))' \\
266
+ '*:branch:'
267
+ ;;
268
+ completions)
269
+ _arguments \\
270
+ '(-h --help)'{-h,--help}'[show help]' \\
271
+ '1:shell:((bash\\:Generate\\ bash\\ completions zsh\\:Generate\\ zsh\\ completions))'
272
+ ;;
273
+ *)
274
+ _arguments \\
275
+ '(-h --help)'{-h,--help}'[show help]' \\
276
+ '(-s --silent)'{-s,--silent}'[suppress output messages]' \\
277
+ '--verbose[show verbose output]'
278
+ ;;
279
+ esac
280
+ ;;
281
+ esac
282
+ }
283
+
284
+ _agency "$@"
285
+ `
286
+
287
+ export const completions = (shell: string | undefined) =>
288
+ Effect.gen(function* () {
289
+ if (!shell || !shells.includes(shell as Shell)) {
290
+ return yield* Effect.fail(
291
+ new Error("Usage: agency completions <bash|zsh>"),
292
+ )
293
+ }
294
+
295
+ console.log(
296
+ shell === "bash" ? generateBashCompletions() : generateZshCompletions(),
297
+ )
298
+ })
299
+
300
+ export const help = `
301
+ Usage: agency completions <bash|zsh>
302
+
303
+ Generate shell completion scripts.
304
+
305
+ Examples:
306
+ agency completions zsh > ~/.zsh/completions/_agency
307
+ agency completions bash > ~/.local/share/bash-completion/completions/agency
308
+ `