The same 5-verb CRUD pattern is hand-copied across ~20 resources. Measured across cmd/*.go (tests excluded):
{Items, TotalCount} list unmarshal: 20×; search-query builder: 20×; page/limit flag block: 25×; Total: %d footer: 25×.
- Get
[]string{"FIELD","VALUE"} table: 20×.
- Create/update file-read block (
"reading file"/"parsing JSON file"): 58×.
"use --file to provide update body": 21×; delete pattern: 20×; getOutputFmt()=="json" guard: 50×.
cmd/services.go, cmd/data_sources.go, cmd/voice_groups.go are ≥90% identical modulo path/columns. A plain CRUD resource costs ~220 lines today.
Proposal — small shared runner file (cmd/runners.go, ~120-150 lines), NOT codegen. Go 1.21 generics keep typed structs at the call site:
func readJSONBody(file string) (interface{}, error)
func runList[T any](path string, page, limit int, search, searchField string, headers []string, row func(T) []string) error
func runGet[T any](path string, rows func(T) [][]string) error
func runDelete(path, label, id string) error
func runUpdateByBody(path, idKey, id string, numeric bool, file string) error
func runUpdateByURL(path, file string) error
What varies (columns, json.Number handling, truncation widths, key type id/name/email, two update-routing styles) lives in the closures/params. Special verbs (tools get ID-fallback, voice-groups sample, outbound upload, sessions audio, insights activate) stay hand-written.
Saves ~1,700-2,000 net lines (15-20% of non-test cmd/). Risk low-medium: behavior is pinned by the 2.7k-line stub suite (method/path/body per command); migrate 2-3 files per PR, old/new coexist. Important: the spec-sync skill templates new resources by copying an existing cmd file — update .claude/skills/syllable-sync/SKILL.md and AGENTS.md in the same PR that converts the first file. Effort: M (spread over PRs).
The same 5-verb CRUD pattern is hand-copied across ~20 resources. Measured across
cmd/*.go(tests excluded):{Items, TotalCount}list unmarshal: 20×; search-query builder: 20×; page/limit flag block: 25×;Total: %dfooter: 25×.[]string{"FIELD","VALUE"}table: 20×."reading file"/"parsing JSON file"): 58×."use --file to provide update body": 21×; delete pattern: 20×;getOutputFmt()=="json"guard: 50×.cmd/services.go,cmd/data_sources.go,cmd/voice_groups.goare ≥90% identical modulo path/columns. A plain CRUD resource costs ~220 lines today.Proposal — small shared runner file (
cmd/runners.go, ~120-150 lines), NOT codegen. Go 1.21 generics keep typed structs at the call site:What varies (columns,
json.Numberhandling, truncation widths, key type id/name/email, two update-routing styles) lives in the closures/params. Special verbs (tools get ID-fallback, voice-groups sample, outbound upload, sessions audio, insights activate) stay hand-written.Saves ~1,700-2,000 net lines (15-20% of non-test
cmd/). Risk low-medium: behavior is pinned by the 2.7k-line stub suite (method/path/body per command); migrate 2-3 files per PR, old/new coexist. Important: the spec-sync skill templates new resources by copying an existing cmd file — update.claude/skills/syllable-sync/SKILL.mdandAGENTS.mdin the same PR that converts the first file. Effort: M (spread over PRs).