@markjaquith/agency 2.9.0 → 2.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.
@@ -0,0 +1,519 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/markjaquith/agency/schemas/agency-graph-v1.schema.json",
4
+ "title": "Agency workbase graph v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": [
8
+ "version",
9
+ "workbase",
10
+ "filters",
11
+ "includes",
12
+ "nodes",
13
+ "edges",
14
+ "summary",
15
+ "validation"
16
+ ],
17
+ "properties": {
18
+ "version": { "const": 1 },
19
+ "workbase": {
20
+ "type": "object",
21
+ "additionalProperties": false,
22
+ "required": ["version"],
23
+ "properties": {
24
+ "version": { "const": 2 },
25
+ "root": { "type": "string" }
26
+ }
27
+ },
28
+ "filters": {
29
+ "type": "object",
30
+ "additionalProperties": false,
31
+ "required": ["ready", "blocked", "statuses", "repositories", "kinds"],
32
+ "properties": {
33
+ "ready": { "type": ["boolean", "null"] },
34
+ "blocked": { "type": ["boolean", "null"] },
35
+ "statuses": {
36
+ "type": "array",
37
+ "items": { "$ref": "#/$defs/status" }
38
+ },
39
+ "repositories": {
40
+ "type": "array",
41
+ "items": { "type": "string" }
42
+ },
43
+ "kinds": {
44
+ "type": "array",
45
+ "items": {
46
+ "enum": ["epic", "task", "phase", "repository", "execution-unit"]
47
+ }
48
+ }
49
+ }
50
+ },
51
+ "includes": {
52
+ "type": "array",
53
+ "items": { "enum": ["bodies", "workspace", "git", "pr"] },
54
+ "uniqueItems": true
55
+ },
56
+ "nodes": {
57
+ "type": "array",
58
+ "items": { "$ref": "#/$defs/node" }
59
+ },
60
+ "edges": {
61
+ "type": "array",
62
+ "items": { "$ref": "#/$defs/edge" }
63
+ },
64
+ "summary": { "$ref": "#/$defs/progress" },
65
+ "validation": {
66
+ "type": "object",
67
+ "additionalProperties": false,
68
+ "required": ["valid", "issues"],
69
+ "properties": {
70
+ "valid": { "type": "boolean" },
71
+ "issues": {
72
+ "type": "array",
73
+ "items": {
74
+ "type": "object",
75
+ "additionalProperties": false,
76
+ "required": ["path", "message"],
77
+ "properties": {
78
+ "path": { "type": "string" },
79
+ "message": { "type": "string" }
80
+ }
81
+ }
82
+ }
83
+ }
84
+ }
85
+ },
86
+ "$defs": {
87
+ "status": {
88
+ "enum": ["open", "working", "delegated", "done", "dropped"]
89
+ },
90
+ "progress": {
91
+ "type": "object",
92
+ "additionalProperties": false,
93
+ "required": [
94
+ "status",
95
+ "total",
96
+ "open",
97
+ "working",
98
+ "delegated",
99
+ "done",
100
+ "dropped",
101
+ "terminal"
102
+ ],
103
+ "properties": {
104
+ "status": { "$ref": "#/$defs/status" },
105
+ "total": { "type": "number" },
106
+ "open": { "type": "number" },
107
+ "working": { "type": "number" },
108
+ "delegated": { "type": "number" },
109
+ "done": { "type": "number" },
110
+ "dropped": { "type": "number" },
111
+ "terminal": { "type": "number" }
112
+ }
113
+ },
114
+ "blocker": {
115
+ "type": "object",
116
+ "additionalProperties": false,
117
+ "required": ["kind", "id", "reason"],
118
+ "properties": {
119
+ "kind": { "enum": ["dependency", "validation", "status"] },
120
+ "id": { "type": "string" },
121
+ "status": { "$ref": "#/$defs/status" },
122
+ "reason": { "type": "string" }
123
+ }
124
+ },
125
+ "readiness": {
126
+ "type": "object",
127
+ "additionalProperties": false,
128
+ "required": ["ready", "blocked", "blockers"],
129
+ "properties": {
130
+ "ready": { "type": "boolean" },
131
+ "blocked": { "type": "boolean" },
132
+ "blockers": {
133
+ "type": "array",
134
+ "items": { "$ref": "#/$defs/blocker" }
135
+ }
136
+ }
137
+ },
138
+ "node": {
139
+ "type": "object",
140
+ "additionalProperties": false,
141
+ "required": [
142
+ "id",
143
+ "kind",
144
+ "key",
145
+ "status",
146
+ "readiness",
147
+ "aggregate",
148
+ "dependents",
149
+ "repositories",
150
+ "data"
151
+ ],
152
+ "properties": {
153
+ "id": { "type": "string" },
154
+ "kind": {
155
+ "enum": ["epic", "task", "phase", "repository", "execution-unit"]
156
+ },
157
+ "key": { "type": "string" },
158
+ "status": {
159
+ "oneOf": [{ "$ref": "#/$defs/status" }, { "type": "null" }]
160
+ },
161
+ "readiness": {
162
+ "oneOf": [{ "$ref": "#/$defs/readiness" }, { "type": "null" }]
163
+ },
164
+ "aggregate": {
165
+ "oneOf": [{ "$ref": "#/$defs/progress" }, { "type": "null" }]
166
+ },
167
+ "dependents": { "type": "array", "items": { "type": "string" } },
168
+ "repositories": { "type": "array", "items": { "type": "string" } },
169
+ "data": {
170
+ "oneOf": [
171
+ { "$ref": "#/$defs/epicData" },
172
+ { "$ref": "#/$defs/singleTaskData" },
173
+ { "$ref": "#/$defs/multiTaskData" },
174
+ { "$ref": "#/$defs/phaseData" },
175
+ { "$ref": "#/$defs/executionData" },
176
+ { "$ref": "#/$defs/repositoryData" }
177
+ ]
178
+ },
179
+ "body": { "type": "string" },
180
+ "workspace": {
181
+ "oneOf": [
182
+ { "$ref": "#/$defs/documentWorkspace" },
183
+ { "$ref": "#/$defs/repositoryWorkspace" },
184
+ { "$ref": "#/$defs/executionWorkspace" }
185
+ ]
186
+ },
187
+ "git": {
188
+ "oneOf": [
189
+ { "$ref": "#/$defs/repositoryGit" },
190
+ { "$ref": "#/$defs/executionGit" }
191
+ ]
192
+ },
193
+ "pr": { "$ref": "#/$defs/pr" }
194
+ },
195
+ "allOf": [
196
+ {
197
+ "if": { "properties": { "kind": { "const": "epic" } } },
198
+ "then": {
199
+ "properties": {
200
+ "data": { "$ref": "#/$defs/epicData" },
201
+ "workspace": { "$ref": "#/$defs/documentWorkspace" },
202
+ "status": { "$ref": "#/$defs/status" },
203
+ "readiness": { "$ref": "#/$defs/readiness" },
204
+ "aggregate": { "$ref": "#/$defs/progress" }
205
+ },
206
+ "not": {
207
+ "anyOf": [{ "required": ["git"] }, { "required": ["pr"] }]
208
+ }
209
+ }
210
+ },
211
+ {
212
+ "if": { "properties": { "kind": { "const": "task" } } },
213
+ "then": {
214
+ "properties": {
215
+ "data": {
216
+ "oneOf": [
217
+ { "$ref": "#/$defs/singleTaskData" },
218
+ { "$ref": "#/$defs/multiTaskData" }
219
+ ]
220
+ },
221
+ "workspace": { "$ref": "#/$defs/documentWorkspace" },
222
+ "status": { "$ref": "#/$defs/status" },
223
+ "readiness": { "$ref": "#/$defs/readiness" },
224
+ "aggregate": { "$ref": "#/$defs/progress" }
225
+ },
226
+ "not": {
227
+ "anyOf": [{ "required": ["git"] }, { "required": ["pr"] }]
228
+ }
229
+ }
230
+ },
231
+ {
232
+ "if": { "properties": { "kind": { "const": "phase" } } },
233
+ "then": {
234
+ "properties": {
235
+ "data": { "$ref": "#/$defs/phaseData" },
236
+ "workspace": { "$ref": "#/$defs/documentWorkspace" },
237
+ "status": { "$ref": "#/$defs/status" },
238
+ "readiness": { "$ref": "#/$defs/readiness" },
239
+ "aggregate": { "$ref": "#/$defs/progress" }
240
+ },
241
+ "not": {
242
+ "anyOf": [{ "required": ["git"] }, { "required": ["pr"] }]
243
+ }
244
+ }
245
+ },
246
+ {
247
+ "if": { "properties": { "kind": { "const": "repository" } } },
248
+ "then": {
249
+ "properties": {
250
+ "data": { "$ref": "#/$defs/repositoryData" },
251
+ "workspace": { "$ref": "#/$defs/repositoryWorkspace" },
252
+ "git": { "$ref": "#/$defs/repositoryGit" },
253
+ "status": { "type": "null" },
254
+ "readiness": { "type": "null" },
255
+ "aggregate": { "type": "null" }
256
+ },
257
+ "not": {
258
+ "anyOf": [{ "required": ["body"] }, { "required": ["pr"] }]
259
+ }
260
+ }
261
+ },
262
+ {
263
+ "if": { "properties": { "kind": { "const": "execution-unit" } } },
264
+ "then": {
265
+ "properties": {
266
+ "data": { "$ref": "#/$defs/executionData" },
267
+ "workspace": { "$ref": "#/$defs/executionWorkspace" },
268
+ "git": { "$ref": "#/$defs/executionGit" },
269
+ "pr": { "$ref": "#/$defs/pr" },
270
+ "status": { "$ref": "#/$defs/status" },
271
+ "readiness": { "$ref": "#/$defs/readiness" },
272
+ "aggregate": { "$ref": "#/$defs/progress" }
273
+ },
274
+ "not": { "required": ["body"] }
275
+ }
276
+ }
277
+ ]
278
+ },
279
+ "repositoryReference": {
280
+ "type": "object",
281
+ "additionalProperties": false,
282
+ "required": ["repo", "ref"],
283
+ "properties": {
284
+ "repo": { "type": "string" },
285
+ "ref": { "type": "string" }
286
+ }
287
+ },
288
+ "dependency": {
289
+ "type": "object",
290
+ "additionalProperties": false,
291
+ "required": ["id"],
292
+ "properties": {
293
+ "id": { "type": "string" },
294
+ "dependsOn": { "type": "array", "items": { "type": "string" } }
295
+ }
296
+ },
297
+ "epicData": {
298
+ "type": "object",
299
+ "additionalProperties": false,
300
+ "required": ["ticketUrl", "repos", "tasks", "sha256"],
301
+ "properties": {
302
+ "ticketUrl": { "type": "string" },
303
+ "description": { "type": "string" },
304
+ "repos": {
305
+ "type": "array",
306
+ "items": { "$ref": "#/$defs/repositoryReference" }
307
+ },
308
+ "tasks": { "type": "array", "items": { "$ref": "#/$defs/dependency" } },
309
+ "sha256": { "type": "string" }
310
+ }
311
+ },
312
+ "singleTaskData": {
313
+ "type": "object",
314
+ "additionalProperties": false,
315
+ "required": [
316
+ "ticketUrl",
317
+ "repo",
318
+ "branch",
319
+ "base",
320
+ "pr",
321
+ "status",
322
+ "sha256"
323
+ ],
324
+ "properties": {
325
+ "ticketUrl": { "type": ["string", "null"] },
326
+ "description": { "type": "string" },
327
+ "epic": { "type": "string" },
328
+ "repo": { "type": "string" },
329
+ "repos": {
330
+ "type": "array",
331
+ "items": { "$ref": "#/$defs/repositoryReference" }
332
+ },
333
+ "branch": { "type": "string" },
334
+ "base": { "type": "string" },
335
+ "pr": { "type": ["string", "null"] },
336
+ "status": { "$ref": "#/$defs/status" },
337
+ "sha256": { "type": "string" }
338
+ }
339
+ },
340
+ "multiTaskData": {
341
+ "type": "object",
342
+ "additionalProperties": false,
343
+ "required": ["ticketUrl", "phases", "sha256"],
344
+ "properties": {
345
+ "ticketUrl": { "type": ["string", "null"] },
346
+ "description": { "type": "string" },
347
+ "epic": { "type": "string" },
348
+ "phases": {
349
+ "type": "array",
350
+ "items": { "$ref": "#/$defs/dependency" }
351
+ },
352
+ "sha256": { "type": "string" }
353
+ }
354
+ },
355
+ "phaseData": {
356
+ "type": "object",
357
+ "additionalProperties": false,
358
+ "required": ["repo", "branch", "base", "pr", "status", "sha256"],
359
+ "properties": {
360
+ "description": { "type": "string" },
361
+ "repo": { "type": "string" },
362
+ "repos": {
363
+ "type": "array",
364
+ "items": { "$ref": "#/$defs/repositoryReference" }
365
+ },
366
+ "branch": { "type": "string" },
367
+ "base": { "type": "string" },
368
+ "pr": { "type": ["string", "null"] },
369
+ "status": { "$ref": "#/$defs/status" },
370
+ "sha256": { "type": "string" }
371
+ }
372
+ },
373
+ "executionData": {
374
+ "type": "object",
375
+ "additionalProperties": false,
376
+ "required": ["taskId", "repo", "branch", "base", "pr", "status"],
377
+ "properties": {
378
+ "taskId": { "type": "string" },
379
+ "phaseId": { "type": "string" },
380
+ "ticketUrl": { "type": ["string", "null"] },
381
+ "epic": { "type": "string" },
382
+ "description": { "type": "string" },
383
+ "repo": { "type": "string" },
384
+ "repos": {
385
+ "type": "array",
386
+ "items": { "$ref": "#/$defs/repositoryReference" }
387
+ },
388
+ "branch": { "type": "string" },
389
+ "base": { "type": "string" },
390
+ "pr": { "type": ["string", "null"] },
391
+ "status": { "$ref": "#/$defs/status" }
392
+ }
393
+ },
394
+ "repositoryData": {
395
+ "type": "object",
396
+ "additionalProperties": false,
397
+ "required": ["alias"],
398
+ "properties": { "alias": { "type": "string" } }
399
+ },
400
+ "documentWorkspace": {
401
+ "type": "object",
402
+ "additionalProperties": false,
403
+ "required": ["documentPath", "directory"],
404
+ "properties": {
405
+ "documentPath": { "type": "string" },
406
+ "directory": { "type": "string" }
407
+ }
408
+ },
409
+ "repositoryWorkspace": {
410
+ "type": "object",
411
+ "additionalProperties": false,
412
+ "required": ["path", "target"],
413
+ "properties": {
414
+ "path": { "type": "string" },
415
+ "target": { "type": ["string", "null"] }
416
+ }
417
+ },
418
+ "executionWorkspace": {
419
+ "type": "object",
420
+ "additionalProperties": false,
421
+ "required": ["codePath", "checkoutPath", "materialized"],
422
+ "properties": {
423
+ "codePath": { "type": "string" },
424
+ "checkoutPath": { "type": "string" },
425
+ "materialized": { "type": "boolean" }
426
+ }
427
+ },
428
+ "repositoryGit": {
429
+ "type": "object",
430
+ "additionalProperties": false,
431
+ "required": ["kind", "remote", "head", "branch"],
432
+ "properties": {
433
+ "kind": { "enum": ["bare", "repository", null] },
434
+ "remote": { "type": ["string", "null"] },
435
+ "head": { "type": ["string", "null"] },
436
+ "branch": { "type": ["string", "null"] }
437
+ }
438
+ },
439
+ "executionGit": {
440
+ "type": "object",
441
+ "additionalProperties": false,
442
+ "required": [
443
+ "branch",
444
+ "base",
445
+ "branchCommit",
446
+ "baseCommit",
447
+ "checkoutCommit",
448
+ "checkoutBranch",
449
+ "dirty"
450
+ ],
451
+ "properties": {
452
+ "branch": { "type": "string" },
453
+ "base": { "type": "string" },
454
+ "branchCommit": { "type": ["string", "null"] },
455
+ "baseCommit": { "type": ["string", "null"] },
456
+ "checkoutCommit": { "type": ["string", "null"] },
457
+ "checkoutBranch": { "type": ["string", "null"] },
458
+ "dirty": { "type": ["boolean", "null"] }
459
+ }
460
+ },
461
+ "pr": {
462
+ "oneOf": [
463
+ {
464
+ "type": "object",
465
+ "additionalProperties": false,
466
+ "required": ["url", "state"],
467
+ "properties": {
468
+ "url": { "type": "null" },
469
+ "state": { "const": "none" }
470
+ }
471
+ },
472
+ {
473
+ "type": "object",
474
+ "additionalProperties": false,
475
+ "required": ["url", "state"],
476
+ "properties": {
477
+ "url": { "type": "string" },
478
+ "state": { "const": "unavailable" }
479
+ }
480
+ },
481
+ {
482
+ "type": "object",
483
+ "additionalProperties": false,
484
+ "required": [
485
+ "recordedUrl",
486
+ "number",
487
+ "state",
488
+ "title",
489
+ "isDraft",
490
+ "headRefName",
491
+ "baseRefName",
492
+ "url"
493
+ ],
494
+ "properties": {
495
+ "recordedUrl": { "type": "string" },
496
+ "number": { "type": "number" },
497
+ "state": { "type": "string" },
498
+ "title": { "type": "string" },
499
+ "isDraft": { "type": "boolean" },
500
+ "headRefName": { "type": "string" },
501
+ "baseRefName": { "type": "string" },
502
+ "url": { "type": "string" }
503
+ }
504
+ }
505
+ ]
506
+ },
507
+ "edge": {
508
+ "type": "object",
509
+ "additionalProperties": false,
510
+ "required": ["id", "kind", "from", "to"],
511
+ "properties": {
512
+ "id": { "type": "string" },
513
+ "kind": { "enum": ["owns", "depends_on", "writes", "references"] },
514
+ "from": { "type": "string" },
515
+ "to": { "type": "string" }
516
+ }
517
+ }
518
+ }
519
+ }
@@ -203,6 +203,12 @@ Follow these invariants:
203
203
  Commands that print Agency-owned results accept `--json` for machine-readable
204
204
  output, including mutations, entity inspection, status, validation, and PR creation.
205
205
 
206
+ Use `agency graph --json` to load the complete, versioned workbase graph without
207
+ walking directories. Filter it with repeatable `--status`, `--repository`, and
208
+ `--kind` options or with `--ready`/`--blocked`. Add durable prose and observational
209
+ details explicitly with `--include bodies|workspace|git|pr`. For large workbases,
210
+ `--jsonl` streams equivalent versioned metadata, node, edge, and end records.
211
+
206
212
  - Multi-phase task frontmatter owns the phase dependency graph.
207
213
  - Epic frontmatter owns the child-task dependency graph.
208
214
  - `pr` is either a GitHub PR URL string or `null`.
@@ -139,11 +139,66 @@ describe("strict CLI parsing", () => {
139
139
  [["pr", "create", "one", "two", "three"], "agency pr create"],
140
140
  [["status", "extra"], "agency status"],
141
141
  [["validate", "one", "two"], "agency validate"],
142
+ [["context", "one", "two"], "agency context"],
143
+ [["graph", "extra"], "agency graph"],
142
144
  ] as const) {
143
145
  expectUsageError([...args], usage)
144
146
  }
145
147
  })
146
148
 
149
+ test("accepts context projections and keeps compact command-local", () => {
150
+ expect(parseCli(["context", ".", "--json", "--compact"])).toMatchObject({
151
+ commandName: "context",
152
+ args: ["."],
153
+ values: { json: true, compact: true },
154
+ })
155
+ expectUsageError(["status", "--compact"], "agency status")
156
+ })
157
+
158
+ test("accepts repeatable graph filters and rejects output conflicts", () => {
159
+ expect(
160
+ parseCli([
161
+ "graph",
162
+ "--json",
163
+ "--status",
164
+ "open",
165
+ "--status",
166
+ "working",
167
+ "--repository",
168
+ "agency",
169
+ "--kind",
170
+ "task",
171
+ "--kind",
172
+ "phase",
173
+ "--include",
174
+ "bodies",
175
+ "--include",
176
+ "git",
177
+ ]).values,
178
+ ).toMatchObject({
179
+ json: true,
180
+ status: ["open", "working"],
181
+ repository: ["agency"],
182
+ kind: ["task", "phase"],
183
+ include: ["bodies", "git"],
184
+ })
185
+ expect(() => parseCli(["graph", "--json", "--jsonl"])).toThrow(
186
+ "cannot be combined",
187
+ )
188
+ expect(() => parseCli(["graph", "--ready", "--blocked"])).toThrow(
189
+ "cannot be combined",
190
+ )
191
+ for (const [option, value] of [
192
+ ["status", "started"],
193
+ ["kind", "worktree"],
194
+ ["include", "secrets"],
195
+ ] as const) {
196
+ expect(() => parseCli(["graph", `--${option}`, value])).toThrow(
197
+ `Invalid '--${option}' value`,
198
+ )
199
+ }
200
+ })
201
+
147
202
  test("reports unknown subcommands with parent usage", () => {
148
203
  expect(() => parseCli(["task", "crate"])).toThrow(
149
204
  "Unknown subcommand 'crate'",
package/src/cli-parser.ts CHANGED
@@ -338,6 +338,52 @@ const commands = {
338
338
  options: ["json"],
339
339
  },
340
340
  },
341
+ context: {
342
+ usage: "agency context [target] [--json] [--compact]",
343
+ options: {
344
+ ...outputOptions,
345
+ compact: { type: "boolean" },
346
+ },
347
+ command: {
348
+ usage: "agency context [target] [--json] [--compact]",
349
+ minArgs: 0,
350
+ maxArgs: 1,
351
+ options: ["json", "compact"],
352
+ },
353
+ },
354
+ graph: {
355
+ usage: "agency graph [options]",
356
+ options: {
357
+ ...outputOptions,
358
+ jsonl: { type: "boolean" },
359
+ ready: { type: "boolean" },
360
+ blocked: { type: "boolean" },
361
+ status: { type: "string", multiple: true },
362
+ repository: { type: "string", multiple: true },
363
+ kind: { type: "string", multiple: true },
364
+ include: { type: "string", multiple: true },
365
+ },
366
+ command: {
367
+ usage: "agency graph [options]",
368
+ minArgs: 0,
369
+ maxArgs: 0,
370
+ options: [
371
+ "json",
372
+ "jsonl",
373
+ "ready",
374
+ "blocked",
375
+ "status",
376
+ "repository",
377
+ "kind",
378
+ "include",
379
+ ],
380
+ repeatable: ["status", "repository", "kind", "include"],
381
+ conflicts: [
382
+ ["json", "jsonl"],
383
+ ["ready", "blocked"],
384
+ ],
385
+ },
386
+ },
341
387
  } satisfies Readonly<Record<string, CommandDefinition>>
342
388
 
343
389
  const rootOptions = commonOptions
@@ -449,6 +495,30 @@ function validateTaskCreate(
449
495
  }
450
496
  }
451
497
 
498
+ function validateGraphOptions(values: ParsedCli["values"], spec: LeafCommand) {
499
+ const allowed = {
500
+ status: new Set(["open", "working", "delegated", "done", "dropped"]),
501
+ kind: new Set(["epic", "task", "phase", "repository", "execution-unit"]),
502
+ include: new Set(["bodies", "workspace", "git", "pr"]),
503
+ }
504
+ for (const [name, accepted] of Object.entries(allowed)) {
505
+ const supplied = values[name]
506
+ const entries = Array.isArray(supplied)
507
+ ? supplied
508
+ : supplied === undefined
509
+ ? []
510
+ : [supplied]
511
+ for (const value of entries) {
512
+ if (typeof value !== "string" || !accepted.has(value)) {
513
+ throw usageError(
514
+ `Invalid '${optionLabel(name)}' value '${String(value)}'. Expected one of: ${[...accepted].join(", ")}.`,
515
+ spec.usage,
516
+ )
517
+ }
518
+ }
519
+ }
520
+ }
521
+
452
522
  export function parseCli(args: readonly string[]): ParsedCli {
453
523
  const commandIndex = findCommandIndex(args)
454
524
  if (commandIndex === -1) {
@@ -576,6 +646,9 @@ export function parseCli(args: readonly string[]): ParsedCli {
576
646
  ) {
577
647
  validateTaskCreate(parsed.values, spec, subcommand === "create")
578
648
  }
649
+ if (commandName === "graph") {
650
+ validateGraphOptions(parsed.values, spec)
651
+ }
579
652
 
580
653
  return {
581
654
  commandName: commandName as keyof typeof commands,