@markjaquith/agency 2.13.0 → 2.15.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.
@@ -1,5 +1,6 @@
1
1
  import { Effect } from "effect"
2
2
  import type { WorkStatus } from "./schemas"
3
+ import { choose } from "../utils/chooser"
3
4
 
4
5
  export type WorkTarget =
5
6
  | {
@@ -52,6 +53,7 @@ interface PhaseRecord {
52
53
 
53
54
  export interface WorkTargetChoice {
54
55
  readonly label: string
56
+ readonly plainLabel: string
55
57
  readonly target: WorkTarget
56
58
  }
57
59
 
@@ -78,6 +80,15 @@ const label = (
78
80
  }[kind]
79
81
  } ${id}${description === undefined ? "" : `\x1b[2m - ${description}\x1b[0m`}`
80
82
 
83
+ const plainLabel = (
84
+ indent: string,
85
+ kind: WorkTarget["kind"],
86
+ id: string,
87
+ description?: string,
88
+ status?: WorkStatus,
89
+ ) =>
90
+ `${indent}${status === undefined ? "" : `[${status}] `}${kind} ${id}${description === undefined ? "" : ` - ${description}`}`
91
+
81
92
  const taskChoices = (
82
93
  task: TaskRecord,
83
94
  phaseRecords: readonly PhaseRecord[],
@@ -93,6 +104,13 @@ const taskChoices = (
93
104
  task.data.description,
94
105
  multiPhase ? undefined : (task.data.status ?? "open"),
95
106
  ),
107
+ plainLabel: plainLabel(
108
+ indent,
109
+ "task",
110
+ task.id,
111
+ task.data.description,
112
+ multiPhase ? undefined : (task.data.status ?? "open"),
113
+ ),
96
114
  target: {
97
115
  kind: "task",
98
116
  taskId: task.id,
@@ -117,6 +135,13 @@ const taskChoices = (
117
135
  record.data.description,
118
136
  record.data.status ?? "open",
119
137
  ),
138
+ plainLabel: plainLabel(
139
+ `${indent} `,
140
+ "phase",
141
+ record.id,
142
+ record.data.description,
143
+ record.data.status ?? "open",
144
+ ),
120
145
  target: {
121
146
  kind: "phase",
122
147
  taskId: task.id,
@@ -135,6 +160,13 @@ const taskChoices = (
135
160
  record.data.description,
136
161
  record.data.status ?? "open",
137
162
  ),
163
+ plainLabel: plainLabel(
164
+ `${indent} `,
165
+ "phase",
166
+ record.id,
167
+ record.data.description,
168
+ record.data.status ?? "open",
169
+ ),
138
170
  target: {
139
171
  kind: "phase",
140
172
  taskId: task.id,
@@ -164,6 +196,7 @@ export const buildWorkTargetChoices = (
164
196
  for (const epic of epicRecords) {
165
197
  choices.push({
166
198
  label: label("", "epic", epic.id, epic.data.description),
199
+ plainLabel: plainLabel("", "epic", epic.id, epic.data.description),
167
200
  target: { kind: "epic", epicId: epic.id, path: epic.path },
168
201
  })
169
202
  for (const child of epic.data.tasks) {
@@ -184,40 +217,17 @@ export const buildWorkTargetChoices = (
184
217
 
185
218
  export type PickWorkTarget = (
186
219
  choices: readonly WorkTargetChoice[],
220
+ command?: readonly string[],
187
221
  ) => Effect.Effect<WorkTarget | null, Error>
188
222
 
189
- const parseWorkTargetSelection = (
190
- output: string,
191
- choices: readonly WorkTargetChoice[],
192
- ) => {
193
- const index = Number.parseInt(output.split("\t", 1)[0] ?? "", 10)
194
- return choices[index]?.target ?? null
195
- }
196
-
197
- export const pickWorkTarget: PickWorkTarget = (choices) =>
198
- Effect.tryPromise({
199
- try: async () => {
200
- const input = choices
201
- .map((choice, index) => `${index}\t${choice.label}`)
202
- .join("\n")
203
- const process = Bun.spawn(
204
- [
205
- "fzf",
206
- "--ansi",
207
- "--delimiter=\t",
208
- "--with-nth=2..",
209
- "--prompt=Work on> ",
210
- ],
211
- { stdin: new Blob([input]), stdout: "pipe", stderr: "inherit" },
212
- )
213
- const [exitCode, output] = await Promise.all([
214
- process.exited,
215
- new Response(process.stdout).text(),
216
- ])
217
- if (exitCode === 1 || exitCode === 130) return null
218
- if (exitCode !== 0) throw new Error(`fzf exited with code ${exitCode}`)
219
- return parseWorkTargetSelection(output, choices)
220
- },
221
- catch: (cause) =>
222
- new Error("Failed to select a work target with fzf", { cause }),
223
- })
223
+ export const pickWorkTarget: PickWorkTarget = (choices, command) =>
224
+ choose(
225
+ "Work on",
226
+ choices.map((choice, index) => ({
227
+ key: String(index),
228
+ label: choice.label,
229
+ plainLabel: choice.plainLabel,
230
+ value: choice.target,
231
+ })),
232
+ command,
233
+ )
@@ -1,43 +1,30 @@
1
1
  import { Effect } from "effect"
2
2
  import { resolve } from "node:path"
3
- import { FileSystemService } from "../services/FileSystemService"
4
3
  import { WorkbaseService } from "../services/WorkbaseService"
4
+ import { choose } from "../utils/chooser"
5
5
 
6
6
  export type PickWorkbase = (
7
7
  workbases: readonly string[],
8
+ command?: readonly string[],
8
9
  ) => Effect.Effect<string | null, Error>
9
10
 
10
- export const pickWorkbase: PickWorkbase = (workbases) =>
11
- Effect.tryPromise({
12
- try: async () => {
13
- const input = workbases
14
- .map((workbase, index) => `${index}\t${workbase}`)
15
- .join("\n")
16
- const process = Bun.spawn(
17
- ["fzf", "--delimiter=\t", "--with-nth=2..", "--prompt=Workbase> "],
18
- { stdin: new Blob([input]), stdout: "pipe", stderr: "inherit" },
19
- )
20
- const [exitCode, output] = await Promise.all([
21
- process.exited,
22
- new Response(process.stdout).text(),
23
- ])
24
- if (exitCode === 1 || exitCode === 130) return null
25
- if (exitCode !== 0) throw new Error(`fzf exited with code ${exitCode}`)
26
- const index = Number.parseInt(output.split("\t", 1)[0] ?? "", 10)
27
- return workbases[index] ?? null
28
- },
29
- catch: (cause) =>
30
- new Error("Failed to select a workbase with fzf", { cause }),
31
- })
11
+ export const pickWorkbase: PickWorkbase = (workbases, command) =>
12
+ choose(
13
+ "Workbase",
14
+ workbases.map((workbase, index) => ({
15
+ key: String(index),
16
+ label: workbase,
17
+ value: workbase,
18
+ })),
19
+ command,
20
+ )
32
21
 
33
22
  export const resolveWorkbase = (
34
23
  startPath: string,
35
- log: (message: string) => void,
36
24
  pick: PickWorkbase = pickWorkbase,
37
25
  inputAllowed = true,
38
26
  ) =>
39
27
  Effect.gen(function* () {
40
- const fs = yield* FileSystemService
41
28
  const workbase = yield* WorkbaseService
42
29
 
43
30
  return yield* workbase.discover(startPath).pipe(
@@ -59,18 +46,6 @@ export const resolveWorkbase = (
59
46
  )
60
47
  }
61
48
 
62
- const fzf = yield* fs.runCommand(["which", "fzf"], {
63
- captureOutput: true,
64
- })
65
- if (fzf.exitCode !== 0) {
66
- for (const path of registered) log(path)
67
- return yield* Effect.fail(
68
- new Error(
69
- "fzf is required to select a workbase; install fzf or run Agency from a registered workbase",
70
- ),
71
- )
72
- }
73
-
74
49
  const selected = yield* pick(registered)
75
50
  return selected ? yield* workbase.discover(selected) : null
76
51
  }),