diff --git a/README.md b/README.md index 773194e..0b509ed 100644 --- a/README.md +++ b/README.md @@ -87,8 +87,9 @@ fizzy comment create --card 42 --body "Looks good!" # Add comment ### Output Formats ```bash -fizzy board list # JSON output -fizzy board list | jq '.data' # Pipe through jq for raw data +fizzy board list # JSON output +fizzy board list --jq '.data' # Built-in jq filtering (no external jq required) +fizzy board list --jq '[.data[] | {id, name}]' # Extract specific fields ``` ### JSON Envelope diff --git a/SURFACE.txt b/SURFACE.txt index 8dd2fb7..c795130 100644 --- a/SURFACE.txt +++ b/SURFACE.txt @@ -155,6 +155,7 @@ FLAG fizzy --api-url type=string FLAG fizzy --count type=bool FLAG fizzy --help type=bool FLAG fizzy --ids-only type=bool +FLAG fizzy --jq type=string FLAG fizzy --json type=bool FLAG fizzy --limit type=int FLAG fizzy --markdown type=bool @@ -168,6 +169,7 @@ FLAG fizzy account --api-url type=string FLAG fizzy account --count type=bool FLAG fizzy account --help type=bool FLAG fizzy account --ids-only type=bool +FLAG fizzy account --jq type=string FLAG fizzy account --json type=bool FLAG fizzy account --limit type=int FLAG fizzy account --markdown type=bool @@ -182,6 +184,7 @@ FLAG fizzy account entropy --auto_postpone_period_in_days type=int FLAG fizzy account entropy --count type=bool FLAG fizzy account entropy --help type=bool FLAG fizzy account entropy --ids-only type=bool +FLAG fizzy account entropy --jq type=string FLAG fizzy account entropy --json type=bool FLAG fizzy account entropy --limit type=int FLAG fizzy account entropy --markdown type=bool @@ -195,6 +198,7 @@ FLAG fizzy account export-create --api-url type=string FLAG fizzy account export-create --count type=bool FLAG fizzy account export-create --help type=bool FLAG fizzy account export-create --ids-only type=bool +FLAG fizzy account export-create --jq type=string FLAG fizzy account export-create --json type=bool FLAG fizzy account export-create --limit type=int FLAG fizzy account export-create --markdown type=bool @@ -208,6 +212,7 @@ FLAG fizzy account export-show --api-url type=string FLAG fizzy account export-show --count type=bool FLAG fizzy account export-show --help type=bool FLAG fizzy account export-show --ids-only type=bool +FLAG fizzy account export-show --jq type=string FLAG fizzy account export-show --json type=bool FLAG fizzy account export-show --limit type=int FLAG fizzy account export-show --markdown type=bool @@ -221,6 +226,7 @@ FLAG fizzy account help --api-url type=string FLAG fizzy account help --count type=bool FLAG fizzy account help --help type=bool FLAG fizzy account help --ids-only type=bool +FLAG fizzy account help --jq type=string FLAG fizzy account help --json type=bool FLAG fizzy account help --limit type=int FLAG fizzy account help --markdown type=bool @@ -234,6 +240,7 @@ FLAG fizzy account join-code-reset --api-url type=string FLAG fizzy account join-code-reset --count type=bool FLAG fizzy account join-code-reset --help type=bool FLAG fizzy account join-code-reset --ids-only type=bool +FLAG fizzy account join-code-reset --jq type=string FLAG fizzy account join-code-reset --json type=bool FLAG fizzy account join-code-reset --limit type=int FLAG fizzy account join-code-reset --markdown type=bool @@ -247,6 +254,7 @@ FLAG fizzy account join-code-show --api-url type=string FLAG fizzy account join-code-show --count type=bool FLAG fizzy account join-code-show --help type=bool FLAG fizzy account join-code-show --ids-only type=bool +FLAG fizzy account join-code-show --jq type=string FLAG fizzy account join-code-show --json type=bool FLAG fizzy account join-code-show --limit type=int FLAG fizzy account join-code-show --markdown type=bool @@ -260,6 +268,7 @@ FLAG fizzy account join-code-update --api-url type=string FLAG fizzy account join-code-update --count type=bool FLAG fizzy account join-code-update --help type=bool FLAG fizzy account join-code-update --ids-only type=bool +FLAG fizzy account join-code-update --jq type=string FLAG fizzy account join-code-update --json type=bool FLAG fizzy account join-code-update --limit type=int FLAG fizzy account join-code-update --markdown type=bool @@ -274,6 +283,7 @@ FLAG fizzy account settings-update --api-url type=string FLAG fizzy account settings-update --count type=bool FLAG fizzy account settings-update --help type=bool FLAG fizzy account settings-update --ids-only type=bool +FLAG fizzy account settings-update --jq type=string FLAG fizzy account settings-update --json type=bool FLAG fizzy account settings-update --limit type=int FLAG fizzy account settings-update --markdown type=bool @@ -288,6 +298,7 @@ FLAG fizzy account show --api-url type=string FLAG fizzy account show --count type=bool FLAG fizzy account show --help type=bool FLAG fizzy account show --ids-only type=bool +FLAG fizzy account show --jq type=string FLAG fizzy account show --json type=bool FLAG fizzy account show --limit type=int FLAG fizzy account show --markdown type=bool @@ -301,6 +312,7 @@ FLAG fizzy auth --api-url type=string FLAG fizzy auth --count type=bool FLAG fizzy auth --help type=bool FLAG fizzy auth --ids-only type=bool +FLAG fizzy auth --jq type=string FLAG fizzy auth --json type=bool FLAG fizzy auth --limit type=int FLAG fizzy auth --markdown type=bool @@ -314,6 +326,7 @@ FLAG fizzy auth help --api-url type=string FLAG fizzy auth help --count type=bool FLAG fizzy auth help --help type=bool FLAG fizzy auth help --ids-only type=bool +FLAG fizzy auth help --jq type=string FLAG fizzy auth help --json type=bool FLAG fizzy auth help --limit type=int FLAG fizzy auth help --markdown type=bool @@ -327,6 +340,7 @@ FLAG fizzy auth list --api-url type=string FLAG fizzy auth list --count type=bool FLAG fizzy auth list --help type=bool FLAG fizzy auth list --ids-only type=bool +FLAG fizzy auth list --jq type=string FLAG fizzy auth list --json type=bool FLAG fizzy auth list --limit type=int FLAG fizzy auth list --markdown type=bool @@ -340,6 +354,7 @@ FLAG fizzy auth login --api-url type=string FLAG fizzy auth login --count type=bool FLAG fizzy auth login --help type=bool FLAG fizzy auth login --ids-only type=bool +FLAG fizzy auth login --jq type=string FLAG fizzy auth login --json type=bool FLAG fizzy auth login --limit type=int FLAG fizzy auth login --markdown type=bool @@ -354,6 +369,7 @@ FLAG fizzy auth logout --api-url type=string FLAG fizzy auth logout --count type=bool FLAG fizzy auth logout --help type=bool FLAG fizzy auth logout --ids-only type=bool +FLAG fizzy auth logout --jq type=string FLAG fizzy auth logout --json type=bool FLAG fizzy auth logout --limit type=int FLAG fizzy auth logout --markdown type=bool @@ -367,6 +383,7 @@ FLAG fizzy auth status --api-url type=string FLAG fizzy auth status --count type=bool FLAG fizzy auth status --help type=bool FLAG fizzy auth status --ids-only type=bool +FLAG fizzy auth status --jq type=string FLAG fizzy auth status --json type=bool FLAG fizzy auth status --limit type=int FLAG fizzy auth status --markdown type=bool @@ -380,6 +397,7 @@ FLAG fizzy auth switch --api-url type=string FLAG fizzy auth switch --count type=bool FLAG fizzy auth switch --help type=bool FLAG fizzy auth switch --ids-only type=bool +FLAG fizzy auth switch --jq type=string FLAG fizzy auth switch --json type=bool FLAG fizzy auth switch --limit type=int FLAG fizzy auth switch --markdown type=bool @@ -393,6 +411,7 @@ FLAG fizzy board --api-url type=string FLAG fizzy board --count type=bool FLAG fizzy board --help type=bool FLAG fizzy board --ids-only type=bool +FLAG fizzy board --jq type=string FLAG fizzy board --json type=bool FLAG fizzy board --limit type=int FLAG fizzy board --markdown type=bool @@ -408,6 +427,7 @@ FLAG fizzy board closed --board type=string FLAG fizzy board closed --count type=bool FLAG fizzy board closed --help type=bool FLAG fizzy board closed --ids-only type=bool +FLAG fizzy board closed --jq type=string FLAG fizzy board closed --json type=bool FLAG fizzy board closed --limit type=int FLAG fizzy board closed --markdown type=bool @@ -424,6 +444,7 @@ FLAG fizzy board create --auto_postpone_period_in_days type=int FLAG fizzy board create --count type=bool FLAG fizzy board create --help type=bool FLAG fizzy board create --ids-only type=bool +FLAG fizzy board create --jq type=string FLAG fizzy board create --json type=bool FLAG fizzy board create --limit type=int FLAG fizzy board create --markdown type=bool @@ -438,6 +459,7 @@ FLAG fizzy board delete --api-url type=string FLAG fizzy board delete --count type=bool FLAG fizzy board delete --help type=bool FLAG fizzy board delete --ids-only type=bool +FLAG fizzy board delete --jq type=string FLAG fizzy board delete --json type=bool FLAG fizzy board delete --limit type=int FLAG fizzy board delete --markdown type=bool @@ -452,6 +474,7 @@ FLAG fizzy board entropy --auto_postpone_period_in_days type=int FLAG fizzy board entropy --count type=bool FLAG fizzy board entropy --help type=bool FLAG fizzy board entropy --ids-only type=bool +FLAG fizzy board entropy --jq type=string FLAG fizzy board entropy --json type=bool FLAG fizzy board entropy --limit type=int FLAG fizzy board entropy --markdown type=bool @@ -465,6 +488,7 @@ FLAG fizzy board help --api-url type=string FLAG fizzy board help --count type=bool FLAG fizzy board help --help type=bool FLAG fizzy board help --ids-only type=bool +FLAG fizzy board help --jq type=string FLAG fizzy board help --json type=bool FLAG fizzy board help --limit type=int FLAG fizzy board help --markdown type=bool @@ -479,6 +503,7 @@ FLAG fizzy board involvement --count type=bool FLAG fizzy board involvement --help type=bool FLAG fizzy board involvement --ids-only type=bool FLAG fizzy board involvement --involvement type=string +FLAG fizzy board involvement --jq type=string FLAG fizzy board involvement --json type=bool FLAG fizzy board involvement --limit type=int FLAG fizzy board involvement --markdown type=bool @@ -493,6 +518,7 @@ FLAG fizzy board list --api-url type=string FLAG fizzy board list --count type=bool FLAG fizzy board list --help type=bool FLAG fizzy board list --ids-only type=bool +FLAG fizzy board list --jq type=string FLAG fizzy board list --json type=bool FLAG fizzy board list --limit type=int FLAG fizzy board list --markdown type=bool @@ -509,6 +535,7 @@ FLAG fizzy board postponed --board type=string FLAG fizzy board postponed --count type=bool FLAG fizzy board postponed --help type=bool FLAG fizzy board postponed --ids-only type=bool +FLAG fizzy board postponed --jq type=string FLAG fizzy board postponed --json type=bool FLAG fizzy board postponed --limit type=int FLAG fizzy board postponed --markdown type=bool @@ -523,6 +550,7 @@ FLAG fizzy board publish --api-url type=string FLAG fizzy board publish --count type=bool FLAG fizzy board publish --help type=bool FLAG fizzy board publish --ids-only type=bool +FLAG fizzy board publish --jq type=string FLAG fizzy board publish --json type=bool FLAG fizzy board publish --limit type=int FLAG fizzy board publish --markdown type=bool @@ -536,6 +564,7 @@ FLAG fizzy board show --api-url type=string FLAG fizzy board show --count type=bool FLAG fizzy board show --help type=bool FLAG fizzy board show --ids-only type=bool +FLAG fizzy board show --jq type=string FLAG fizzy board show --json type=bool FLAG fizzy board show --limit type=int FLAG fizzy board show --markdown type=bool @@ -551,6 +580,7 @@ FLAG fizzy board stream --board type=string FLAG fizzy board stream --count type=bool FLAG fizzy board stream --help type=bool FLAG fizzy board stream --ids-only type=bool +FLAG fizzy board stream --jq type=string FLAG fizzy board stream --json type=bool FLAG fizzy board stream --limit type=int FLAG fizzy board stream --markdown type=bool @@ -565,6 +595,7 @@ FLAG fizzy board unpublish --api-url type=string FLAG fizzy board unpublish --count type=bool FLAG fizzy board unpublish --help type=bool FLAG fizzy board unpublish --ids-only type=bool +FLAG fizzy board unpublish --jq type=string FLAG fizzy board unpublish --json type=bool FLAG fizzy board unpublish --limit type=int FLAG fizzy board unpublish --markdown type=bool @@ -580,6 +611,7 @@ FLAG fizzy board update --auto_postpone_period_in_days type=int FLAG fizzy board update --count type=bool FLAG fizzy board update --help type=bool FLAG fizzy board update --ids-only type=bool +FLAG fizzy board update --jq type=string FLAG fizzy board update --json type=bool FLAG fizzy board update --limit type=int FLAG fizzy board update --markdown type=bool @@ -594,6 +626,7 @@ FLAG fizzy card --api-url type=string FLAG fizzy card --count type=bool FLAG fizzy card --help type=bool FLAG fizzy card --ids-only type=bool +FLAG fizzy card --jq type=string FLAG fizzy card --json type=bool FLAG fizzy card --limit type=int FLAG fizzy card --markdown type=bool @@ -607,6 +640,7 @@ FLAG fizzy card assign --api-url type=string FLAG fizzy card assign --count type=bool FLAG fizzy card assign --help type=bool FLAG fizzy card assign --ids-only type=bool +FLAG fizzy card assign --jq type=string FLAG fizzy card assign --json type=bool FLAG fizzy card assign --limit type=int FLAG fizzy card assign --markdown type=bool @@ -621,6 +655,7 @@ FLAG fizzy card attachments --api-url type=string FLAG fizzy card attachments --count type=bool FLAG fizzy card attachments --help type=bool FLAG fizzy card attachments --ids-only type=bool +FLAG fizzy card attachments --jq type=string FLAG fizzy card attachments --json type=bool FLAG fizzy card attachments --limit type=int FLAG fizzy card attachments --markdown type=bool @@ -635,6 +670,7 @@ FLAG fizzy card attachments download --count type=bool FLAG fizzy card attachments download --help type=bool FLAG fizzy card attachments download --ids-only type=bool FLAG fizzy card attachments download --include-comments type=bool +FLAG fizzy card attachments download --jq type=string FLAG fizzy card attachments download --json type=bool FLAG fizzy card attachments download --limit type=int FLAG fizzy card attachments download --markdown type=bool @@ -649,6 +685,7 @@ FLAG fizzy card attachments help --api-url type=string FLAG fizzy card attachments help --count type=bool FLAG fizzy card attachments help --help type=bool FLAG fizzy card attachments help --ids-only type=bool +FLAG fizzy card attachments help --jq type=string FLAG fizzy card attachments help --json type=bool FLAG fizzy card attachments help --limit type=int FLAG fizzy card attachments help --markdown type=bool @@ -663,6 +700,7 @@ FLAG fizzy card attachments show --count type=bool FLAG fizzy card attachments show --help type=bool FLAG fizzy card attachments show --ids-only type=bool FLAG fizzy card attachments show --include-comments type=bool +FLAG fizzy card attachments show --jq type=string FLAG fizzy card attachments show --json type=bool FLAG fizzy card attachments show --limit type=int FLAG fizzy card attachments show --markdown type=bool @@ -676,6 +714,7 @@ FLAG fizzy card close --api-url type=string FLAG fizzy card close --count type=bool FLAG fizzy card close --help type=bool FLAG fizzy card close --ids-only type=bool +FLAG fizzy card close --jq type=string FLAG fizzy card close --json type=bool FLAG fizzy card close --limit type=int FLAG fizzy card close --markdown type=bool @@ -690,6 +729,7 @@ FLAG fizzy card column --column type=string FLAG fizzy card column --count type=bool FLAG fizzy card column --help type=bool FLAG fizzy card column --ids-only type=bool +FLAG fizzy card column --jq type=string FLAG fizzy card column --json type=bool FLAG fizzy card column --limit type=int FLAG fizzy card column --markdown type=bool @@ -708,6 +748,7 @@ FLAG fizzy card create --description_file type=string FLAG fizzy card create --help type=bool FLAG fizzy card create --ids-only type=bool FLAG fizzy card create --image type=string +FLAG fizzy card create --jq type=string FLAG fizzy card create --json type=bool FLAG fizzy card create --limit type=int FLAG fizzy card create --markdown type=bool @@ -722,6 +763,7 @@ FLAG fizzy card delete --api-url type=string FLAG fizzy card delete --count type=bool FLAG fizzy card delete --help type=bool FLAG fizzy card delete --ids-only type=bool +FLAG fizzy card delete --jq type=string FLAG fizzy card delete --json type=bool FLAG fizzy card delete --limit type=int FLAG fizzy card delete --markdown type=bool @@ -735,6 +777,7 @@ FLAG fizzy card golden --api-url type=string FLAG fizzy card golden --count type=bool FLAG fizzy card golden --help type=bool FLAG fizzy card golden --ids-only type=bool +FLAG fizzy card golden --jq type=string FLAG fizzy card golden --json type=bool FLAG fizzy card golden --limit type=int FLAG fizzy card golden --markdown type=bool @@ -748,6 +791,7 @@ FLAG fizzy card help --api-url type=string FLAG fizzy card help --count type=bool FLAG fizzy card help --help type=bool FLAG fizzy card help --ids-only type=bool +FLAG fizzy card help --jq type=string FLAG fizzy card help --json type=bool FLAG fizzy card help --limit type=int FLAG fizzy card help --markdown type=bool @@ -761,6 +805,7 @@ FLAG fizzy card image-remove --api-url type=string FLAG fizzy card image-remove --count type=bool FLAG fizzy card image-remove --help type=bool FLAG fizzy card image-remove --ids-only type=bool +FLAG fizzy card image-remove --jq type=string FLAG fizzy card image-remove --json type=bool FLAG fizzy card image-remove --limit type=int FLAG fizzy card image-remove --markdown type=bool @@ -783,6 +828,7 @@ FLAG fizzy card list --creator type=string FLAG fizzy card list --help type=bool FLAG fizzy card list --ids-only type=bool FLAG fizzy card list --indexed-by type=string +FLAG fizzy card list --jq type=string FLAG fizzy card list --json type=bool FLAG fizzy card list --limit type=int FLAG fizzy card list --markdown type=bool @@ -801,6 +847,7 @@ FLAG fizzy card mark-read --api-url type=string FLAG fizzy card mark-read --count type=bool FLAG fizzy card mark-read --help type=bool FLAG fizzy card mark-read --ids-only type=bool +FLAG fizzy card mark-read --jq type=string FLAG fizzy card mark-read --json type=bool FLAG fizzy card mark-read --limit type=int FLAG fizzy card mark-read --markdown type=bool @@ -814,6 +861,7 @@ FLAG fizzy card mark-unread --api-url type=string FLAG fizzy card mark-unread --count type=bool FLAG fizzy card mark-unread --help type=bool FLAG fizzy card mark-unread --ids-only type=bool +FLAG fizzy card mark-unread --jq type=string FLAG fizzy card mark-unread --json type=bool FLAG fizzy card mark-unread --limit type=int FLAG fizzy card mark-unread --markdown type=bool @@ -827,6 +875,7 @@ FLAG fizzy card move --api-url type=string FLAG fizzy card move --count type=bool FLAG fizzy card move --help type=bool FLAG fizzy card move --ids-only type=bool +FLAG fizzy card move --jq type=string FLAG fizzy card move --json type=bool FLAG fizzy card move --limit type=int FLAG fizzy card move --markdown type=bool @@ -841,6 +890,7 @@ FLAG fizzy card pin --api-url type=string FLAG fizzy card pin --count type=bool FLAG fizzy card pin --help type=bool FLAG fizzy card pin --ids-only type=bool +FLAG fizzy card pin --jq type=string FLAG fizzy card pin --json type=bool FLAG fizzy card pin --limit type=int FLAG fizzy card pin --markdown type=bool @@ -854,6 +904,7 @@ FLAG fizzy card postpone --api-url type=string FLAG fizzy card postpone --count type=bool FLAG fizzy card postpone --help type=bool FLAG fizzy card postpone --ids-only type=bool +FLAG fizzy card postpone --jq type=string FLAG fizzy card postpone --json type=bool FLAG fizzy card postpone --limit type=int FLAG fizzy card postpone --markdown type=bool @@ -867,6 +918,7 @@ FLAG fizzy card publish --api-url type=string FLAG fizzy card publish --count type=bool FLAG fizzy card publish --help type=bool FLAG fizzy card publish --ids-only type=bool +FLAG fizzy card publish --jq type=string FLAG fizzy card publish --json type=bool FLAG fizzy card publish --limit type=int FLAG fizzy card publish --markdown type=bool @@ -880,6 +932,7 @@ FLAG fizzy card reopen --api-url type=string FLAG fizzy card reopen --count type=bool FLAG fizzy card reopen --help type=bool FLAG fizzy card reopen --ids-only type=bool +FLAG fizzy card reopen --jq type=string FLAG fizzy card reopen --json type=bool FLAG fizzy card reopen --limit type=int FLAG fizzy card reopen --markdown type=bool @@ -893,6 +946,7 @@ FLAG fizzy card self-assign --api-url type=string FLAG fizzy card self-assign --count type=bool FLAG fizzy card self-assign --help type=bool FLAG fizzy card self-assign --ids-only type=bool +FLAG fizzy card self-assign --jq type=string FLAG fizzy card self-assign --json type=bool FLAG fizzy card self-assign --limit type=int FLAG fizzy card self-assign --markdown type=bool @@ -906,6 +960,7 @@ FLAG fizzy card show --api-url type=string FLAG fizzy card show --count type=bool FLAG fizzy card show --help type=bool FLAG fizzy card show --ids-only type=bool +FLAG fizzy card show --jq type=string FLAG fizzy card show --json type=bool FLAG fizzy card show --limit type=int FLAG fizzy card show --markdown type=bool @@ -919,6 +974,7 @@ FLAG fizzy card tag --api-url type=string FLAG fizzy card tag --count type=bool FLAG fizzy card tag --help type=bool FLAG fizzy card tag --ids-only type=bool +FLAG fizzy card tag --jq type=string FLAG fizzy card tag --json type=bool FLAG fizzy card tag --limit type=int FLAG fizzy card tag --markdown type=bool @@ -933,6 +989,7 @@ FLAG fizzy card ungolden --api-url type=string FLAG fizzy card ungolden --count type=bool FLAG fizzy card ungolden --help type=bool FLAG fizzy card ungolden --ids-only type=bool +FLAG fizzy card ungolden --jq type=string FLAG fizzy card ungolden --json type=bool FLAG fizzy card ungolden --limit type=int FLAG fizzy card ungolden --markdown type=bool @@ -946,6 +1003,7 @@ FLAG fizzy card unpin --api-url type=string FLAG fizzy card unpin --count type=bool FLAG fizzy card unpin --help type=bool FLAG fizzy card unpin --ids-only type=bool +FLAG fizzy card unpin --jq type=string FLAG fizzy card unpin --json type=bool FLAG fizzy card unpin --limit type=int FLAG fizzy card unpin --markdown type=bool @@ -959,6 +1017,7 @@ FLAG fizzy card untriage --api-url type=string FLAG fizzy card untriage --count type=bool FLAG fizzy card untriage --help type=bool FLAG fizzy card untriage --ids-only type=bool +FLAG fizzy card untriage --jq type=string FLAG fizzy card untriage --json type=bool FLAG fizzy card untriage --limit type=int FLAG fizzy card untriage --markdown type=bool @@ -972,6 +1031,7 @@ FLAG fizzy card unwatch --api-url type=string FLAG fizzy card unwatch --count type=bool FLAG fizzy card unwatch --help type=bool FLAG fizzy card unwatch --ids-only type=bool +FLAG fizzy card unwatch --jq type=string FLAG fizzy card unwatch --json type=bool FLAG fizzy card unwatch --limit type=int FLAG fizzy card unwatch --markdown type=bool @@ -989,6 +1049,7 @@ FLAG fizzy card update --description_file type=string FLAG fizzy card update --help type=bool FLAG fizzy card update --ids-only type=bool FLAG fizzy card update --image type=string +FLAG fizzy card update --jq type=string FLAG fizzy card update --json type=bool FLAG fizzy card update --limit type=int FLAG fizzy card update --markdown type=bool @@ -1003,6 +1064,7 @@ FLAG fizzy card watch --api-url type=string FLAG fizzy card watch --count type=bool FLAG fizzy card watch --help type=bool FLAG fizzy card watch --ids-only type=bool +FLAG fizzy card watch --jq type=string FLAG fizzy card watch --json type=bool FLAG fizzy card watch --limit type=int FLAG fizzy card watch --markdown type=bool @@ -1016,6 +1078,7 @@ FLAG fizzy column --api-url type=string FLAG fizzy column --count type=bool FLAG fizzy column --help type=bool FLAG fizzy column --ids-only type=bool +FLAG fizzy column --jq type=string FLAG fizzy column --json type=bool FLAG fizzy column --limit type=int FLAG fizzy column --markdown type=bool @@ -1031,6 +1094,7 @@ FLAG fizzy column create --color type=string FLAG fizzy column create --count type=bool FLAG fizzy column create --help type=bool FLAG fizzy column create --ids-only type=bool +FLAG fizzy column create --jq type=string FLAG fizzy column create --json type=bool FLAG fizzy column create --limit type=int FLAG fizzy column create --markdown type=bool @@ -1046,6 +1110,7 @@ FLAG fizzy column delete --board type=string FLAG fizzy column delete --count type=bool FLAG fizzy column delete --help type=bool FLAG fizzy column delete --ids-only type=bool +FLAG fizzy column delete --jq type=string FLAG fizzy column delete --json type=bool FLAG fizzy column delete --limit type=int FLAG fizzy column delete --markdown type=bool @@ -1059,6 +1124,7 @@ FLAG fizzy column help --api-url type=string FLAG fizzy column help --count type=bool FLAG fizzy column help --help type=bool FLAG fizzy column help --ids-only type=bool +FLAG fizzy column help --jq type=string FLAG fizzy column help --json type=bool FLAG fizzy column help --limit type=int FLAG fizzy column help --markdown type=bool @@ -1073,6 +1139,7 @@ FLAG fizzy column list --board type=string FLAG fizzy column list --count type=bool FLAG fizzy column list --help type=bool FLAG fizzy column list --ids-only type=bool +FLAG fizzy column list --jq type=string FLAG fizzy column list --json type=bool FLAG fizzy column list --limit type=int FLAG fizzy column list --markdown type=bool @@ -1086,6 +1153,7 @@ FLAG fizzy column move-left --api-url type=string FLAG fizzy column move-left --count type=bool FLAG fizzy column move-left --help type=bool FLAG fizzy column move-left --ids-only type=bool +FLAG fizzy column move-left --jq type=string FLAG fizzy column move-left --json type=bool FLAG fizzy column move-left --limit type=int FLAG fizzy column move-left --markdown type=bool @@ -1099,6 +1167,7 @@ FLAG fizzy column move-right --api-url type=string FLAG fizzy column move-right --count type=bool FLAG fizzy column move-right --help type=bool FLAG fizzy column move-right --ids-only type=bool +FLAG fizzy column move-right --jq type=string FLAG fizzy column move-right --json type=bool FLAG fizzy column move-right --limit type=int FLAG fizzy column move-right --markdown type=bool @@ -1113,6 +1182,7 @@ FLAG fizzy column show --board type=string FLAG fizzy column show --count type=bool FLAG fizzy column show --help type=bool FLAG fizzy column show --ids-only type=bool +FLAG fizzy column show --jq type=string FLAG fizzy column show --json type=bool FLAG fizzy column show --limit type=int FLAG fizzy column show --markdown type=bool @@ -1128,6 +1198,7 @@ FLAG fizzy column update --color type=string FLAG fizzy column update --count type=bool FLAG fizzy column update --help type=bool FLAG fizzy column update --ids-only type=bool +FLAG fizzy column update --jq type=string FLAG fizzy column update --json type=bool FLAG fizzy column update --limit type=int FLAG fizzy column update --markdown type=bool @@ -1142,6 +1213,7 @@ FLAG fizzy commands --api-url type=string FLAG fizzy commands --count type=bool FLAG fizzy commands --help type=bool FLAG fizzy commands --ids-only type=bool +FLAG fizzy commands --jq type=string FLAG fizzy commands --json type=bool FLAG fizzy commands --limit type=int FLAG fizzy commands --markdown type=bool @@ -1155,6 +1227,7 @@ FLAG fizzy comment --api-url type=string FLAG fizzy comment --count type=bool FLAG fizzy comment --help type=bool FLAG fizzy comment --ids-only type=bool +FLAG fizzy comment --jq type=string FLAG fizzy comment --json type=bool FLAG fizzy comment --limit type=int FLAG fizzy comment --markdown type=bool @@ -1168,6 +1241,7 @@ FLAG fizzy comment attachments --api-url type=string FLAG fizzy comment attachments --count type=bool FLAG fizzy comment attachments --help type=bool FLAG fizzy comment attachments --ids-only type=bool +FLAG fizzy comment attachments --jq type=string FLAG fizzy comment attachments --json type=bool FLAG fizzy comment attachments --limit type=int FLAG fizzy comment attachments --markdown type=bool @@ -1182,6 +1256,7 @@ FLAG fizzy comment attachments download --card type=string FLAG fizzy comment attachments download --count type=bool FLAG fizzy comment attachments download --help type=bool FLAG fizzy comment attachments download --ids-only type=bool +FLAG fizzy comment attachments download --jq type=string FLAG fizzy comment attachments download --json type=bool FLAG fizzy comment attachments download --limit type=int FLAG fizzy comment attachments download --markdown type=bool @@ -1196,6 +1271,7 @@ FLAG fizzy comment attachments help --api-url type=string FLAG fizzy comment attachments help --count type=bool FLAG fizzy comment attachments help --help type=bool FLAG fizzy comment attachments help --ids-only type=bool +FLAG fizzy comment attachments help --jq type=string FLAG fizzy comment attachments help --json type=bool FLAG fizzy comment attachments help --limit type=int FLAG fizzy comment attachments help --markdown type=bool @@ -1210,6 +1286,7 @@ FLAG fizzy comment attachments show --card type=string FLAG fizzy comment attachments show --count type=bool FLAG fizzy comment attachments show --help type=bool FLAG fizzy comment attachments show --ids-only type=bool +FLAG fizzy comment attachments show --jq type=string FLAG fizzy comment attachments show --json type=bool FLAG fizzy comment attachments show --limit type=int FLAG fizzy comment attachments show --markdown type=bool @@ -1227,6 +1304,7 @@ FLAG fizzy comment create --count type=bool FLAG fizzy comment create --created-at type=string FLAG fizzy comment create --help type=bool FLAG fizzy comment create --ids-only type=bool +FLAG fizzy comment create --jq type=string FLAG fizzy comment create --json type=bool FLAG fizzy comment create --limit type=int FLAG fizzy comment create --markdown type=bool @@ -1241,6 +1319,7 @@ FLAG fizzy comment delete --card type=string FLAG fizzy comment delete --count type=bool FLAG fizzy comment delete --help type=bool FLAG fizzy comment delete --ids-only type=bool +FLAG fizzy comment delete --jq type=string FLAG fizzy comment delete --json type=bool FLAG fizzy comment delete --limit type=int FLAG fizzy comment delete --markdown type=bool @@ -1254,6 +1333,7 @@ FLAG fizzy comment help --api-url type=string FLAG fizzy comment help --count type=bool FLAG fizzy comment help --help type=bool FLAG fizzy comment help --ids-only type=bool +FLAG fizzy comment help --jq type=string FLAG fizzy comment help --json type=bool FLAG fizzy comment help --limit type=int FLAG fizzy comment help --markdown type=bool @@ -1269,6 +1349,7 @@ FLAG fizzy comment list --card type=string FLAG fizzy comment list --count type=bool FLAG fizzy comment list --help type=bool FLAG fizzy comment list --ids-only type=bool +FLAG fizzy comment list --jq type=string FLAG fizzy comment list --json type=bool FLAG fizzy comment list --limit type=int FLAG fizzy comment list --markdown type=bool @@ -1284,6 +1365,7 @@ FLAG fizzy comment show --card type=string FLAG fizzy comment show --count type=bool FLAG fizzy comment show --help type=bool FLAG fizzy comment show --ids-only type=bool +FLAG fizzy comment show --jq type=string FLAG fizzy comment show --json type=bool FLAG fizzy comment show --limit type=int FLAG fizzy comment show --markdown type=bool @@ -1300,6 +1382,7 @@ FLAG fizzy comment update --card type=string FLAG fizzy comment update --count type=bool FLAG fizzy comment update --help type=bool FLAG fizzy comment update --ids-only type=bool +FLAG fizzy comment update --jq type=string FLAG fizzy comment update --json type=bool FLAG fizzy comment update --limit type=int FLAG fizzy comment update --markdown type=bool @@ -1313,6 +1396,7 @@ FLAG fizzy completion --api-url type=string FLAG fizzy completion --count type=bool FLAG fizzy completion --help type=bool FLAG fizzy completion --ids-only type=bool +FLAG fizzy completion --jq type=string FLAG fizzy completion --json type=bool FLAG fizzy completion --limit type=int FLAG fizzy completion --markdown type=bool @@ -1326,6 +1410,7 @@ FLAG fizzy help --api-url type=string FLAG fizzy help --count type=bool FLAG fizzy help --help type=bool FLAG fizzy help --ids-only type=bool +FLAG fizzy help --jq type=string FLAG fizzy help --json type=bool FLAG fizzy help --limit type=int FLAG fizzy help --markdown type=bool @@ -1339,6 +1424,7 @@ FLAG fizzy identity --api-url type=string FLAG fizzy identity --count type=bool FLAG fizzy identity --help type=bool FLAG fizzy identity --ids-only type=bool +FLAG fizzy identity --jq type=string FLAG fizzy identity --json type=bool FLAG fizzy identity --limit type=int FLAG fizzy identity --markdown type=bool @@ -1352,6 +1438,7 @@ FLAG fizzy identity help --api-url type=string FLAG fizzy identity help --count type=bool FLAG fizzy identity help --help type=bool FLAG fizzy identity help --ids-only type=bool +FLAG fizzy identity help --jq type=string FLAG fizzy identity help --json type=bool FLAG fizzy identity help --limit type=int FLAG fizzy identity help --markdown type=bool @@ -1365,6 +1452,7 @@ FLAG fizzy identity show --api-url type=string FLAG fizzy identity show --count type=bool FLAG fizzy identity show --help type=bool FLAG fizzy identity show --ids-only type=bool +FLAG fizzy identity show --jq type=string FLAG fizzy identity show --json type=bool FLAG fizzy identity show --limit type=int FLAG fizzy identity show --markdown type=bool @@ -1378,6 +1466,7 @@ FLAG fizzy migrate --api-url type=string FLAG fizzy migrate --count type=bool FLAG fizzy migrate --help type=bool FLAG fizzy migrate --ids-only type=bool +FLAG fizzy migrate --jq type=string FLAG fizzy migrate --json type=bool FLAG fizzy migrate --limit type=int FLAG fizzy migrate --markdown type=bool @@ -1396,6 +1485,7 @@ FLAG fizzy migrate board --ids-only type=bool FLAG fizzy migrate board --include-comments type=bool FLAG fizzy migrate board --include-images type=bool FLAG fizzy migrate board --include-steps type=bool +FLAG fizzy migrate board --jq type=string FLAG fizzy migrate board --json type=bool FLAG fizzy migrate board --limit type=int FLAG fizzy migrate board --markdown type=bool @@ -1410,6 +1500,7 @@ FLAG fizzy migrate help --api-url type=string FLAG fizzy migrate help --count type=bool FLAG fizzy migrate help --help type=bool FLAG fizzy migrate help --ids-only type=bool +FLAG fizzy migrate help --jq type=string FLAG fizzy migrate help --json type=bool FLAG fizzy migrate help --limit type=int FLAG fizzy migrate help --markdown type=bool @@ -1423,6 +1514,7 @@ FLAG fizzy notification --api-url type=string FLAG fizzy notification --count type=bool FLAG fizzy notification --help type=bool FLAG fizzy notification --ids-only type=bool +FLAG fizzy notification --jq type=string FLAG fizzy notification --json type=bool FLAG fizzy notification --limit type=int FLAG fizzy notification --markdown type=bool @@ -1436,6 +1528,7 @@ FLAG fizzy notification help --api-url type=string FLAG fizzy notification help --count type=bool FLAG fizzy notification help --help type=bool FLAG fizzy notification help --ids-only type=bool +FLAG fizzy notification help --jq type=string FLAG fizzy notification help --json type=bool FLAG fizzy notification help --limit type=int FLAG fizzy notification help --markdown type=bool @@ -1450,6 +1543,7 @@ FLAG fizzy notification list --api-url type=string FLAG fizzy notification list --count type=bool FLAG fizzy notification list --help type=bool FLAG fizzy notification list --ids-only type=bool +FLAG fizzy notification list --jq type=string FLAG fizzy notification list --json type=bool FLAG fizzy notification list --limit type=int FLAG fizzy notification list --markdown type=bool @@ -1464,6 +1558,7 @@ FLAG fizzy notification read --api-url type=string FLAG fizzy notification read --count type=bool FLAG fizzy notification read --help type=bool FLAG fizzy notification read --ids-only type=bool +FLAG fizzy notification read --jq type=string FLAG fizzy notification read --json type=bool FLAG fizzy notification read --limit type=int FLAG fizzy notification read --markdown type=bool @@ -1477,6 +1572,7 @@ FLAG fizzy notification read-all --api-url type=string FLAG fizzy notification read-all --count type=bool FLAG fizzy notification read-all --help type=bool FLAG fizzy notification read-all --ids-only type=bool +FLAG fizzy notification read-all --jq type=string FLAG fizzy notification read-all --json type=bool FLAG fizzy notification read-all --limit type=int FLAG fizzy notification read-all --markdown type=bool @@ -1490,6 +1586,7 @@ FLAG fizzy notification settings-show --api-url type=string FLAG fizzy notification settings-show --count type=bool FLAG fizzy notification settings-show --help type=bool FLAG fizzy notification settings-show --ids-only type=bool +FLAG fizzy notification settings-show --jq type=string FLAG fizzy notification settings-show --json type=bool FLAG fizzy notification settings-show --limit type=int FLAG fizzy notification settings-show --markdown type=bool @@ -1504,6 +1601,7 @@ FLAG fizzy notification settings-update --bundle-email-frequency type=string FLAG fizzy notification settings-update --count type=bool FLAG fizzy notification settings-update --help type=bool FLAG fizzy notification settings-update --ids-only type=bool +FLAG fizzy notification settings-update --jq type=string FLAG fizzy notification settings-update --json type=bool FLAG fizzy notification settings-update --limit type=int FLAG fizzy notification settings-update --markdown type=bool @@ -1518,6 +1616,7 @@ FLAG fizzy notification tray --count type=bool FLAG fizzy notification tray --help type=bool FLAG fizzy notification tray --ids-only type=bool FLAG fizzy notification tray --include-read type=bool +FLAG fizzy notification tray --jq type=string FLAG fizzy notification tray --json type=bool FLAG fizzy notification tray --limit type=int FLAG fizzy notification tray --markdown type=bool @@ -1531,6 +1630,7 @@ FLAG fizzy notification unread --api-url type=string FLAG fizzy notification unread --count type=bool FLAG fizzy notification unread --help type=bool FLAG fizzy notification unread --ids-only type=bool +FLAG fizzy notification unread --jq type=string FLAG fizzy notification unread --json type=bool FLAG fizzy notification unread --limit type=int FLAG fizzy notification unread --markdown type=bool @@ -1544,6 +1644,7 @@ FLAG fizzy pin --api-url type=string FLAG fizzy pin --count type=bool FLAG fizzy pin --help type=bool FLAG fizzy pin --ids-only type=bool +FLAG fizzy pin --jq type=string FLAG fizzy pin --json type=bool FLAG fizzy pin --limit type=int FLAG fizzy pin --markdown type=bool @@ -1557,6 +1658,7 @@ FLAG fizzy pin help --api-url type=string FLAG fizzy pin help --count type=bool FLAG fizzy pin help --help type=bool FLAG fizzy pin help --ids-only type=bool +FLAG fizzy pin help --jq type=string FLAG fizzy pin help --json type=bool FLAG fizzy pin help --limit type=int FLAG fizzy pin help --markdown type=bool @@ -1570,6 +1672,7 @@ FLAG fizzy pin list --api-url type=string FLAG fizzy pin list --count type=bool FLAG fizzy pin list --help type=bool FLAG fizzy pin list --ids-only type=bool +FLAG fizzy pin list --jq type=string FLAG fizzy pin list --json type=bool FLAG fizzy pin list --limit type=int FLAG fizzy pin list --markdown type=bool @@ -1583,6 +1686,7 @@ FLAG fizzy reaction --api-url type=string FLAG fizzy reaction --count type=bool FLAG fizzy reaction --help type=bool FLAG fizzy reaction --ids-only type=bool +FLAG fizzy reaction --jq type=string FLAG fizzy reaction --json type=bool FLAG fizzy reaction --limit type=int FLAG fizzy reaction --markdown type=bool @@ -1599,6 +1703,7 @@ FLAG fizzy reaction create --content type=string FLAG fizzy reaction create --count type=bool FLAG fizzy reaction create --help type=bool FLAG fizzy reaction create --ids-only type=bool +FLAG fizzy reaction create --jq type=string FLAG fizzy reaction create --json type=bool FLAG fizzy reaction create --limit type=int FLAG fizzy reaction create --markdown type=bool @@ -1614,6 +1719,7 @@ FLAG fizzy reaction delete --comment type=string FLAG fizzy reaction delete --count type=bool FLAG fizzy reaction delete --help type=bool FLAG fizzy reaction delete --ids-only type=bool +FLAG fizzy reaction delete --jq type=string FLAG fizzy reaction delete --json type=bool FLAG fizzy reaction delete --limit type=int FLAG fizzy reaction delete --markdown type=bool @@ -1627,6 +1733,7 @@ FLAG fizzy reaction help --api-url type=string FLAG fizzy reaction help --count type=bool FLAG fizzy reaction help --help type=bool FLAG fizzy reaction help --ids-only type=bool +FLAG fizzy reaction help --jq type=string FLAG fizzy reaction help --json type=bool FLAG fizzy reaction help --limit type=int FLAG fizzy reaction help --markdown type=bool @@ -1642,6 +1749,7 @@ FLAG fizzy reaction list --comment type=string FLAG fizzy reaction list --count type=bool FLAG fizzy reaction list --help type=bool FLAG fizzy reaction list --ids-only type=bool +FLAG fizzy reaction list --jq type=string FLAG fizzy reaction list --json type=bool FLAG fizzy reaction list --limit type=int FLAG fizzy reaction list --markdown type=bool @@ -1659,6 +1767,7 @@ FLAG fizzy search --count type=bool FLAG fizzy search --help type=bool FLAG fizzy search --ids-only type=bool FLAG fizzy search --indexed-by type=string +FLAG fizzy search --jq type=string FLAG fizzy search --json type=bool FLAG fizzy search --limit type=int FLAG fizzy search --markdown type=bool @@ -1675,6 +1784,7 @@ FLAG fizzy setup --api-url type=string FLAG fizzy setup --count type=bool FLAG fizzy setup --help type=bool FLAG fizzy setup --ids-only type=bool +FLAG fizzy setup --jq type=string FLAG fizzy setup --json type=bool FLAG fizzy setup --limit type=int FLAG fizzy setup --markdown type=bool @@ -1688,6 +1798,7 @@ FLAG fizzy setup claude --api-url type=string FLAG fizzy setup claude --count type=bool FLAG fizzy setup claude --help type=bool FLAG fizzy setup claude --ids-only type=bool +FLAG fizzy setup claude --jq type=string FLAG fizzy setup claude --json type=bool FLAG fizzy setup claude --limit type=int FLAG fizzy setup claude --markdown type=bool @@ -1701,6 +1812,7 @@ FLAG fizzy setup help --api-url type=string FLAG fizzy setup help --count type=bool FLAG fizzy setup help --help type=bool FLAG fizzy setup help --ids-only type=bool +FLAG fizzy setup help --jq type=string FLAG fizzy setup help --json type=bool FLAG fizzy setup help --limit type=int FLAG fizzy setup help --markdown type=bool @@ -1714,6 +1826,7 @@ FLAG fizzy signup --api-url type=string FLAG fizzy signup --count type=bool FLAG fizzy signup --help type=bool FLAG fizzy signup --ids-only type=bool +FLAG fizzy signup --jq type=string FLAG fizzy signup --json type=bool FLAG fizzy signup --limit type=int FLAG fizzy signup --markdown type=bool @@ -1728,6 +1841,7 @@ FLAG fizzy signup complete --api-url type=string FLAG fizzy signup complete --count type=bool FLAG fizzy signup complete --help type=bool FLAG fizzy signup complete --ids-only type=bool +FLAG fizzy signup complete --jq type=string FLAG fizzy signup complete --json type=bool FLAG fizzy signup complete --limit type=int FLAG fizzy signup complete --markdown type=bool @@ -1742,6 +1856,7 @@ FLAG fizzy signup help --api-url type=string FLAG fizzy signup help --count type=bool FLAG fizzy signup help --help type=bool FLAG fizzy signup help --ids-only type=bool +FLAG fizzy signup help --jq type=string FLAG fizzy signup help --json type=bool FLAG fizzy signup help --limit type=int FLAG fizzy signup help --markdown type=bool @@ -1756,6 +1871,7 @@ FLAG fizzy signup start --count type=bool FLAG fizzy signup start --email type=string FLAG fizzy signup start --help type=bool FLAG fizzy signup start --ids-only type=bool +FLAG fizzy signup start --jq type=string FLAG fizzy signup start --json type=bool FLAG fizzy signup start --limit type=int FLAG fizzy signup start --markdown type=bool @@ -1770,6 +1886,7 @@ FLAG fizzy signup verify --code type=string FLAG fizzy signup verify --count type=bool FLAG fizzy signup verify --help type=bool FLAG fizzy signup verify --ids-only type=bool +FLAG fizzy signup verify --jq type=string FLAG fizzy signup verify --json type=bool FLAG fizzy signup verify --limit type=int FLAG fizzy signup verify --markdown type=bool @@ -1784,6 +1901,7 @@ FLAG fizzy skill --api-url type=string FLAG fizzy skill --count type=bool FLAG fizzy skill --help type=bool FLAG fizzy skill --ids-only type=bool +FLAG fizzy skill --jq type=string FLAG fizzy skill --json type=bool FLAG fizzy skill --limit type=int FLAG fizzy skill --markdown type=bool @@ -1797,6 +1915,7 @@ FLAG fizzy skill help --api-url type=string FLAG fizzy skill help --count type=bool FLAG fizzy skill help --help type=bool FLAG fizzy skill help --ids-only type=bool +FLAG fizzy skill help --jq type=string FLAG fizzy skill help --json type=bool FLAG fizzy skill help --limit type=int FLAG fizzy skill help --markdown type=bool @@ -1810,6 +1929,7 @@ FLAG fizzy skill install --api-url type=string FLAG fizzy skill install --count type=bool FLAG fizzy skill install --help type=bool FLAG fizzy skill install --ids-only type=bool +FLAG fizzy skill install --jq type=string FLAG fizzy skill install --json type=bool FLAG fizzy skill install --limit type=int FLAG fizzy skill install --markdown type=bool @@ -1823,6 +1943,7 @@ FLAG fizzy step --api-url type=string FLAG fizzy step --count type=bool FLAG fizzy step --help type=bool FLAG fizzy step --ids-only type=bool +FLAG fizzy step --jq type=string FLAG fizzy step --json type=bool FLAG fizzy step --limit type=int FLAG fizzy step --markdown type=bool @@ -1839,6 +1960,7 @@ FLAG fizzy step create --content type=string FLAG fizzy step create --count type=bool FLAG fizzy step create --help type=bool FLAG fizzy step create --ids-only type=bool +FLAG fizzy step create --jq type=string FLAG fizzy step create --json type=bool FLAG fizzy step create --limit type=int FLAG fizzy step create --markdown type=bool @@ -1853,6 +1975,7 @@ FLAG fizzy step delete --card type=string FLAG fizzy step delete --count type=bool FLAG fizzy step delete --help type=bool FLAG fizzy step delete --ids-only type=bool +FLAG fizzy step delete --jq type=string FLAG fizzy step delete --json type=bool FLAG fizzy step delete --limit type=int FLAG fizzy step delete --markdown type=bool @@ -1866,6 +1989,7 @@ FLAG fizzy step help --api-url type=string FLAG fizzy step help --count type=bool FLAG fizzy step help --help type=bool FLAG fizzy step help --ids-only type=bool +FLAG fizzy step help --jq type=string FLAG fizzy step help --json type=bool FLAG fizzy step help --limit type=int FLAG fizzy step help --markdown type=bool @@ -1880,6 +2004,7 @@ FLAG fizzy step list --card type=string FLAG fizzy step list --count type=bool FLAG fizzy step list --help type=bool FLAG fizzy step list --ids-only type=bool +FLAG fizzy step list --jq type=string FLAG fizzy step list --json type=bool FLAG fizzy step list --limit type=int FLAG fizzy step list --markdown type=bool @@ -1894,6 +2019,7 @@ FLAG fizzy step show --card type=string FLAG fizzy step show --count type=bool FLAG fizzy step show --help type=bool FLAG fizzy step show --ids-only type=bool +FLAG fizzy step show --jq type=string FLAG fizzy step show --json type=bool FLAG fizzy step show --limit type=int FLAG fizzy step show --markdown type=bool @@ -1910,6 +2036,7 @@ FLAG fizzy step update --content type=string FLAG fizzy step update --count type=bool FLAG fizzy step update --help type=bool FLAG fizzy step update --ids-only type=bool +FLAG fizzy step update --jq type=string FLAG fizzy step update --json type=bool FLAG fizzy step update --limit type=int FLAG fizzy step update --markdown type=bool @@ -1924,6 +2051,7 @@ FLAG fizzy tag --api-url type=string FLAG fizzy tag --count type=bool FLAG fizzy tag --help type=bool FLAG fizzy tag --ids-only type=bool +FLAG fizzy tag --jq type=string FLAG fizzy tag --json type=bool FLAG fizzy tag --limit type=int FLAG fizzy tag --markdown type=bool @@ -1937,6 +2065,7 @@ FLAG fizzy tag help --api-url type=string FLAG fizzy tag help --count type=bool FLAG fizzy tag help --help type=bool FLAG fizzy tag help --ids-only type=bool +FLAG fizzy tag help --jq type=string FLAG fizzy tag help --json type=bool FLAG fizzy tag help --limit type=int FLAG fizzy tag help --markdown type=bool @@ -1951,6 +2080,7 @@ FLAG fizzy tag list --api-url type=string FLAG fizzy tag list --count type=bool FLAG fizzy tag list --help type=bool FLAG fizzy tag list --ids-only type=bool +FLAG fizzy tag list --jq type=string FLAG fizzy tag list --json type=bool FLAG fizzy tag list --limit type=int FLAG fizzy tag list --markdown type=bool @@ -1965,6 +2095,7 @@ FLAG fizzy upload --api-url type=string FLAG fizzy upload --count type=bool FLAG fizzy upload --help type=bool FLAG fizzy upload --ids-only type=bool +FLAG fizzy upload --jq type=string FLAG fizzy upload --json type=bool FLAG fizzy upload --limit type=int FLAG fizzy upload --markdown type=bool @@ -1978,6 +2109,7 @@ FLAG fizzy upload file --api-url type=string FLAG fizzy upload file --count type=bool FLAG fizzy upload file --help type=bool FLAG fizzy upload file --ids-only type=bool +FLAG fizzy upload file --jq type=string FLAG fizzy upload file --json type=bool FLAG fizzy upload file --limit type=int FLAG fizzy upload file --markdown type=bool @@ -1991,6 +2123,7 @@ FLAG fizzy upload help --api-url type=string FLAG fizzy upload help --count type=bool FLAG fizzy upload help --help type=bool FLAG fizzy upload help --ids-only type=bool +FLAG fizzy upload help --jq type=string FLAG fizzy upload help --json type=bool FLAG fizzy upload help --limit type=int FLAG fizzy upload help --markdown type=bool @@ -2004,6 +2137,7 @@ FLAG fizzy user --api-url type=string FLAG fizzy user --count type=bool FLAG fizzy user --help type=bool FLAG fizzy user --ids-only type=bool +FLAG fizzy user --jq type=string FLAG fizzy user --json type=bool FLAG fizzy user --limit type=int FLAG fizzy user --markdown type=bool @@ -2017,6 +2151,7 @@ FLAG fizzy user avatar-remove --api-url type=string FLAG fizzy user avatar-remove --count type=bool FLAG fizzy user avatar-remove --help type=bool FLAG fizzy user avatar-remove --ids-only type=bool +FLAG fizzy user avatar-remove --jq type=string FLAG fizzy user avatar-remove --json type=bool FLAG fizzy user avatar-remove --limit type=int FLAG fizzy user avatar-remove --markdown type=bool @@ -2030,6 +2165,7 @@ FLAG fizzy user deactivate --api-url type=string FLAG fizzy user deactivate --count type=bool FLAG fizzy user deactivate --help type=bool FLAG fizzy user deactivate --ids-only type=bool +FLAG fizzy user deactivate --jq type=string FLAG fizzy user deactivate --json type=bool FLAG fizzy user deactivate --limit type=int FLAG fizzy user deactivate --markdown type=bool @@ -2043,6 +2179,7 @@ FLAG fizzy user help --api-url type=string FLAG fizzy user help --count type=bool FLAG fizzy user help --help type=bool FLAG fizzy user help --ids-only type=bool +FLAG fizzy user help --jq type=string FLAG fizzy user help --json type=bool FLAG fizzy user help --limit type=int FLAG fizzy user help --markdown type=bool @@ -2057,6 +2194,7 @@ FLAG fizzy user list --api-url type=string FLAG fizzy user list --count type=bool FLAG fizzy user list --help type=bool FLAG fizzy user list --ids-only type=bool +FLAG fizzy user list --jq type=string FLAG fizzy user list --json type=bool FLAG fizzy user list --limit type=int FLAG fizzy user list --markdown type=bool @@ -2073,6 +2211,7 @@ FLAG fizzy user push-subscription-create --count type=bool FLAG fizzy user push-subscription-create --endpoint type=string FLAG fizzy user push-subscription-create --help type=bool FLAG fizzy user push-subscription-create --ids-only type=bool +FLAG fizzy user push-subscription-create --jq type=string FLAG fizzy user push-subscription-create --json type=bool FLAG fizzy user push-subscription-create --limit type=int FLAG fizzy user push-subscription-create --markdown type=bool @@ -2088,6 +2227,7 @@ FLAG fizzy user push-subscription-delete --api-url type=string FLAG fizzy user push-subscription-delete --count type=bool FLAG fizzy user push-subscription-delete --help type=bool FLAG fizzy user push-subscription-delete --ids-only type=bool +FLAG fizzy user push-subscription-delete --jq type=string FLAG fizzy user push-subscription-delete --json type=bool FLAG fizzy user push-subscription-delete --limit type=int FLAG fizzy user push-subscription-delete --markdown type=bool @@ -2102,6 +2242,7 @@ FLAG fizzy user role --api-url type=string FLAG fizzy user role --count type=bool FLAG fizzy user role --help type=bool FLAG fizzy user role --ids-only type=bool +FLAG fizzy user role --jq type=string FLAG fizzy user role --json type=bool FLAG fizzy user role --limit type=int FLAG fizzy user role --markdown type=bool @@ -2116,6 +2257,7 @@ FLAG fizzy user show --api-url type=string FLAG fizzy user show --count type=bool FLAG fizzy user show --help type=bool FLAG fizzy user show --ids-only type=bool +FLAG fizzy user show --jq type=string FLAG fizzy user show --json type=bool FLAG fizzy user show --limit type=int FLAG fizzy user show --markdown type=bool @@ -2130,6 +2272,7 @@ FLAG fizzy user update --avatar type=string FLAG fizzy user update --count type=bool FLAG fizzy user update --help type=bool FLAG fizzy user update --ids-only type=bool +FLAG fizzy user update --jq type=string FLAG fizzy user update --json type=bool FLAG fizzy user update --limit type=int FLAG fizzy user update --markdown type=bool @@ -2144,6 +2287,7 @@ FLAG fizzy version --api-url type=string FLAG fizzy version --count type=bool FLAG fizzy version --help type=bool FLAG fizzy version --ids-only type=bool +FLAG fizzy version --jq type=string FLAG fizzy version --json type=bool FLAG fizzy version --limit type=int FLAG fizzy version --markdown type=bool @@ -2157,6 +2301,7 @@ FLAG fizzy webhook --api-url type=string FLAG fizzy webhook --count type=bool FLAG fizzy webhook --help type=bool FLAG fizzy webhook --ids-only type=bool +FLAG fizzy webhook --jq type=string FLAG fizzy webhook --json type=bool FLAG fizzy webhook --limit type=int FLAG fizzy webhook --markdown type=bool @@ -2172,6 +2317,7 @@ FLAG fizzy webhook create --board type=string FLAG fizzy webhook create --count type=bool FLAG fizzy webhook create --help type=bool FLAG fizzy webhook create --ids-only type=bool +FLAG fizzy webhook create --jq type=string FLAG fizzy webhook create --json type=bool FLAG fizzy webhook create --limit type=int FLAG fizzy webhook create --markdown type=bool @@ -2188,6 +2334,7 @@ FLAG fizzy webhook delete --board type=string FLAG fizzy webhook delete --count type=bool FLAG fizzy webhook delete --help type=bool FLAG fizzy webhook delete --ids-only type=bool +FLAG fizzy webhook delete --jq type=string FLAG fizzy webhook delete --json type=bool FLAG fizzy webhook delete --limit type=int FLAG fizzy webhook delete --markdown type=bool @@ -2201,6 +2348,7 @@ FLAG fizzy webhook help --api-url type=string FLAG fizzy webhook help --count type=bool FLAG fizzy webhook help --help type=bool FLAG fizzy webhook help --ids-only type=bool +FLAG fizzy webhook help --jq type=string FLAG fizzy webhook help --json type=bool FLAG fizzy webhook help --limit type=int FLAG fizzy webhook help --markdown type=bool @@ -2216,6 +2364,7 @@ FLAG fizzy webhook list --board type=string FLAG fizzy webhook list --count type=bool FLAG fizzy webhook list --help type=bool FLAG fizzy webhook list --ids-only type=bool +FLAG fizzy webhook list --jq type=string FLAG fizzy webhook list --json type=bool FLAG fizzy webhook list --limit type=int FLAG fizzy webhook list --markdown type=bool @@ -2231,6 +2380,7 @@ FLAG fizzy webhook reactivate --board type=string FLAG fizzy webhook reactivate --count type=bool FLAG fizzy webhook reactivate --help type=bool FLAG fizzy webhook reactivate --ids-only type=bool +FLAG fizzy webhook reactivate --jq type=string FLAG fizzy webhook reactivate --json type=bool FLAG fizzy webhook reactivate --limit type=int FLAG fizzy webhook reactivate --markdown type=bool @@ -2245,6 +2395,7 @@ FLAG fizzy webhook show --board type=string FLAG fizzy webhook show --count type=bool FLAG fizzy webhook show --help type=bool FLAG fizzy webhook show --ids-only type=bool +FLAG fizzy webhook show --jq type=string FLAG fizzy webhook show --json type=bool FLAG fizzy webhook show --limit type=int FLAG fizzy webhook show --markdown type=bool @@ -2260,6 +2411,7 @@ FLAG fizzy webhook update --board type=string FLAG fizzy webhook update --count type=bool FLAG fizzy webhook update --help type=bool FLAG fizzy webhook update --ids-only type=bool +FLAG fizzy webhook update --jq type=string FLAG fizzy webhook update --json type=bool FLAG fizzy webhook update --limit type=int FLAG fizzy webhook update --markdown type=bool diff --git a/go.mod b/go.mod index 3f9ea02..dbf9ebc 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/charmbracelet/huh v1.0.0 github.com/charmbracelet/lipgloss v1.1.0 github.com/charmbracelet/x/term v0.2.2 + github.com/itchyny/gojq v0.12.18 github.com/mattn/go-isatty v0.0.20 github.com/muesli/termenv v0.16.0 github.com/spf13/cobra v1.10.2 @@ -27,14 +28,17 @@ require ( github.com/charmbracelet/x/ansi v0.9.3 // indirect github.com/charmbracelet/x/cellbuf v0.0.13 // indirect github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect + github.com/clipperhouse/stringish v0.1.1 // indirect + github.com/clipperhouse/uax29/v2 v2.3.0 // indirect github.com/danieljoos/wincred v1.2.2 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/itchyny/timefmt-go v0.1.7 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-localereader v0.0.1 // indirect - github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mattn/go-runewidth v0.0.19 // indirect github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect @@ -42,6 +46,6 @@ require ( github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/zalando/go-keyring v0.2.6 // indirect golang.org/x/sync v0.15.0 // indirect - golang.org/x/sys v0.36.0 // indirect + golang.org/x/sys v0.38.0 // indirect golang.org/x/text v0.23.0 // indirect ) diff --git a/go.sum b/go.sum index 37f70e5..389a9a5 100644 --- a/go.sum +++ b/go.sum @@ -42,6 +42,10 @@ github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8 github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo= github.com/charmbracelet/x/xpty v0.1.2 h1:Pqmu4TEJ8KeA9uSkISKMU3f+C1F6OGBn8ABuGlqCbtI= github.com/charmbracelet/x/xpty v0.1.2/go.mod h1:XK2Z0id5rtLWcpeNiMYBccNNBrP2IJnzHI0Lq13Xzq4= +github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= +github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= +github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4= +github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= @@ -59,14 +63,18 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaU github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/itchyny/gojq v0.12.18 h1:gFGHyt/MLbG9n6dqnvlliiya2TaMMh6FFaR2b1H6Drc= +github.com/itchyny/gojq v0.12.18/go.mod h1:4hPoZ/3lN9fDL1D+aK7DY1f39XZpY9+1Xpjz8atrEkg= +github.com/itchyny/timefmt-go v0.1.7 h1:xyftit9Tbw+Dc/huSSPJaEmX1TVL8lw5vxjJLK4GMMA= +github.com/itchyny/timefmt-go v0.1.7/go.mod h1:5E46Q+zj7vbTgWY8o5YkMeYb4I6GeWLFnetPy5oBrAI= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= -github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= -github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= +github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= @@ -77,7 +85,6 @@ github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -103,8 +110,8 @@ golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= diff --git a/internal/commands/completion.go b/internal/commands/completion.go index ba73d64..bc08668 100644 --- a/internal/commands/completion.go +++ b/internal/commands/completion.go @@ -5,6 +5,7 @@ import ( "os" "github.com/basecamp/cli/output" + "github.com/basecamp/fizzy-cli/internal/errors" "github.com/spf13/cobra" ) @@ -42,6 +43,9 @@ PowerShell: ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), RunE: func(cmd *cobra.Command, args []string) error { + if cfgJQ != "" { + return errors.ErrJQNotSupported("completion script generation") + } var err error switch args[0] { case "bash": diff --git a/internal/commands/format_test.go b/internal/commands/format_test.go index 6093392..1621328 100644 --- a/internal/commands/format_test.go +++ b/internal/commands/format_test.go @@ -281,6 +281,7 @@ func runCobraWithArgs(args ...string) (string, error) { cfgAgent = false cfgStyled = false cfgMarkdown = false + cfgJQ = "" testBuf.Reset() lastRawOutput = "" out = output.New(output.Options{Format: output.FormatJSON, Writer: &testBuf}) diff --git a/internal/commands/jq.go b/internal/commands/jq.go new file mode 100644 index 0000000..ca5e3b3 --- /dev/null +++ b/internal/commands/jq.go @@ -0,0 +1,92 @@ +package commands + +import ( + "encoding/json" + "fmt" + "io" + "os" + + "github.com/basecamp/fizzy-cli/internal/errors" + "github.com/itchyny/gojq" +) + +// jqWriter wraps an io.Writer and applies a compiled jq filter to JSON output. +// Non-JSON writes pass through unchanged. +type jqWriter struct { + dest io.Writer + code *gojq.Code +} + +// newJQWriter parses and compiles the jq expression and returns a filtering writer. +// Delegates to compileJQ for compilation so options are maintained in one place. +func newJQWriter(dest io.Writer, filter string) (*jqWriter, error) { + code, err := compileJQ(filter) + if err != nil { + return nil, err + } + return &jqWriter{dest: dest, code: code}, nil +} + +// newJQWriterWithCode creates a jqWriter using a pre-compiled *gojq.Code. +// Used when the expression has already been validated and compiled (e.g. in PersistentPreRunE). +func newJQWriterWithCode(dest io.Writer, code *gojq.Code) *jqWriter { + return &jqWriter{dest: dest, code: code} +} + +// compileJQ parses and compiles a jq expression, returning the compiled code. +func compileJQ(filter string) (*gojq.Code, error) { + query, err := gojq.Parse(filter) + if err != nil { + return nil, errors.ErrJQValidation(err) + } + code, err := gojq.Compile(query, gojq.WithEnvironLoader(os.Environ)) + if err != nil { + return nil, errors.ErrJQValidation(err) + } + return code, nil +} + +// Write intercepts JSON output, applies the jq filter, and writes filtered results. +// String results print as plain text; everything else prints as compact single-line JSON. +// Error envelopes (ok: false) pass through unfiltered so error messages are never hidden. +func (w *jqWriter) Write(p []byte) (int, error) { + var input any + if err := json.Unmarshal(p, &input); err != nil { + // Not JSON — pass through unchanged. + return w.dest.Write(p) + } + + // Pass through error envelopes unfiltered so jq doesn't hide error messages. + if m, ok := input.(map[string]any); ok { + if okVal, exists := m["ok"]; exists { + if okBool, isBool := okVal.(bool); isBool && !okBool { + return w.dest.Write(p) + } + } + } + + iter := w.code.Run(input) + for { + v, ok := iter.Next() + if !ok { + break + } + if err, isErr := v.(error); isErr { + return 0, errors.ErrJQRuntime(err) + } + if s, isStr := v.(string); isStr { + if _, err := fmt.Fprintln(w.dest, s); err != nil { + return 0, err + } + } else { + raw, err := json.Marshal(v) + if err != nil { + return 0, errors.ErrJQRuntime(fmt.Errorf("result not serializable: %w", err)) + } + if _, err := fmt.Fprintln(w.dest, string(raw)); err != nil { + return 0, err + } + } + } + return len(p), nil +} diff --git a/internal/commands/jq_test.go b/internal/commands/jq_test.go new file mode 100644 index 0000000..3086dbc --- /dev/null +++ b/internal/commands/jq_test.go @@ -0,0 +1,685 @@ +package commands + +import ( + "encoding/json" + stderrors "errors" + "strings" + "testing" + + "github.com/basecamp/cli/output" + "github.com/basecamp/fizzy-cli/internal/client" + "github.com/basecamp/fizzy-cli/internal/errors" +) + +func TestJQFlagRegistered(t *testing.T) { + if rootCmd.PersistentFlags().Lookup("jq") == nil { + t.Error("expected --jq flag to be registered") + } +} + +func TestResolveFormatJQImpliesJSON(t *testing.T) { + defer resetTest() + + t.Run("--jq implies JSON", func(t *testing.T) { + resetTest() + cfgJQ = ".data" + f, err := resolveFormat() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if f != output.FormatJSON { + t.Errorf("expected FormatJSON, got %v", f) + } + }) + + t.Run("--jq --agent implies Quiet", func(t *testing.T) { + resetTest() + cfgJQ = ".data" + cfgAgent = true + f, err := resolveFormat() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if f != output.FormatQuiet { + t.Errorf("expected FormatQuiet, got %v", f) + } + }) + + t.Run("--jq --styled is an error", func(t *testing.T) { + resetTest() + cfgJQ = ".data" + cfgStyled = true + _, err := resolveFormat() + if err == nil { + t.Fatal("expected error for --jq --styled") + } + if !strings.Contains(err.Error(), "--jq cannot be used with") { + t.Errorf("unexpected error message: %v", err) + } + }) + + t.Run("--jq --markdown is an error", func(t *testing.T) { + resetTest() + cfgJQ = ".data" + cfgMarkdown = true + _, err := resolveFormat() + if err == nil { + t.Fatal("expected error for --jq --markdown") + } + }) + + t.Run("--jq --ids-only is an error", func(t *testing.T) { + resetTest() + cfgJQ = ".data" + cfgIDsOnly = true + _, err := resolveFormat() + if err == nil { + t.Fatal("expected error for --jq --ids-only") + } + }) + + t.Run("--jq --count is an error", func(t *testing.T) { + resetTest() + cfgJQ = ".data" + cfgCount = true + _, err := resolveFormat() + if err == nil { + t.Fatal("expected error for --jq --count") + } + }) + + t.Run("--jq --json is allowed", func(t *testing.T) { + resetTest() + cfgJQ = ".data" + cfgJSON = true + f, err := resolveFormat() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if f != output.FormatJSON { + t.Errorf("expected FormatJSON, got %v", f) + } + }) + + t.Run("--jq --quiet is allowed", func(t *testing.T) { + resetTest() + cfgJQ = ".data" + cfgQuiet = true + f, err := resolveFormat() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if f != output.FormatQuiet { + t.Errorf("expected FormatQuiet, got %v", f) + } + }) +} + +func TestJQIsMachineOutput(t *testing.T) { + defer resetTest() + resetTest() + cfgJQ = ".data" + if !IsMachineOutput() { + t.Error("expected IsMachineOutput to be true when --jq is set") + } +} + +func TestJQWriterExtractsField(t *testing.T) { + var buf strings.Builder + w, err := newJQWriter(&buf, ".data") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + input := `{"ok":true,"data":[{"id":"1","name":"Board 1"},{"id":"2","name":"Board 2"}]}` + if _, err := w.Write([]byte(input)); err != nil { + t.Fatalf("write error: %v", err) + } + + var result []map[string]any + if err := json.Unmarshal([]byte(buf.String()), &result); err != nil { + t.Fatalf("expected JSON array, got parse error: %v\noutput: %s", err, buf.String()) + } + if len(result) != 2 { + t.Errorf("expected 2 items, got %d", len(result)) + } +} + +func TestJQWriterStringOutput(t *testing.T) { + var buf strings.Builder + w, err := newJQWriter(&buf, ".data[0].name") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + input := `{"ok":true,"data":[{"id":"1","name":"Board 1"}]}` + if _, err := w.Write([]byte(input)); err != nil { + t.Fatalf("write error: %v", err) + } + + got := strings.TrimSpace(buf.String()) + if got != "Board 1" { + t.Errorf("expected plain string 'Board 1', got %q", got) + } +} + +func TestJQWriterSelect(t *testing.T) { + var buf strings.Builder + w, err := newJQWriter(&buf, `[.data[] | select(.completed == true)]`) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + input := `{"ok":true,"data":[{"id":"1","completed":true},{"id":"2","completed":false},{"id":"3","completed":true}]}` + if _, err := w.Write([]byte(input)); err != nil { + t.Fatalf("write error: %v", err) + } + + var result []map[string]any + if err := json.Unmarshal([]byte(buf.String()), &result); err != nil { + t.Fatalf("expected JSON array: %v\noutput: %s", err, buf.String()) + } + if len(result) != 2 { + t.Errorf("expected 2 completed items, got %d", len(result)) + } +} + +func TestJQWriterLength(t *testing.T) { + var buf strings.Builder + w, err := newJQWriter(&buf, ".data | length") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + input := `{"ok":true,"data":[{"id":"1"},{"id":"2"},{"id":"3"}]}` + if _, err := w.Write([]byte(input)); err != nil { + t.Fatalf("write error: %v", err) + } + + got := strings.TrimSpace(buf.String()) + if got != "3" { + t.Errorf("expected '3', got %q", got) + } +} + +func TestJQWriterInvalidExpression(t *testing.T) { + _, err := newJQWriter(nil, ".data[") + if err == nil { + t.Fatal("expected error for invalid jq expression") + } + if !strings.Contains(err.Error(), "invalid --jq expression") { + t.Errorf("expected 'invalid --jq expression' error, got: %v", err) + } +} + +func TestJQWriterMap(t *testing.T) { + var buf strings.Builder + w, err := newJQWriter(&buf, "[.data[] | {id, name}]") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + input := `{"ok":true,"data":[{"id":"1","name":"A","extra":"x"},{"id":"2","name":"B","extra":"y"}]}` + if _, err := w.Write([]byte(input)); err != nil { + t.Fatalf("write error: %v", err) + } + + var result []map[string]any + if err := json.Unmarshal([]byte(buf.String()), &result); err != nil { + t.Fatalf("expected JSON array: %v\noutput: %s", err, buf.String()) + } + if len(result) != 2 { + t.Errorf("expected 2 items, got %d", len(result)) + } + // Should not contain "extra" field + if _, hasExtra := result[0]["extra"]; hasExtra { + t.Error("expected 'extra' field to be excluded") + } +} + +func TestJQWriterPassthroughErrorEnvelope(t *testing.T) { + var buf strings.Builder + w, err := newJQWriter(&buf, ".data") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // Error envelopes (ok: false) should pass through unfiltered + errorJSON := `{"ok":false,"error":"not found","code":"not_found"}` + if _, err := w.Write([]byte(errorJSON)); err != nil { + t.Fatalf("write error: %v", err) + } + + var result map[string]any + if err := json.Unmarshal([]byte(buf.String()), &result); err != nil { + t.Fatalf("expected JSON: %v\noutput: %s", err, buf.String()) + } + if result["ok"] != false { + t.Error("expected ok to be false") + } + if result["error"] != "not found" { + t.Errorf("expected error message preserved, got %v", result["error"]) + } +} + +func TestJQWriterPassthroughNonJSON(t *testing.T) { + var buf strings.Builder + w, err := newJQWriter(&buf, ".data") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + nonJSON := "not json at all" + if _, err := w.Write([]byte(nonJSON)); err != nil { + t.Fatalf("write error: %v", err) + } + if buf.String() != nonJSON { + t.Errorf("expected passthrough of non-JSON, got %q", buf.String()) + } +} + +func TestJQWriterIdentity(t *testing.T) { + var buf strings.Builder + w, err := newJQWriter(&buf, ".") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + input := `{"ok":true,"data":"hello"}` + if _, err := w.Write([]byte(input)); err != nil { + t.Fatalf("write error: %v", err) + } + + var result map[string]any + if err := json.Unmarshal([]byte(buf.String()), &result); err != nil { + t.Fatalf("expected JSON: %v\noutput: %s", err, buf.String()) + } + if result["ok"] != true { + t.Error("expected identity filter to pass through full object") + } +} + +func TestCobraJQExtractsData(t *testing.T) { + mock := NewMockClient() + mock.GetWithPaginationResponse = &client.APIResponse{ + StatusCode: 200, + Data: []map[string]any{ + {"id": "1", "name": "Board 1"}, + {"id": "2", "name": "Board 2"}, + }, + } + SetTestModeWithSDK(mock) + SetTestConfig("token", "account", "https://api.example.com") + defer resetTest() + + raw, err := runCobraWithArgs("board", "list", "--jq", ".data") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + var data []map[string]any + if err := json.Unmarshal([]byte(raw), &data); err != nil { + t.Fatalf("expected JSON array, got parse error: %v\noutput: %s", err, raw) + } + if len(data) != 2 { + t.Errorf("expected 2 items, got %d", len(data)) + } +} + +func TestCobraJQExtractsFieldAsString(t *testing.T) { + mock := NewMockClient() + mock.GetWithPaginationResponse = &client.APIResponse{ + StatusCode: 200, + Data: []map[string]any{ + {"id": "1", "name": "Board 1"}, + }, + } + SetTestModeWithSDK(mock) + SetTestConfig("token", "account", "https://api.example.com") + defer resetTest() + + raw, err := runCobraWithArgs("board", "list", "--jq", ".data[0].name") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + got := strings.TrimSpace(raw) + if got != "Board 1" { + t.Errorf("expected 'Board 1', got %q", got) + } +} + +func TestCobraJQInvalidExpression(t *testing.T) { + mock := NewMockClient() + SetTestModeWithSDK(mock) + SetTestConfig("token", "account", "https://api.example.com") + defer resetTest() + + _, err := runCobraWithArgs("board", "list", "--jq", ".data[") + if err == nil { + t.Fatal("expected error for invalid jq expression") + } +} + +// ============================================================================= +// Early validation tests (PersistentPreRunE) +// ============================================================================= + +func TestJQInvalidExpressionRejectedBeforeRunE(t *testing.T) { + mock := NewMockClient() + SetTestModeWithSDK(mock) + SetTestConfig("token", "account", "https://api.example.com") + defer resetTest() + + _, err := runCobraWithArgs("board", "list", "--jq", ".[invalid") + if err == nil { + t.Fatal("expected error for invalid jq expression") + } + if !strings.Contains(err.Error(), "invalid --jq expression") { + t.Errorf("expected 'invalid --jq expression', got: %v", err) + } + if !errors.IsJQError(err) { + t.Error("expected IsJQError to be true") + } +} + +func TestJQCompileErrorRejectedBeforeRunE(t *testing.T) { + mock := NewMockClient() + SetTestModeWithSDK(mock) + SetTestConfig("token", "account", "https://api.example.com") + defer resetTest() + + _, err := runCobraWithArgs("board", "list", "--jq", "$__loc__") + if err == nil { + t.Fatal("expected error for compile-time jq error") + } + if !strings.Contains(err.Error(), "invalid --jq expression") { + t.Errorf("expected 'invalid --jq expression', got: %v", err) + } +} + +func TestJQWithIDsOnlyConflict(t *testing.T) { + mock := NewMockClient() + SetTestModeWithSDK(mock) + SetTestConfig("token", "account", "https://api.example.com") + defer resetTest() + + _, err := runCobraWithArgs("board", "list", "--jq", ".data", "--ids-only") + if err == nil { + t.Fatal("expected error for --jq --ids-only conflict") + } + if !strings.Contains(err.Error(), "cannot use --jq with --ids-only") { + t.Errorf("unexpected error: %v", err) + } +} + +func TestJQWithCountConflict(t *testing.T) { + mock := NewMockClient() + SetTestModeWithSDK(mock) + SetTestConfig("token", "account", "https://api.example.com") + defer resetTest() + + _, err := runCobraWithArgs("board", "list", "--jq", ".data", "--count") + if err == nil { + t.Fatal("expected error for --jq --count conflict") + } + if !strings.Contains(err.Error(), "cannot use --jq with --count") { + t.Errorf("unexpected error: %v", err) + } +} + +// ============================================================================= +// env.VAR and $ENV.VAR access +// ============================================================================= + +func TestJQWriterEnvAccess(t *testing.T) { + t.Setenv("FIZZY_TEST_JQ_VAR", "hello_from_env") + + var buf strings.Builder + w, err := newJQWriter(&buf, `env.FIZZY_TEST_JQ_VAR`) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + input := `{"ok":true,"data":{"id":1}}` + if _, err := w.Write([]byte(input)); err != nil { + t.Fatalf("write error: %v", err) + } + if strings.TrimSpace(buf.String()) != "hello_from_env" { + t.Errorf("expected 'hello_from_env', got %q", buf.String()) + } +} + +func TestJQWriterEnvDollarAccess(t *testing.T) { + t.Setenv("FIZZY_TEST_JQ_VAR2", "dollar_form") + + var buf strings.Builder + w, err := newJQWriter(&buf, `$ENV.FIZZY_TEST_JQ_VAR2`) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + input := `{"ok":true,"data":{"id":1}}` + if _, err := w.Write([]byte(input)); err != nil { + t.Fatalf("write error: %v", err) + } + if strings.TrimSpace(buf.String()) != "dollar_form" { + t.Errorf("expected 'dollar_form', got %q", buf.String()) + } +} + +// ============================================================================= +// Compact output +// ============================================================================= + +func TestJQWriterCompactOutput(t *testing.T) { + var buf strings.Builder + w, err := newJQWriter(&buf, `.data`) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + input := `{"ok":true,"data":{"name":"test","value":42}}` + if _, err := w.Write([]byte(input)); err != nil { + t.Fatalf("write error: %v", err) + } + + out := strings.TrimSpace(buf.String()) + // Must be single-line compact JSON + if strings.Contains(out, "\n") { + t.Error("expected compact single-line JSON, got multi-line") + } + if !strings.Contains(out, `"name":"test"`) { + t.Errorf("expected compact JSON with name field, got %q", out) + } +} + +// ============================================================================= +// Edge cases: empty, null, multi-result, non-serializable +// ============================================================================= + +func TestJQWriterEmptyResult(t *testing.T) { + var buf strings.Builder + w, err := newJQWriter(&buf, `empty`) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + input := `{"ok":true,"data":{"id":1}}` + if _, err := w.Write([]byte(input)); err != nil { + t.Fatalf("write error: %v", err) + } + if buf.String() != "" { + t.Errorf("expected no output for empty result, got %q", buf.String()) + } +} + +func TestJQWriterNullResult(t *testing.T) { + var buf strings.Builder + w, err := newJQWriter(&buf, `null`) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + input := `{"ok":true,"data":{"id":1}}` + if _, err := w.Write([]byte(input)); err != nil { + t.Fatalf("write error: %v", err) + } + if strings.TrimSpace(buf.String()) != "null" { + t.Errorf("expected 'null', got %q", buf.String()) + } +} + +func TestJQWriterMultiResult(t *testing.T) { + var buf strings.Builder + w, err := newJQWriter(&buf, `.data.a, .data.b, .data.c`) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + input := `{"ok":true,"data":{"a":1,"b":"two","c":true}}` + if _, err := w.Write([]byte(input)); err != nil { + t.Fatalf("write error: %v", err) + } + + lines := strings.Split(strings.TrimSpace(buf.String()), "\n") + if len(lines) != 3 { + t.Fatalf("expected 3 lines, got %d: %v", len(lines), lines) + } + if lines[0] != "1" { + t.Errorf("expected '1', got %q", lines[0]) + } + if lines[1] != "two" { + t.Errorf("expected 'two', got %q", lines[1]) + } + if lines[2] != "true" { + t.Errorf("expected 'true', got %q", lines[2]) + } +} + +func TestJQWriterNonSerializableResultReturnsError(t *testing.T) { + for _, expr := range []string{"nan", "infinite"} { + t.Run(expr, func(t *testing.T) { + var buf strings.Builder + w, err := newJQWriter(&buf, expr) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + input := `{"ok":true,"data":{"id":1}}` + _, err = w.Write([]byte(input)) + if err == nil { + t.Fatal("expected error for non-serializable result") + } + if !strings.Contains(err.Error(), "not serializable") { + t.Errorf("expected 'not serializable' error, got: %v", err) + } + if !errors.IsJQError(err) { + t.Error("expected IsJQError to be true") + } + }) + } +} + +func TestJQWriterRuntimeError(t *testing.T) { + var buf strings.Builder + w, err := newJQWriter(&buf, `.data / 0`) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + input := `{"ok":true,"data":1}` + _, err = w.Write([]byte(input)) + if err == nil { + t.Fatal("expected runtime error") + } + if !errors.IsJQError(err) { + t.Error("expected IsJQError to be true for runtime error") + } + var outputErr *output.Error + if !stderrors.As(err, &outputErr) { + t.Error("expected error to be *output.Error") + } else if outputErr.Code != output.CodeUsage { + t.Errorf("expected CodeUsage, got %v", outputErr.Code) + } +} + +// ============================================================================= +// Command-level --jq rejection +// ============================================================================= + +func TestJQRejectedByCompletion(t *testing.T) { + mock := NewMockClient() + SetTestModeWithSDK(mock) + SetTestConfig("token", "account", "https://api.example.com") + defer resetTest() + + _, err := runCobraWithArgs("completion", "bash", "--jq", ".data") + if err == nil { + t.Fatal("expected error for --jq with completion") + } + if !strings.Contains(err.Error(), "--jq is not supported by") { + t.Errorf("unexpected error: %v", err) + } +} + +func TestJQRejectedBySkillInMachineMode(t *testing.T) { + defer resetTest() + resetTest() + cfgAgent = true + cfgJQ = ".data" + + err := runSkill(skillCmd, nil) + if err == nil { + t.Fatal("expected error for --jq with skill in machine mode") + } + if !strings.Contains(err.Error(), "--jq is not supported by") { + t.Errorf("unexpected error: %v", err) + } +} + +func TestJQRejectedByVersion(t *testing.T) { + mock := NewMockClient() + SetTestModeWithSDK(mock) + SetTestConfig("token", "account", "https://api.example.com") + defer resetTest() + + _, err := runCobraWithArgs("version", "--jq", ".version") + if err == nil { + t.Fatal("expected error for --jq with version") + } + if !strings.Contains(err.Error(), "--jq is not supported by the version command") { + t.Errorf("unexpected error: %v", err) + } +} + +func TestVersionOutputPlainText(t *testing.T) { + defer resetTest() + resetTest() + + var buf strings.Builder + versionCmd.SetOut(&buf) + defer versionCmd.SetOut(nil) + + err := versionCmd.RunE(versionCmd, nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !strings.Contains(buf.String(), "fizzy version") { + t.Errorf("expected 'fizzy version' in output, got %q", buf.String()) + } +} + +func TestJQSetupBlockedInMachineMode(t *testing.T) { + defer resetTest() + resetTest() + cfgJQ = ".data" + + err := runSetup(setupCmd, nil) + if err == nil { + t.Fatal("expected error when running setup with --jq") + } + if !strings.Contains(err.Error(), "interactive terminal") { + t.Errorf("unexpected error: %v", err) + } +} diff --git a/internal/commands/root.go b/internal/commands/root.go index e4725b6..674567e 100644 --- a/internal/commands/root.go +++ b/internal/commands/root.go @@ -21,6 +21,7 @@ import ( "github.com/basecamp/fizzy-cli/internal/errors" "github.com/basecamp/fizzy-cli/internal/render" fizzy "github.com/basecamp/fizzy-sdk/go/pkg/fizzy" + "github.com/itchyny/gojq" "github.com/mattn/go-isatty" "github.com/spf13/cobra" ) @@ -42,6 +43,7 @@ var ( cfgStyled bool cfgMarkdown bool cfgLimit int + cfgJQ string // Loaded config cfg *config.Config @@ -73,6 +75,25 @@ var rootCmd = &cobra.Command{ Use fizzy to manage boards, cards, comments, and more from your terminal.`, Version: "dev", PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + // Early jq validation: check flag conflicts first (actionable message), + // then parse + compile before RunE so invalid expressions are rejected + // with no side effects. The compiled code is reused below to avoid + // parsing the expression twice. + var jqCode *gojq.Code + if cfgJQ != "" { + if cfgIDsOnly { + return errors.ErrJQConflict("--ids-only") + } + if cfgCount { + return errors.ErrJQConflict("--count") + } + var err error + jqCode, err = compileJQ(cfgJQ) + if err != nil { + return err + } + } + // Resolve output format from parsed flags (must happen post-parse). format, err := resolveFormat() if err != nil { @@ -81,10 +102,18 @@ Use fizzy to manage boards, cards, comments, and more from your terminal.`, if lastResult != nil { // Test mode — preserve test buffer as writer. outWriter = &testBuf - out = output.New(output.Options{Format: format, Writer: &testBuf}) + var w io.Writer = &testBuf + if jqCode != nil { + w = newJQWriterWithCode(&testBuf, jqCode) + } + out = output.New(output.Options{Format: format, Writer: w}) } else { outWriter = os.Stdout - out = output.New(output.Options{Format: format, Writer: os.Stdout}) + var w io.Writer = os.Stdout + if jqCode != nil { + w = newJQWriterWithCode(os.Stdout, jqCode) + } + out = output.New(output.Options{Format: format, Writer: w}) } // In test mode, cfg is already set by SetTestConfig - don't overwrite @@ -164,6 +193,24 @@ func Execute() { // Cobra-level errors (arg count, unknown flag) → usage e = &output.Error{Code: output.CodeUsage, Message: err.Error()} } + + // jq-related errors (validation failures, unsupported commands, conflicts) + // must never be fed through the jq filter. Rebuild the output writer + // without jq so the error renders cleanly. Re-resolve the format to + // honor explicit flags like --agent --json. + if errors.IsJQError(err) && cfgJQ != "" { + format, fmtErr := resolveFormat() + if fmtErr != nil { + // resolveFormat() can fail when --jq conflicts with another flag + // (e.g. --jq --styled). Fall back to a sensible machine format. + format = output.FormatJSON + if cfgAgent || cfgQuiet { + format = output.FormatQuiet + } + } + out = output.New(output.Options{Format: format, Writer: outWriter}) + } + _ = out.Err(e) os.Exit(e.ExitCode()) } @@ -201,6 +248,11 @@ func resolveFormat() (output.Format, error) { return 0, fmt.Errorf("--agent and --styled cannot be used together") } + // --jq is incompatible with non-JSON format flags + if cfgJQ != "" && (cfgStyled || cfgMarkdown || cfgIDsOnly || cfgCount) { + return 0, fmt.Errorf("--jq cannot be used with --styled, --markdown, --ids-only, or --count") + } + // Explicit format flag wins switch { case cfgQuiet: @@ -217,6 +269,14 @@ func resolveFormat() (output.Format, error) { return output.FormatMarkdown, nil } + // --jq implies JSON (or quiet for --agent) + if cfgJQ != "" { + if cfgAgent { + return output.FormatQuiet, nil + } + return output.FormatJSON, nil + } + // --agent alone defaults to FormatQuiet if cfgAgent { return output.FormatQuiet, nil @@ -228,7 +288,7 @@ func resolveFormat() (output.Format, error) { // IsMachineOutput returns true when output should be treated as machine-consumable. // True when any machine format flag is set, --agent is set, or stdout/stdin is not a TTY. func IsMachineOutput() bool { - if cfgAgent || cfgJSON || cfgQuiet || cfgIDsOnly || cfgCount { + if cfgAgent || cfgJSON || cfgQuiet || cfgIDsOnly || cfgCount || cfgJQ != "" { return true } if !isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd()) { @@ -253,6 +313,7 @@ func init() { rootCmd.PersistentFlags().BoolVar(&cfgStyled, "styled", false, "Styled terminal output with colors") rootCmd.PersistentFlags().BoolVar(&cfgMarkdown, "markdown", false, "Markdown formatted output") rootCmd.PersistentFlags().IntVar(&cfgLimit, "limit", 0, "Maximum number of results to display") + rootCmd.PersistentFlags().StringVar(&cfgJQ, "jq", "", "Apply jq filter to JSON output (built-in, no external jq required)") installAgentHelp() } @@ -1056,6 +1117,7 @@ func ResetTestMode() { cfgStyled = false cfgMarkdown = false cfgLimit = 0 + cfgJQ = "" cfgProfile = "" } diff --git a/internal/commands/skill.go b/internal/commands/skill.go index 615746e..18e8e00 100644 --- a/internal/commands/skill.go +++ b/internal/commands/skill.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/basecamp/cli/output" + "github.com/basecamp/fizzy-cli/internal/errors" "github.com/basecamp/fizzy-cli/internal/harness" "github.com/basecamp/fizzy-cli/internal/skills" "github.com/charmbracelet/huh" @@ -45,6 +46,9 @@ func init() { func runSkill(cmd *cobra.Command, args []string) error { // Non-interactive: print skill content if IsMachineOutput() { + if cfgJQ != "" { + return errors.ErrJQNotSupported("the skill command") + } _, err := fmt.Fprint(cmd.OutOrStdout(), string(skills.Content)) return err } diff --git a/internal/commands/version.go b/internal/commands/version.go index 505f15d..f859e34 100644 --- a/internal/commands/version.go +++ b/internal/commands/version.go @@ -1,15 +1,21 @@ package commands -import "github.com/spf13/cobra" +import ( + "fmt" + + "github.com/basecamp/fizzy-cli/internal/errors" + "github.com/spf13/cobra" +) var versionCmd = &cobra.Command{ Use: "version", Short: "Print version information", RunE: func(cmd *cobra.Command, args []string) error { - printSuccess(map[string]any{ - "version": rootCmd.Version, - }) - return nil + if cfgJQ != "" { + return errors.ErrJQNotSupported("the version command") + } + _, err := fmt.Fprintf(cmd.OutOrStdout(), "fizzy version %s\n", rootCmd.Version) + return err }, } diff --git a/internal/errors/errors.go b/internal/errors/errors.go index edf3b0c..6b5818b 100644 --- a/internal/errors/errors.go +++ b/internal/errors/errors.go @@ -3,6 +3,7 @@ package errors import ( + "errors" "fmt" "github.com/basecamp/cli/output" @@ -69,6 +70,55 @@ func NewNetworkError(message string) *CLIError { return e } +// errJQ is a sentinel cause for all jq-related errors (validation, +// unsupported command, flag conflict, and runtime failures). +// root.go uses IsJQError() to detect these and bypass jq filtering +// when rendering the error itself. +var errJQ = errors.New("jq error") + +// ErrJQValidation returns a usage error for invalid --jq expressions. +func ErrJQValidation(cause error) *CLIError { + return &output.Error{ + Code: output.CodeUsage, + Message: fmt.Sprintf("invalid --jq expression: %s", cause), + Cause: errJQ, + } +} + +// ErrJQNotSupported returns a usage error for commands that don't support --jq. +func ErrJQNotSupported(command string) *CLIError { + return &output.Error{ + Code: output.CodeUsage, + Message: fmt.Sprintf("--jq is not supported by %s", command), + Cause: errJQ, + } +} + +// ErrJQConflict returns a usage error for flags that conflict with --jq. +func ErrJQConflict(flag string) *CLIError { + return &output.Error{ + Code: output.CodeUsage, + Message: fmt.Sprintf("cannot use --jq with %s", flag), + Cause: errJQ, + } +} + +// ErrJQRuntime returns a usage error for jq runtime failures +// (e.g. type errors, non-serializable results). +func ErrJQRuntime(cause error) *CLIError { + return &output.Error{ + Code: output.CodeUsage, + Message: fmt.Sprintf("jq filter error: %s", cause), + Cause: errJQ, + } +} + +// IsJQError returns true if the error is a jq-related error +// (validation failure, unsupported command, flag conflict, or runtime failure). +func IsJQError(err error) bool { + return errors.Is(err, errJQ) +} + // FromHTTPStatus creates an appropriate error from an HTTP status code. func FromHTTPStatus(status int, message string) *CLIError { switch status { diff --git a/internal/errors/errors_test.go b/internal/errors/errors_test.go index 9293fe4..c6a2c7b 100644 --- a/internal/errors/errors_test.go +++ b/internal/errors/errors_test.go @@ -1,6 +1,8 @@ package errors import ( + stderrors "errors" + "fmt" "testing" "github.com/basecamp/cli/output" @@ -181,6 +183,99 @@ func TestNewInvalidArgsError(t *testing.T) { } } +// ============================================================================= +// JQ Error Constructor Tests +// ============================================================================= + +func TestErrJQValidation(t *testing.T) { + cause := fmt.Errorf("unexpected token") + err := ErrJQValidation(cause) + + if err.Code != output.CodeUsage { + t.Errorf("expected code %q, got %q", output.CodeUsage, err.Code) + } + if err.Message != "invalid --jq expression: unexpected token" { + t.Errorf("unexpected message: %q", err.Message) + } + if !IsJQError(err) { + t.Error("expected IsJQError to be true") + } +} + +func TestErrJQNotSupported(t *testing.T) { + err := ErrJQNotSupported("the version command") + + if err.Code != output.CodeUsage { + t.Errorf("expected code %q, got %q", output.CodeUsage, err.Code) + } + if err.Message != "--jq is not supported by the version command" { + t.Errorf("unexpected message: %q", err.Message) + } + if !IsJQError(err) { + t.Error("expected IsJQError to be true") + } +} + +func TestErrJQConflict(t *testing.T) { + err := ErrJQConflict("--ids-only") + + if err.Code != output.CodeUsage { + t.Errorf("expected code %q, got %q", output.CodeUsage, err.Code) + } + if err.Message != "cannot use --jq with --ids-only" { + t.Errorf("unexpected message: %q", err.Message) + } + if !IsJQError(err) { + t.Error("expected IsJQError to be true") + } +} + +func TestErrJQRuntime(t *testing.T) { + cause := fmt.Errorf("division by zero") + err := ErrJQRuntime(cause) + + if err.Code != output.CodeUsage { + t.Errorf("expected code %q, got %q", output.CodeUsage, err.Code) + } + if err.Message != "jq filter error: division by zero" { + t.Errorf("unexpected message: %q", err.Message) + } + if !IsJQError(err) { + t.Error("expected IsJQError to be true") + } +} + +func TestIsJQErrorFalseForNonJQError(t *testing.T) { + if IsJQError(NewInvalidArgsError("plain error")) { + t.Error("expected false for non-jq usage error") + } + if IsJQError(stderrors.New("random error")) { + t.Error("expected false for random error") + } + if IsJQError(NewNotFoundError("project not found")) { + t.Error("expected false for not-found error") + } +} + +func TestJQErrorExitCodes(t *testing.T) { + tests := []struct { + name string + err *CLIError + }{ + {"validation", ErrJQValidation(fmt.Errorf("test"))}, + {"not_supported", ErrJQNotSupported("test")}, + {"conflict", ErrJQConflict("--test")}, + {"runtime", ErrJQRuntime(fmt.Errorf("test"))}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.err.ExitCode() != ExitUsage { + t.Errorf("expected exit code %d, got %d", ExitUsage, tt.err.ExitCode()) + } + }) + } +} + func TestFromHTTPStatus(t *testing.T) { tests := []struct { name string diff --git a/internal/skills/SKILL.md b/internal/skills/SKILL.md index 6d4c22a..cd7dd39 100644 --- a/internal/skills/SKILL.md +++ b/internal/skills/SKILL.md @@ -61,7 +61,7 @@ Full CLI coverage: boards, cards, columns, comments, steps, reactions, tags, use **MUST follow these rules:** 1. **Cards use NUMBER, not ID** — `fizzy card show 42` uses the card number. Other resources use their `id` field. -2. **Parse JSON with jq** to reduce token output — `fizzy card list | jq '[.data[] | {number, title}]'` +2. **Use built-in `--jq` for filtering** to reduce token output — `fizzy card list --jq '[.data[] | {number, title}]'`. Never pipe to external jq — use `--jq` instead. `--jq` implies `--json`, no need to pass both. 3. **Check breadcrumbs** in responses for available next actions with pre-filled values 4. **Check for board context** via `.fizzy.yaml` or `--board` flag before listing cards 5. **Rich text fields accept HTML** — use `
` tags for paragraphs, ` Feature description Feature description Looks good! Looks good! ` tags for paragraphs, ` Feature description Feature description Looks good! Looks good!