@bdsqqq/lnr-cli 1.6.0 → 2.0.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.
Files changed (47) hide show
  1. package/package.json +2 -3
  2. package/src/bench-lnr-overhead.ts +160 -0
  3. package/src/e2e-mutations.test.ts +378 -0
  4. package/src/e2e-readonly.test.ts +103 -0
  5. package/src/generated/doc.ts +270 -0
  6. package/src/generated/issue.ts +807 -0
  7. package/src/generated/label.ts +273 -0
  8. package/src/generated/project.ts +596 -0
  9. package/src/generated/template.ts +157 -0
  10. package/src/hand-crafted/issue.ts +27 -0
  11. package/src/lib/adapters/doc.ts +14 -0
  12. package/src/lib/adapters/index.ts +4 -0
  13. package/src/lib/adapters/issue.ts +32 -0
  14. package/src/lib/adapters/label.ts +20 -0
  15. package/src/lib/adapters/project.ts +23 -0
  16. package/src/lib/arktype-config.ts +18 -0
  17. package/src/lib/command-introspection.ts +97 -0
  18. package/src/lib/dispatch-effects.test.ts +297 -0
  19. package/src/lib/error.ts +37 -1
  20. package/src/lib/operation-spec.test.ts +317 -0
  21. package/src/lib/operation-spec.ts +11 -0
  22. package/src/lib/operation-specs.ts +21 -0
  23. package/src/lib/output.test.ts +3 -1
  24. package/src/lib/output.ts +1 -296
  25. package/src/lib/renderers/comments.ts +300 -0
  26. package/src/lib/renderers/detail.ts +61 -0
  27. package/src/lib/renderers/index.ts +2 -0
  28. package/src/router/agent-sessions.ts +253 -0
  29. package/src/router/auth.ts +6 -5
  30. package/src/router/config.ts +7 -6
  31. package/src/router/contract.test.ts +364 -0
  32. package/src/router/cycles.ts +372 -95
  33. package/src/router/git-automation-states.ts +355 -0
  34. package/src/router/git-automation-target-branches.ts +309 -0
  35. package/src/router/index.ts +26 -8
  36. package/src/router/initiatives.ts +260 -0
  37. package/src/router/me.ts +8 -7
  38. package/src/router/notifications.ts +176 -0
  39. package/src/router/roadmaps.ts +172 -0
  40. package/src/router/search.ts +7 -6
  41. package/src/router/teams.ts +82 -24
  42. package/src/router/users.ts +126 -0
  43. package/src/router/views.ts +399 -0
  44. package/src/router/docs.ts +0 -153
  45. package/src/router/issues.ts +0 -606
  46. package/src/router/labels.ts +0 -192
  47. package/src/router/projects.ts +0 -220
@@ -0,0 +1,355 @@
1
+ import type { OperationSpec } from "../lib/operation-spec";
2
+ import "../lib/arktype-config";
3
+ import { type } from "arktype";
4
+ import {
5
+ getClient,
6
+ listGitAutomationStates,
7
+ createGitAutomationState,
8
+ updateGitAutomationState,
9
+ deleteGitAutomationState,
10
+ findTeamByKeyOrName,
11
+ getTeamStates,
12
+ type GitAutomationState,
13
+ type GitAutomationEvent,
14
+ } from "@bdsqqq/lnr-core";
15
+ import { router, procedure } from "./trpc";
16
+ import { exitWithError, handleApiError, EXIT_CODES } from "../lib/error";
17
+ import {
18
+ outputJson,
19
+ outputQuiet,
20
+ outputTable,
21
+ getOutputFormat,
22
+ type OutputOptions,
23
+ type TableColumn,
24
+ } from "../lib/output";
25
+
26
+ const gitAutomationEvents = ["draft", "merge", "mergeable", "review", "start"] as const;
27
+
28
+ export const listGitAutomationStatesInput = type({
29
+ team: type("string").describe("team key"),
30
+ "json?": type("boolean").describe("output as json"),
31
+ "quiet?": type("boolean").describe("output ids only"),
32
+ "verbose?": type("boolean").describe("show all columns"),
33
+ });
34
+
35
+ export const gitAutomationStateInput = type({
36
+ idOrEvent: type("string").configure({ positional: true }).describe("automation id, event name, or 'new'"),
37
+ team: type("string").describe("team key"),
38
+ "event?": type("'draft' | 'merge' | 'mergeable' | 'review' | 'start'").describe("git event: draft, merge, mergeable, review, start"),
39
+ "state?": type("string").describe("workflow state name to transition to"),
40
+ "branch?": type("string").describe("target branch ID"),
41
+ "delete?": type("boolean").describe("delete the automation"),
42
+ "json?": type("boolean").describe("output as json"),
43
+ "quiet?": type("boolean").describe("output ids only"),
44
+ "verbose?": type("boolean").describe("show all columns"),
45
+ });
46
+
47
+ type GitAutomationStateCliInput = typeof gitAutomationStateInput.infer;
48
+
49
+ const automationColumns: TableColumn<GitAutomationState>[] = [
50
+ { header: "EVENT", value: (a) => a.event, width: 12 },
51
+ { header: "STATE", value: (a) => a.stateName ?? "(no action)", width: 20 },
52
+ { header: "BRANCH", value: (a) => a.targetBranchPattern ?? "(all)", width: 20 },
53
+ ];
54
+
55
+ const verboseAutomationColumns: TableColumn<GitAutomationState>[] = [
56
+ ...automationColumns,
57
+ { header: "ID", value: (a) => a.id, width: 36 },
58
+ ];
59
+
60
+ export const gitAutomationStateOperations = ["create", "read", "update", "delete"] as const;
61
+ type Operation = (typeof gitAutomationStateOperations)[number];
62
+
63
+ export const gitAutomationStateMutationFlags: readonly (keyof GitAutomationStateCliInput)[] = [
64
+ "event", "state", "branch"
65
+ ] as const;
66
+
67
+ export function inferOperation(input: GitAutomationStateCliInput): Operation {
68
+ if (input.idOrEvent === "new") return "create";
69
+ if (input.delete) return "delete";
70
+
71
+ for (const flag of gitAutomationStateMutationFlags) {
72
+ if (input[flag] !== undefined) return "update";
73
+ }
74
+
75
+ return "read";
76
+ }
77
+
78
+ export const gitAutomationStateOperationSpec: OperationSpec<GitAutomationStateCliInput, Operation> = {
79
+ command: "git-automation",
80
+ operations: gitAutomationStateOperations,
81
+ mutationFlags: gitAutomationStateMutationFlags,
82
+ inferOperation,
83
+ };
84
+
85
+ async function handleListGitAutomationStates(
86
+ input: typeof listGitAutomationStatesInput.infer
87
+ ): Promise<void> {
88
+ try {
89
+ const client = getClient();
90
+ const automations = await listGitAutomationStates(client, input.team);
91
+
92
+ const outputOpts: OutputOptions = {
93
+ format: input.json ? "json" : input.quiet ? "quiet" : undefined,
94
+ verbose: input.verbose,
95
+ };
96
+ const format = getOutputFormat(outputOpts);
97
+
98
+ if (format === "json") {
99
+ outputJson(automations);
100
+ return;
101
+ }
102
+
103
+ if (format === "quiet") {
104
+ outputQuiet(automations.map((a) => a.id));
105
+ return;
106
+ }
107
+
108
+ const columns = input.verbose ? verboseAutomationColumns : automationColumns;
109
+ outputTable(automations, columns, outputOpts);
110
+ } catch (error) {
111
+ handleApiError(error);
112
+ }
113
+ }
114
+
115
+ async function handleShowGitAutomationState(
116
+ idOrEvent: string,
117
+ input: GitAutomationStateCliInput
118
+ ): Promise<void> {
119
+ try {
120
+ const client = getClient();
121
+
122
+ const outputOpts: OutputOptions = {
123
+ format: input.json ? "json" : input.quiet ? "quiet" : undefined,
124
+ verbose: input.verbose,
125
+ };
126
+ const format = getOutputFormat(outputOpts);
127
+
128
+ const automations = await listGitAutomationStates(client, input.team);
129
+ let automation: GitAutomationState | null = null;
130
+
131
+ if (gitAutomationEvents.includes(idOrEvent as GitAutomationEvent)) {
132
+ automation = automations.find((a) => a.event === idOrEvent) ?? null;
133
+ } else {
134
+ automation = automations.find((a) => a.id === idOrEvent) ?? null;
135
+ }
136
+
137
+ if (!automation) {
138
+ exitWithError(
139
+ `git automation "${idOrEvent}" not found`,
140
+ `try: lnr git-automations --team ${input.team}`,
141
+ EXIT_CODES.NOT_FOUND
142
+ );
143
+ }
144
+
145
+ if (format === "json") {
146
+ outputJson(automation);
147
+ return;
148
+ }
149
+
150
+ if (format === "quiet") {
151
+ console.log(automation.id);
152
+ return;
153
+ }
154
+
155
+ console.log(`git automation: ${automation.event}`);
156
+ console.log(` state: ${automation.stateName ?? "(no action)"}`);
157
+ console.log(` branch: ${automation.targetBranchPattern ?? "(all branches)"}`);
158
+ if (input.verbose) {
159
+ console.log(` id: ${automation.id}`);
160
+ console.log(` team: ${automation.teamKey ?? automation.teamId}`);
161
+ }
162
+ } catch (error) {
163
+ handleApiError(error);
164
+ }
165
+ }
166
+
167
+ async function handleCreateGitAutomationState(
168
+ input: GitAutomationStateCliInput
169
+ ): Promise<void> {
170
+ if (!input.event) {
171
+ exitWithError(
172
+ "--event is required",
173
+ "usage: lnr git-automation new --team ENG --event merge --state Done"
174
+ );
175
+ }
176
+
177
+ try {
178
+ const client = getClient();
179
+
180
+ const team = await findTeamByKeyOrName(client, input.team);
181
+ if (!team) {
182
+ exitWithError(`team "${input.team}" not found`);
183
+ }
184
+
185
+ let stateId: string | undefined;
186
+ if (input.state) {
187
+ const states = await getTeamStates(client, team.id);
188
+ const state = states.find(
189
+ (s) => s.name.toLowerCase() === input.state?.toLowerCase()
190
+ );
191
+ if (!state) {
192
+ exitWithError(
193
+ `state "${input.state}" not found in team "${input.team}"`,
194
+ `available states: ${states.map((s) => s.name).join(", ")}`
195
+ );
196
+ }
197
+ stateId = state.id;
198
+ }
199
+
200
+ const automation = await createGitAutomationState(client, {
201
+ teamId: team.id,
202
+ event: input.event,
203
+ stateId,
204
+ targetBranchId: input.branch,
205
+ });
206
+
207
+ if (automation) {
208
+ console.log(`created git automation: ${automation.event} → ${automation.stateName ?? "(no action)"}`);
209
+ } else {
210
+ exitWithError("failed to create git automation");
211
+ }
212
+ } catch (error) {
213
+ handleApiError(error);
214
+ }
215
+ }
216
+
217
+ async function handleUpdateGitAutomationState(
218
+ idOrEvent: string,
219
+ input: GitAutomationStateCliInput
220
+ ): Promise<void> {
221
+ try {
222
+ const client = getClient();
223
+
224
+ const automations = await listGitAutomationStates(client, input.team);
225
+ let automation: GitAutomationState | null = null;
226
+
227
+ if (gitAutomationEvents.includes(idOrEvent as GitAutomationEvent)) {
228
+ automation = automations.find((a) => a.event === idOrEvent) ?? null;
229
+ } else {
230
+ automation = automations.find((a) => a.id === idOrEvent) ?? null;
231
+ }
232
+
233
+ if (!automation) {
234
+ exitWithError(
235
+ `git automation "${idOrEvent}" not found`,
236
+ `try: lnr git-automations --team ${input.team}`,
237
+ EXIT_CODES.NOT_FOUND
238
+ );
239
+ }
240
+
241
+ let stateId: string | undefined;
242
+ if (input.state) {
243
+ const team = await findTeamByKeyOrName(client, input.team);
244
+ if (!team) {
245
+ exitWithError(`team "${input.team}" not found`);
246
+ }
247
+ const states = await getTeamStates(client, team.id);
248
+ const state = states.find(
249
+ (s) => s.name.toLowerCase() === input.state?.toLowerCase()
250
+ );
251
+ if (!state) {
252
+ exitWithError(
253
+ `state "${input.state}" not found in team "${input.team}"`,
254
+ `available states: ${states.map((s) => s.name).join(", ")}`
255
+ );
256
+ }
257
+ stateId = state.id;
258
+ }
259
+
260
+ const success = await updateGitAutomationState(client, automation.id, {
261
+ event: input.event,
262
+ stateId,
263
+ targetBranchId: input.branch,
264
+ });
265
+
266
+ if (!success) {
267
+ exitWithError(
268
+ `failed to update git automation "${idOrEvent}"`,
269
+ undefined,
270
+ EXIT_CODES.NOT_FOUND
271
+ );
272
+ }
273
+
274
+ console.log(`updated git automation: ${input.event ?? automation.event}`);
275
+ } catch (error) {
276
+ handleApiError(error);
277
+ }
278
+ }
279
+
280
+ async function handleDeleteGitAutomationState(
281
+ idOrEvent: string,
282
+ input: GitAutomationStateCliInput
283
+ ): Promise<void> {
284
+ try {
285
+ const client = getClient();
286
+
287
+ const automations = await listGitAutomationStates(client, input.team);
288
+ let automation: GitAutomationState | null = null;
289
+
290
+ if (gitAutomationEvents.includes(idOrEvent as GitAutomationEvent)) {
291
+ automation = automations.find((a) => a.event === idOrEvent) ?? null;
292
+ } else {
293
+ automation = automations.find((a) => a.id === idOrEvent) ?? null;
294
+ }
295
+
296
+ if (!automation) {
297
+ exitWithError(
298
+ `git automation "${idOrEvent}" not found`,
299
+ `try: lnr git-automations --team ${input.team}`,
300
+ EXIT_CODES.NOT_FOUND
301
+ );
302
+ }
303
+
304
+ const success = await deleteGitAutomationState(client, automation.id);
305
+
306
+ if (!success) {
307
+ exitWithError(
308
+ `failed to delete git automation "${idOrEvent}"`,
309
+ undefined,
310
+ EXIT_CODES.NOT_FOUND
311
+ );
312
+ }
313
+
314
+ console.log(`deleted git automation: ${automation.event}`);
315
+ } catch (error) {
316
+ handleApiError(error);
317
+ }
318
+ }
319
+
320
+ export const gitAutomationStatesRouter = router({
321
+ "git-automations": procedure
322
+ .meta({
323
+ aliases: { command: ["ga"] },
324
+ description: "list git automation states for a team",
325
+ })
326
+ .input(listGitAutomationStatesInput)
327
+ .query(async ({ input }) => {
328
+ await handleListGitAutomationStates(input);
329
+ }),
330
+
331
+ "git-automation": procedure
332
+ .meta({
333
+ description: "show, create, update, or delete a git automation state",
334
+ })
335
+ .input(gitAutomationStateInput)
336
+ .mutation(async ({ input }) => {
337
+ const operation = inferOperation(input);
338
+
339
+ switch (operation) {
340
+ case "create":
341
+ await handleCreateGitAutomationState(input);
342
+ break;
343
+ case "delete":
344
+ await handleDeleteGitAutomationState(input.idOrEvent, input);
345
+ break;
346
+ case "update":
347
+ await handleUpdateGitAutomationState(input.idOrEvent, input);
348
+ break;
349
+ case "read":
350
+ default:
351
+ await handleShowGitAutomationState(input.idOrEvent, input);
352
+ break;
353
+ }
354
+ }),
355
+ });
@@ -0,0 +1,309 @@
1
+ import type { OperationSpec } from "../lib/operation-spec";
2
+ import "../lib/arktype-config";
3
+ import { type } from "arktype";
4
+ import {
5
+ getClient,
6
+ listGitAutomationTargetBranches,
7
+ createGitAutomationTargetBranch,
8
+ updateGitAutomationTargetBranch,
9
+ deleteGitAutomationTargetBranch,
10
+ findTeamByKeyOrName,
11
+ type GitAutomationTargetBranch,
12
+ } from "@bdsqqq/lnr-core";
13
+ import { router, procedure } from "./trpc";
14
+ import { exitWithError, handleApiError, EXIT_CODES } from "../lib/error";
15
+ import {
16
+ outputJson,
17
+ outputQuiet,
18
+ outputTable,
19
+ getOutputFormat,
20
+ type OutputOptions,
21
+ type TableColumn,
22
+ } from "../lib/output";
23
+
24
+ export const listGitAutomationTargetBranchesInput = type({
25
+ team: type("string").describe("team key"),
26
+ "json?": type("boolean").describe("output as json"),
27
+ "quiet?": type("boolean").describe("output ids only"),
28
+ "verbose?": type("boolean").describe("show all columns"),
29
+ });
30
+
31
+ export const gitAutomationTargetBranchInput = type({
32
+ patternOrId: type("string").configure({ positional: true }).describe("branch pattern, id, or 'new'"),
33
+ team: type("string").describe("team key"),
34
+ "pattern?": type("string").describe("branch pattern"),
35
+ "regex?": type("boolean").describe("treat pattern as regex"),
36
+ "delete?": type("boolean").describe("delete the target branch"),
37
+ "json?": type("boolean").describe("output as json"),
38
+ "quiet?": type("boolean").describe("output ids only"),
39
+ "verbose?": type("boolean").describe("show all columns"),
40
+ });
41
+
42
+ type GitAutomationTargetBranchCliInput = typeof gitAutomationTargetBranchInput.infer;
43
+
44
+ const branchColumns: TableColumn<GitAutomationTargetBranch>[] = [
45
+ { header: "PATTERN", value: (b) => b.branchPattern, width: 30 },
46
+ { header: "REGEX", value: (b) => (b.isRegex ? "yes" : "no"), width: 8 },
47
+ ];
48
+
49
+ const verboseBranchColumns: TableColumn<GitAutomationTargetBranch>[] = [
50
+ ...branchColumns,
51
+ { header: "ID", value: (b) => b.id, width: 36 },
52
+ ];
53
+
54
+ export const gitAutomationTargetBranchOperations = ["create", "read", "update", "delete"] as const;
55
+ type Operation = (typeof gitAutomationTargetBranchOperations)[number];
56
+
57
+ export const gitAutomationTargetBranchMutationFlags: readonly (keyof GitAutomationTargetBranchCliInput)[] = [
58
+ "pattern", "regex"
59
+ ] as const;
60
+
61
+ export function inferOperation(input: GitAutomationTargetBranchCliInput): Operation {
62
+ if (input.patternOrId === "new") return "create";
63
+ if (input.delete) return "delete";
64
+
65
+ for (const flag of gitAutomationTargetBranchMutationFlags) {
66
+ if (input[flag] !== undefined) return "update";
67
+ }
68
+
69
+ return "read";
70
+ }
71
+
72
+ export const gitAutomationTargetBranchOperationSpec: OperationSpec<GitAutomationTargetBranchCliInput, Operation> = {
73
+ command: "git-branch",
74
+ operations: gitAutomationTargetBranchOperations,
75
+ mutationFlags: gitAutomationTargetBranchMutationFlags,
76
+ inferOperation,
77
+ };
78
+
79
+ async function handleListGitAutomationTargetBranches(
80
+ input: typeof listGitAutomationTargetBranchesInput.infer
81
+ ): Promise<void> {
82
+ try {
83
+ const client = getClient();
84
+ const branches = await listGitAutomationTargetBranches(client, input.team);
85
+
86
+ const outputOpts: OutputOptions = {
87
+ format: input.json ? "json" : input.quiet ? "quiet" : undefined,
88
+ verbose: input.verbose,
89
+ };
90
+ const format = getOutputFormat(outputOpts);
91
+
92
+ if (format === "json") {
93
+ outputJson(branches);
94
+ return;
95
+ }
96
+
97
+ if (format === "quiet") {
98
+ outputQuiet(branches.map((b) => b.id));
99
+ return;
100
+ }
101
+
102
+ const columns = input.verbose ? verboseBranchColumns : branchColumns;
103
+ outputTable(branches, columns, outputOpts);
104
+ } catch (error) {
105
+ handleApiError(error);
106
+ }
107
+ }
108
+
109
+ async function handleShowGitAutomationTargetBranch(
110
+ patternOrId: string,
111
+ input: GitAutomationTargetBranchCliInput
112
+ ): Promise<void> {
113
+ try {
114
+ const client = getClient();
115
+
116
+ const outputOpts: OutputOptions = {
117
+ format: input.json ? "json" : input.quiet ? "quiet" : undefined,
118
+ verbose: input.verbose,
119
+ };
120
+ const format = getOutputFormat(outputOpts);
121
+
122
+ const branches = await listGitAutomationTargetBranches(client, input.team);
123
+ let branch: GitAutomationTargetBranch | null = null;
124
+
125
+ branch = branches.find((b) => b.branchPattern === patternOrId) ?? null;
126
+ if (!branch) {
127
+ branch = branches.find((b) => b.id === patternOrId) ?? null;
128
+ }
129
+
130
+ if (!branch) {
131
+ exitWithError(
132
+ `git automation target branch "${patternOrId}" not found`,
133
+ `try: lnr git-branches --team ${input.team}`,
134
+ EXIT_CODES.NOT_FOUND
135
+ );
136
+ }
137
+
138
+ if (format === "json") {
139
+ outputJson(branch);
140
+ return;
141
+ }
142
+
143
+ if (format === "quiet") {
144
+ console.log(branch.id);
145
+ return;
146
+ }
147
+
148
+ console.log(`git automation target branch: ${branch.branchPattern}`);
149
+ console.log(` regex: ${branch.isRegex ? "yes" : "no"}`);
150
+ if (input.verbose) {
151
+ console.log(` id: ${branch.id}`);
152
+ console.log(` team: ${branch.teamKey ?? branch.teamId}`);
153
+ }
154
+ } catch (error) {
155
+ handleApiError(error);
156
+ }
157
+ }
158
+
159
+ async function handleCreateGitAutomationTargetBranch(
160
+ input: GitAutomationTargetBranchCliInput
161
+ ): Promise<void> {
162
+ if (!input.pattern) {
163
+ exitWithError(
164
+ "--pattern is required",
165
+ "usage: lnr git-branch new --team ENG --pattern main"
166
+ );
167
+ }
168
+
169
+ try {
170
+ const client = getClient();
171
+
172
+ const team = await findTeamByKeyOrName(client, input.team);
173
+ if (!team) {
174
+ exitWithError(`team "${input.team}" not found`);
175
+ }
176
+
177
+ const branch = await createGitAutomationTargetBranch(client, {
178
+ teamId: team.id,
179
+ branchPattern: input.pattern,
180
+ isRegex: input.regex,
181
+ });
182
+
183
+ if (branch) {
184
+ console.log(`created git automation target branch: ${branch.branchPattern}`);
185
+ } else {
186
+ exitWithError("failed to create git automation target branch");
187
+ }
188
+ } catch (error) {
189
+ handleApiError(error);
190
+ }
191
+ }
192
+
193
+ async function handleUpdateGitAutomationTargetBranch(
194
+ patternOrId: string,
195
+ input: GitAutomationTargetBranchCliInput
196
+ ): Promise<void> {
197
+ try {
198
+ const client = getClient();
199
+
200
+ const branches = await listGitAutomationTargetBranches(client, input.team);
201
+ let branch: GitAutomationTargetBranch | null = null;
202
+
203
+ branch = branches.find((b) => b.branchPattern === patternOrId) ?? null;
204
+ if (!branch) {
205
+ branch = branches.find((b) => b.id === patternOrId) ?? null;
206
+ }
207
+
208
+ if (!branch) {
209
+ exitWithError(
210
+ `git automation target branch "${patternOrId}" not found`,
211
+ `try: lnr git-branches --team ${input.team}`,
212
+ EXIT_CODES.NOT_FOUND
213
+ );
214
+ }
215
+
216
+ const success = await updateGitAutomationTargetBranch(client, branch.id, {
217
+ branchPattern: input.pattern,
218
+ isRegex: input.regex,
219
+ });
220
+
221
+ if (!success) {
222
+ exitWithError(
223
+ `failed to update git automation target branch "${patternOrId}"`,
224
+ undefined,
225
+ EXIT_CODES.NOT_FOUND
226
+ );
227
+ }
228
+
229
+ console.log(`updated git automation target branch: ${input.pattern ?? branch.branchPattern}`);
230
+ } catch (error) {
231
+ handleApiError(error);
232
+ }
233
+ }
234
+
235
+ async function handleDeleteGitAutomationTargetBranch(
236
+ patternOrId: string,
237
+ input: GitAutomationTargetBranchCliInput
238
+ ): Promise<void> {
239
+ try {
240
+ const client = getClient();
241
+
242
+ const branches = await listGitAutomationTargetBranches(client, input.team);
243
+ let branch: GitAutomationTargetBranch | null = null;
244
+
245
+ branch = branches.find((b) => b.branchPattern === patternOrId) ?? null;
246
+ if (!branch) {
247
+ branch = branches.find((b) => b.id === patternOrId) ?? null;
248
+ }
249
+
250
+ if (!branch) {
251
+ exitWithError(
252
+ `git automation target branch "${patternOrId}" not found`,
253
+ `try: lnr git-branches --team ${input.team}`,
254
+ EXIT_CODES.NOT_FOUND
255
+ );
256
+ }
257
+
258
+ const success = await deleteGitAutomationTargetBranch(client, branch.id);
259
+
260
+ if (!success) {
261
+ exitWithError(
262
+ `failed to delete git automation target branch "${patternOrId}"`,
263
+ undefined,
264
+ EXIT_CODES.NOT_FOUND
265
+ );
266
+ }
267
+
268
+ console.log(`deleted git automation target branch: ${branch.branchPattern}`);
269
+ } catch (error) {
270
+ handleApiError(error);
271
+ }
272
+ }
273
+
274
+ export const gitAutomationTargetBranchesRouter = router({
275
+ "git-branches": procedure
276
+ .meta({
277
+ aliases: { command: ["gb"] },
278
+ description: "list git automation target branches for a team",
279
+ })
280
+ .input(listGitAutomationTargetBranchesInput)
281
+ .query(async ({ input }) => {
282
+ await handleListGitAutomationTargetBranches(input);
283
+ }),
284
+
285
+ "git-branch": procedure
286
+ .meta({
287
+ description: "show, create, update, or delete a git automation target branch",
288
+ })
289
+ .input(gitAutomationTargetBranchInput)
290
+ .mutation(async ({ input }) => {
291
+ const operation = inferOperation(input);
292
+
293
+ switch (operation) {
294
+ case "create":
295
+ await handleCreateGitAutomationTargetBranch(input);
296
+ break;
297
+ case "delete":
298
+ await handleDeleteGitAutomationTargetBranch(input.patternOrId, input);
299
+ break;
300
+ case "update":
301
+ await handleUpdateGitAutomationTargetBranch(input.patternOrId, input);
302
+ break;
303
+ case "read":
304
+ default:
305
+ await handleShowGitAutomationTargetBranch(input.patternOrId, input);
306
+ break;
307
+ }
308
+ }),
309
+ });