This is an automated email from the ASF dual-hosted git repository.

sbp pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-releases-client.git


The following commit(s) were added to refs/heads/main by this push:
     new 60e2ea7  Add commands to delete and list ignores, and rename some 
commands
60e2ea7 is described below

commit 60e2ea7e3bf5a8a479689a3a138d6b6e08644cc0
Author: Sean B. Palmer <[email protected]>
AuthorDate: Wed Jul 30 15:48:56 2025 +0100

    Add commands to delete and list ignores, and rename some commands
---
 COMMANDS.md                 | 174 ++++++++++++++++++++++++++++++++-----------
 docs/release-workflow.md    |   6 +-
 pyproject.toml              |   4 +-
 src/atrclient/client.py     | 176 +++++++++++++++++++++++++-------------------
 src/atrclient/models/api.py |  61 +++++++++------
 src/atrclient/models/sql.py |   4 +
 tests/cli_keys.t            |  10 +--
 tests/cli_workflow.t        |   6 +-
 tests/test_all.py           |   4 +-
 uv.lock                     |   4 +-
 10 files changed, 294 insertions(+), 155 deletions(-)

diff --git a/COMMANDS.md b/COMMANDS.md
index 127a646..d11bf0a 100644
--- a/COMMANDS.md
+++ b/COMMANDS.md
@@ -27,10 +27,25 @@ Announce a release.
 
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
 ```
 
-## atr checks
+## atr api
 
 ```
-Usage: checks COMMAND
+Usage: api [ARGS] [OPTIONS]
+
+Call the API directly.
+
+╭─ Arguments 
──────────────────────────────────────────────────────────────────────────────────────────────────────────╮
+│ *  PATH  [required]                                                          
                                        │
+╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
+╭─ Parameters 
─────────────────────────────────────────────────────────────────────────────────────────────────────────╮
+│ --[KEYWORD]                                                                  
                                        │
+╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
+```
+
+## atr check
+
+```
+Usage: check COMMAND
 
 Check result operations.
 
@@ -43,7 +58,7 @@ Check result operations.
 
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
 ```
 
-### atr checks exceptions
+### atr check exceptions
 
 ```
 Usage: exceptions [ARGS] [OPTIONS]
@@ -60,7 +75,7 @@ Get check exceptions for a release revision.
 
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
 ```
 
-### atr checks failures
+### atr check failures
 
 ```
 Usage: failures [ARGS] [OPTIONS]
@@ -77,7 +92,7 @@ Get check failures for a release revision.
 
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
 ```
 
-### atr checks status
+### atr check status
 
 ```
 Usage: status [ARGS] [OPTIONS]
@@ -94,7 +109,7 @@ Get check status for a release revision.
 
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
 ```
 
-### atr checks wait
+### atr check wait
 
 ```
 Usage: wait [ARGS] [OPTIONS]
@@ -112,7 +127,7 @@ Wait for checks to be completed.
 
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
 ```
 
-### atr checks warnings
+### atr check warnings
 
 ```
 Usage: warnings [ARGS] [OPTIONS]
@@ -168,7 +183,9 @@ Developer operations.
 ╭─ Commands 
───────────────────────────────────────────────────────────────────────────────────────────────────────────╮
 │ delete  Delete a release.                                                    
                                        │
 │ env     Show the environment variables.                                      
                                        │
+│ key     Return a test OpenPGP key.                                           
                                        │
 │ pat     Read a PAT from development configuration.                           
                                        │
+│ pwd     Show the current working directory.                                  
                                        │
 │ stamp   Update version and exclude-newer in pyproject.toml.                  
                                        │
 │ token   Generate a random alphabetical token.                                
                                        │
 │ user    Show the value of $USER.                                             
                                        │
@@ -196,6 +213,14 @@ Usage: env
 Show the environment variables.
 ```
 
+### atr dev key
+
+```
+Usage: key
+
+Return a test OpenPGP key.
+```
+
 ### atr dev pat
 
 ```
@@ -204,6 +229,14 @@ Usage: pat
 Read a PAT from development configuration.
 ```
 
+### atr dev pwd
+
+```
+Usage: pwd
+
+Show the current working directory.
+```
+
 ### atr dev stamp
 
 ```
@@ -260,6 +293,53 @@ Remove a configuration key using dot notation.
 
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
 ```
 
+## atr ignore
+
+```
+Usage: ignore COMMAND
+
+Ignore operations.
+
+╭─ Commands 
───────────────────────────────────────────────────────────────────────────────────────────────────────────╮
+│ add     Add a check ignore.                                                  
                                        │
+│ delete  Delete a check ignore.                                               
                                        │
+│ list    List check ignores.                                                  
                                        │
+╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
+```
+
+### atr ignore add
+
+```
+Usage: add [ARGS] [OPTIONS]
+
+Add a check ignore.
+
+╭─ Arguments 
──────────────────────────────────────────────────────────────────────────────────────────────────────────╮
+│ *  COMMITTEE  [required]                                                     
                                        │
+╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
+╭─ Parameters 
─────────────────────────────────────────────────────────────────────────────────────────────────────────╮
+│ RELEASE --release                                                            
                                        │
+│ REVISION --revision                                                          
                                        │
+│ CHECKER --checker                                                            
                                        │
+│ PRIMARY-REL-PATH --primary-rel-path                                          
                                        │
+│ MEMBER-REL-PATH --member-rel-path                                            
                                        │
+│ STATUS --status                      [choices: exception, failure, warning]  
                                        │
+│ MESSAGE --message                                                            
                                        │
+╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
+```
+
+### atr ignore list
+
+```
+Usage: list [ARGS]
+
+List check ignores.
+
+╭─ Arguments 
──────────────────────────────────────────────────────────────────────────────────────────────────────────╮
+│ *  COMMITTEE  [required]                                                     
                                        │
+╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
+```
+
 ## atr jwt
 
 ```
@@ -311,28 +391,6 @@ Usage: show
 Show stored JWT token.
 ```
 
-## atr keys
-
-```
-Usage: keys
-
-Keys operations.
-```
-
-## atr list
-
-```
-Usage: list [ARGS]
-
-List all files within a release.
-
-╭─ Arguments 
──────────────────────────────────────────────────────────────────────────────────────────────────────────╮
-│ *  PROJECT   [required]                                                      
                                        │
-│ *  VERSION   [required]                                                      
                                        │
-│    REVISION                                                                  
                                        │
-╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
-```
-
 ## atr release
 
 ```
@@ -373,6 +431,21 @@ List all revisions for a release.
 
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
 ```
 
+## atr rsync
+
+```
+Usage: rsync [ARGS]
+
+Rsync a release.
+
+╭─ Arguments 
──────────────────────────────────────────────────────────────────────────────────────────────────────────╮
+│ *  PROJECT  [required]                                                       
                                        │
+│ *  VERSION  [required]                                                       
                                        │
+│    SOURCE   [default: .]                                                     
                                        │
+│    TARGET   [default: /]                                                     
                                        │
+╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
+```
+
 ## atr set
 
 ```
@@ -400,30 +473,33 @@ SSH operations.
 
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
 ```
 
-### atr ssh add
+## atr upload
 
 ```
-Usage: add [ARGS]
+Usage: upload [ARGS]
 
-Add an SSH key.
+Upload a file to a release.
 
 ╭─ Arguments 
──────────────────────────────────────────────────────────────────────────────────────────────────────────╮
-│ *  TEXT  [required]                                                          
                                        │
+│ *  PROJECT   [required]                                                      
                                        │
+│ *  VERSION   [required]                                                      
                                        │
+│ *  PATH      [required]                                                      
                                        │
+│ *  FILEPATH  [required]                                                      
                                        │
 
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
 ```
 
-## atr upload
+## atr verify
 
 ```
-Usage: upload [ARGS]
+Usage: verify [ARGS] [OPTIONS]
 
-Upload a file to a release.
+Verify an artifact.
 
 ╭─ Arguments 
──────────────────────────────────────────────────────────────────────────────────────────────────────────╮
-│ *  PROJECT   [required]                                                      
                                        │
-│ *  VERSION   [required]                                                      
                                        │
-│ *  PATH      [required]                                                      
                                        │
-│ *  FILEPATH  [required]                                                      
                                        │
+│ *  URL  [required]                                                           
                                        │
+╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
+╭─ Parameters 
─────────────────────────────────────────────────────────────────────────────────────────────────────────╮
+│ VERBOSE --verbose --no-verbose  [default: False]                             
                                        │
 
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
 ```
 
@@ -435,8 +511,9 @@ Usage: vote COMMAND
 Vote operations.
 
 ╭─ Commands 
───────────────────────────────────────────────────────────────────────────────────────────────────────────╮
-│ resolve  Resolve a vote.                                                     
                                        │
-│ start    Start a vote.                                                       
                                        │
+│ resolve   Resolve a vote.                                                    
                                        │
+│ start     Start a vote.                                                      
                                        │
+│ tabulate  Tabulate a vote.                                                   
                                        │
 
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
 ```
 
@@ -453,3 +530,16 @@ Resolve a vote.
 │ *  RESOLUTION --resolution  [choices: passed, failed] [required]             
                                        │
 
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
 ```
+
+### atr vote tabulate
+
+```
+Usage: tabulate [ARGS]
+
+Tabulate a vote.
+
+╭─ Arguments 
──────────────────────────────────────────────────────────────────────────────────────────────────────────╮
+│ *  PROJECT  [required]                                                       
                                        │
+│ *  VERSION  [required]                                                       
                                        │
+╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
+```
diff --git a/docs/release-workflow.md b/docs/release-workflow.md
index b70f49c..3374b26 100644
--- a/docs/release-workflow.md
+++ b/docs/release-workflow.md
@@ -30,7 +30,7 @@ Your release is in the ① COMPOSE phase.
 
 ```
 atr upload your-project 0.1+test example.txt "$FILE_TO_UPLOAD"
-atr checks wait your-project 0.1+test
+atr check wait your-project 0.1+test
 atr vote start your-project 0.1+test 00002 -m "${ASF_UID}@apache.org"
 ```
 
@@ -93,10 +93,10 @@ Your release is then in the ① COMPOSE phase. You must add a 
file to be able to
 
 ```
 atr upload your-project 0.1+test example.txt "$FILE_TO_UPLOAD"
-atr checks wait your-project 0.1+test
+atr check wait your-project 0.1+test
 ```
 
-To see the status of the checks here, you could run `atr checks status 
your-project 0.1+test 00002`. You need to know the revision to get the status, 
but we plan to make this command use the most recent revision if omitted.
+To see the status of the checks here, you could run `atr check status 
your-project 0.1+test 00002`. You need to know the revision to get the status, 
but we plan to make this command use the most recent revision if omitted.
 
 ### Vote
 
diff --git a/pyproject.toml b/pyproject.toml
index 3307ef1..77969fe 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -11,7 +11,7 @@ build-backend = "hatchling.build"
 
 [project]
 name            = "apache-trusted-releases"
-version         = "0.20250729.1943"
+version         = "0.20250730.1448"
 description     = "ATR CLI and Python API"
 readme          = "README.md"
 requires-python = ">=3.13"
@@ -79,4 +79,4 @@ filterwarnings = [
 ]
 
 [tool.uv]
-exclude-newer = "2025-07-29T19:43:00Z"
+exclude-newer = "2025-07-30T14:48:00Z"
diff --git a/src/atrclient/client.py b/src/atrclient/client.py
index 931aeea..e3af369 100755
--- a/src/atrclient/client.py
+++ b/src/atrclient/client.py
@@ -54,12 +54,13 @@ if TYPE_CHECKING:
     from collections.abc import Callable, Generator, Sequence
 
 APP: cyclopts.App = cyclopts.App()
-APP_CHECKS: cyclopts.App = cyclopts.App(name="checks", help="Check result 
operations.")
+APP_CHECK: cyclopts.App = cyclopts.App(name="check", help="Check result 
operations.")
 APP_CONFIG: cyclopts.App = cyclopts.App(name="config", help="Configuration 
operations.")
 APP_DEV: cyclopts.App = cyclopts.App(name="dev", help="Developer operations.")
 APP_DRAFT: cyclopts.App = cyclopts.App(name="draft", help="Draft operations.")
+APP_IGNORE: cyclopts.App = cyclopts.App(name="ignore", help="Ignore 
operations.")
 APP_JWT: cyclopts.App = cyclopts.App(name="jwt", help="JWT operations.")
-APP_KEYS: cyclopts.App = cyclopts.App(name="keys", help="Keys operations.")
+APP_KEY: cyclopts.App = cyclopts.App(name="key", help="Key operations.")
 APP_RELEASE: cyclopts.App = cyclopts.App(name="release", help="Release 
operations.")
 APP_SSH: cyclopts.App = cyclopts.App(name="ssh", help="SSH operations.")
 APP_VOTE: cyclopts.App = cyclopts.App(name="vote", help="Vote operations.")
@@ -156,12 +157,6 @@ def api_post(path: str) -> Callable[[Callable[[ApiPost, 
A], R]], Callable[[A], R
     return decorator
 
 
-@api_post("/checks/ignore/add")
-def api_checks_ignore_add(api: ApiPost, args: models.api.ChecksIgnoreAddArgs) 
-> models.api.ChecksIgnoreAddResults:
-    response = api.post(args)
-    return models.api.validate_checks_ignore_add(response)
-
-
 @api_get("/checks/list")
 def api_checks_list(api: ApiGet, project: str, version: str, revision: str) -> 
models.api.ChecksListResults:
     response = api.get(project, version, revision)
@@ -176,6 +171,24 @@ def api_checks_ongoing(
     return models.api.validate_checks_ongoing(response)
 
 
+@api_post("/ignore/add")
+def api_ignore_add(api: ApiPost, args: models.api.IgnoreAddArgs) -> 
models.api.IgnoreAddResults:
+    response = api.post(args)
+    return models.api.validate_ignore_add(response)
+
+
+@api_post("/ignore/delete")
+def api_ignore_delete(api: ApiPost, args: models.api.IgnoreDeleteArgs) -> 
models.api.IgnoreDeleteResults:
+    response = api.post(args)
+    return models.api.validate_ignore_delete(response)
+
+
+@api_get("/ignore/list")
+def api_ignore_list(api: ApiGet, committee: str) -> 
models.api.IgnoreListResults:
+    response = api.get(committee)
+    return models.api.validate_ignore_list(response)
+
+
 @api_post("/key/add")
 def api_key_add(api: ApiPost, args: models.api.KeyAddArgs) -> 
models.api.KeyAddResults:
     response = api.post(args)
@@ -354,8 +367,8 @@ def app_api(path: str, /, **kwargs: str) -> None:
     print(json.dumps(json_data, indent=None))
 
 
-@APP_CHECKS.command(name="exceptions", help="Get check exceptions for a 
release revision.")
-def app_checks_exceptions(
+@APP_CHECK.command(name="exceptions", help="Get check exceptions for a release 
revision.")
+def app_check_exceptions(
     project: str,
     version: str,
     revision: str,
@@ -366,8 +379,8 @@ def app_checks_exceptions(
     checks_display_status("exception", checks_list.checks, members=members)
 
 
-@APP_CHECKS.command(name="failures", help="Get check failures for a release 
revision.")
-def app_checks_failures(
+@APP_CHECK.command(name="failures", help="Get check failures for a release 
revision.")
+def app_check_failures(
     project: str,
     version: str,
     revision: str,
@@ -377,54 +390,9 @@ def app_checks_failures(
     checks_list = api_checks_list(project, version, revision)
     checks_display_status("failure", checks_list.checks, members=members)
 
-    # committee_name: str = schema.Field(..., **example("example"))
-    # release_glob: str | None = schema.Field(default=None, 
**example("example-0.0.*"))
-    # revision_number: str | None = schema.Field(default=None, 
**example("00001"))
-    # checker_glob: str | None = schema.Field(default=None, 
**example("atr.tasks.checks.license.files"))
-    # primary_rel_path_glob: str | None = schema.Field(default=None, 
**example("apache-example-0.0.1-*.tar.gz"))
-    # member_rel_path_glob: str | None = schema.Field(default=None, 
**example("apache-example-0.0.1/*.xml"))
-    # status: sql.CheckResultStatusIgnore | None = schema.Field(
-    #     default=None, **example(sql.CheckResultStatusIgnore.FAILURE)
-    # )
-    # message_glob: str | None = schema.Field(default=None, **example("sha512 
matches for apache-example-0.0.1/*.xml"))
-
-
-@APP_CHECKS.command(name="ignore", help="Ignore a check result.")
-def app_checks_ignore(
-    committee: str,
-    /,
-    release: str | None = None,
-    revision: str | None = None,
-    checker: str | None = None,
-    primary_rel_path: str | None = None,
-    member_rel_path: str | None = None,
-    status: models.sql.CheckResultStatusIgnore | None = None,
-    message: str | None = None,
-) -> None:
-    args = models.api.ChecksIgnoreAddArgs(
-        committee_name=committee,
-        release_glob=release,
-        revision_number=revision,
-        checker_glob=checker,
-        primary_rel_path_glob=primary_rel_path,
-        member_rel_path_glob=member_rel_path,
-        status=status,
-        message_glob=message,
-    )
-    api_checks_ignore_add(args)
-    print("Check result ignored for:")
-    print(f"  Committee: {committee}")
-    print(f"  Release (glob): {release}")
-    print(f"  Revision: {revision}")
-    print(f"  Checker (glob): {checker}")
-    print(f"  Primary rel path (glob): {primary_rel_path}")
-    print(f"  Member rel path (glob): {member_rel_path}")
-    print(f"  Status: {status}")
-    print(f"  Message (glob): {message}")
-
 
-@APP_CHECKS.command(name="status", help="Get check status for a release 
revision.")
-def app_checks_status(
+@APP_CHECK.command(name="status", help="Get check status for a release 
revision.")
+def app_check_status(
     project: str,
     version: str,
     /,
@@ -450,8 +418,8 @@ def app_checks_status(
     checks_display(checks_list.checks, verbose)
 
 
-@APP_CHECKS.command(name="wait", help="Wait for checks to be completed.")
-def app_checks_wait(
+@APP_CHECK.command(name="wait", help="Wait for checks to be completed.")
+def app_check_wait(
     project: str,
     version: str,
     /,
@@ -477,8 +445,8 @@ def app_checks_wait(
     print("Checks completed.")
 
 
-@APP_CHECKS.command(name="warnings", help="Get check warnings for a release 
revision.")
-def app_checks_warnings(
+@APP_CHECK.command(name="warnings", help="Get check warnings for a release 
revision.")
+def app_check_warnings(
     project: str,
     version: str,
     revision: str,
@@ -691,6 +659,63 @@ def app_drop(path: str, /) -> None:
     print(f"Removed {path}.")
 
 
+@APP_IGNORE.command(name="add", help="Add a check ignore.")
+def app_ignore_add(
+    committee: str,
+    /,
+    release: str | None = None,
+    revision: str | None = None,
+    checker: str | None = None,
+    primary_rel_path: str | None = None,
+    member_rel_path: str | None = None,
+    status: models.sql.CheckResultStatusIgnore | None = None,
+    message: str | None = None,
+) -> None:
+    args = models.api.IgnoreAddArgs(
+        committee_name=committee,
+        release_glob=release,
+        revision_number=revision,
+        checker_glob=checker,
+        primary_rel_path_glob=primary_rel_path,
+        member_rel_path_glob=member_rel_path,
+        status=status,
+        message_glob=message,
+    )
+    api_ignore_add(args)
+    print("Check result ignored for:")
+    print(f"  Committee: {committee}")
+    print(f"  Release (glob): {release}")
+    print(f"  Revision: {revision}")
+    print(f"  Checker (glob): {checker}")
+    print(f"  Primary rel path (glob): {primary_rel_path}")
+    print(f"  Member rel path (glob): {member_rel_path}")
+    print(f"  Status: {status}")
+    print(f"  Message (glob): {message}")
+
+
+@APP_IGNORE.command(name="delete", help="Delete a check ignore.")
+def app_ignore_delete(
+    committee: str,
+    id: int,
+    /,
+) -> None:
+    args = models.api.IgnoreDeleteArgs(committee=committee, id=id)
+    api_ignore_delete(args)
+    print("Check ignore deleted for:")
+    print(f"  Committee: {committee}")
+    print(f"  ID: {id}")
+
+
+@APP_IGNORE.command(name="list", help="List check ignores.")
+def app_ignore_list(
+    committee: str,
+    /,
+) -> None:
+    ignores = api_ignore_list(committee)
+    for ignore in ignores.ignores:
+        print(ignore.model_dump_json(indent=None))
+
+
 @APP_JWT.command(name="dump", help="Show decoded JWT payload from stored 
config.")
 def app_jwt_dump() -> None:
     jwt_value = config_jwt_get()
@@ -735,8 +760,8 @@ def app_jwt_show() -> None:
     return app_show("tokens.jwt")
 
 
-@APP_KEYS.command(name="add", help="Add an OpenPGP key.")
-def app_keys_add(path: str, committees: str = "", /) -> None:
+@APP_KEY.command(name="add", help="Add an OpenPGP key.")
+def app_key_add(path: str, committees: str = "", /) -> None:
     selected_committee_names = []
     if committees:
         selected_committee_names[:] = committees.split(",")
@@ -750,21 +775,21 @@ def app_keys_add(path: str, committees: str = "", /) -> 
None:
     print(keys_add.fingerprint)
 
 
-@APP_KEYS.command(name="delete", help="Delete an OpenPGP key.")
-def app_keys_delete(fingerprint: str, /) -> None:
+@APP_KEY.command(name="delete", help="Delete an OpenPGP key.")
+def app_key_delete(fingerprint: str, /) -> None:
     keys_delete_args = models.api.KeyDeleteArgs(fingerprint=fingerprint)
     keys_delete = api_key_delete(keys_delete_args)
     print(keys_delete.success)
 
 
-@APP_KEYS.command(name="get", help="Get an OpenPGP key.")
-def app_keys_get(fingerprint: str, /) -> None:
+@APP_KEY.command(name="get", help="Get an OpenPGP key.")
+def app_key_get(fingerprint: str, /) -> None:
     keys_get = api_key_get(fingerprint)
     print(keys_get.key.model_dump_json(indent=None))
 
 
-@APP_KEYS.command(name="upload", help="Upload a KEYS file.")
-def app_keys_upload(path: str, selected_committee_name: str, /) -> None:
+@APP_KEY.command(name="upload", help="Upload a KEYS file.")
+def app_key_upload(path: str, selected_committee_name: str, /) -> None:
     # selected_committee_names = []
     # if selected_committees:
     #     selected_committee_names[:] = selected_committees.split(",")
@@ -777,8 +802,8 @@ def app_keys_upload(path: str, selected_committee_name: 
str, /) -> None:
     print(f"Failed to upload {keys_upload.error_count} keys.")
 
 
-@APP_KEYS.command(name="user", help="List OpenPGP keys for a user.")
-def app_keys_user(asf_uid: str | None = None) -> None:
+@APP_KEY.command(name="user", help="List OpenPGP keys for a user.")
+def app_key_user(asf_uid: str | None = None) -> None:
     if asf_uid is None:
         with config_lock() as config:
             asf_uid = config_get(config, ["asf", "uid"])
@@ -1428,12 +1453,13 @@ def show_warning(message: str) -> None:
 
 
 def subcommands_register(app: cyclopts.App) -> None:
-    app.command(APP_CHECKS)
+    app.command(APP_CHECK)
     app.command(APP_CONFIG)
     app.command(APP_DEV)
     app.command(APP_DRAFT)
+    app.command(APP_IGNORE)
     app.command(APP_JWT)
-    app.command(APP_KEYS)
+    app.command(APP_KEY)
     app.command(APP_RELEASE)
     app.command(APP_SSH)
     app.command(APP_VOTE)
diff --git a/src/atrclient/models/api.py b/src/atrclient/models/api.py
index 009ae07..a0294cd 100644
--- a/src/atrclient/models/api.py
+++ b/src/atrclient/models/api.py
@@ -34,24 +34,6 @@ class ResultsTypeError(TypeError):
     pass
 
 
-class ChecksIgnoreAddArgs(schema.Strict):
-    committee_name: str = schema.Field(..., **example("example"))
-    release_glob: str | None = schema.Field(default=None, 
**example("example-0.0.*"))
-    revision_number: str | None = schema.Field(default=None, 
**example("00001"))
-    checker_glob: str | None = schema.Field(default=None, 
**example("atr.tasks.checks.license.files"))
-    primary_rel_path_glob: str | None = schema.Field(default=None, 
**example("apache-example-0.0.1-*.tar.gz"))
-    member_rel_path_glob: str | None = schema.Field(default=None, 
**example("apache-example-0.0.1/*.xml"))
-    status: sql.CheckResultStatusIgnore | None = schema.Field(
-        default=None, **example(sql.CheckResultStatusIgnore.FAILURE)
-    )
-    message_glob: str | None = schema.Field(default=None, **example("sha512 
matches for apache-example-0.0.1/*.xml"))
-
-
-class ChecksIgnoreAddResults(schema.Strict):
-    endpoint: Literal["/checks/ignore/add"] = schema.Field(alias="endpoint")
-    success: Literal[True] = schema.Field(..., **example(True))
-
-
 class ChecksListResults(schema.Strict):
     endpoint: Literal["/checks/list"] = schema.Field(alias="endpoint")
     checks: Sequence[sql.CheckResult]
@@ -89,6 +71,39 @@ class CommitteesListResults(schema.Strict):
     committees: Sequence[sql.Committee]
 
 
+class IgnoreAddArgs(schema.Strict):
+    committee_name: str = schema.Field(..., **example("example"))
+    release_glob: str | None = schema.Field(default=None, 
**example("example-0.0.*"))
+    revision_number: str | None = schema.Field(default=None, 
**example("00001"))
+    checker_glob: str | None = schema.Field(default=None, 
**example("atr.tasks.checks.license.files"))
+    primary_rel_path_glob: str | None = schema.Field(default=None, 
**example("apache-example-0.0.1-*.tar.gz"))
+    member_rel_path_glob: str | None = schema.Field(default=None, 
**example("apache-example-0.0.1/*.xml"))
+    status: sql.CheckResultStatusIgnore | None = schema.Field(
+        default=None, **example(sql.CheckResultStatusIgnore.FAILURE)
+    )
+    message_glob: str | None = schema.Field(default=None, **example("sha512 
matches for apache-example-0.0.1/*.xml"))
+
+
+class IgnoreAddResults(schema.Strict):
+    endpoint: Literal["/ignore/add"] = schema.Field(alias="endpoint")
+    success: Literal[True] = schema.Field(..., **example(True))
+
+
+class IgnoreDeleteArgs(schema.Strict):
+    committee: str = schema.Field(..., **example("example"))
+    id: int = schema.Field(..., **example(1))
+
+
+class IgnoreDeleteResults(schema.Strict):
+    endpoint: Literal["/ignore/delete"] = schema.Field(alias="endpoint")
+    success: Literal[True] = schema.Field(..., **example(True))
+
+
+class IgnoreListResults(schema.Strict):
+    endpoint: Literal["/ignore/list"] = schema.Field(alias="endpoint")
+    ignores: Sequence[sql.CheckResultIgnore]
+
+
 class JwtCreateArgs(schema.Strict):
     asfuid: str = schema.Field(..., **example("user"))
     pat: str = schema.Field(..., 
**example("8M5t4GCU63EdOy4NNXgXn7o-bc-muK8TRg5W-DeBaWY"))
@@ -399,13 +414,15 @@ class VoteTabulateResults(schema.Strict):
 # This is for *Results classes only
 # We do NOT put *Args classes here
 Results = Annotated[
-    ChecksIgnoreAddResults
-    | ChecksListResults
+    ChecksListResults
     | ChecksOngoingResults
     | CommitteeGetResults
     | CommitteeKeysResults
     | CommitteeProjectsResults
     | CommitteesListResults
+    | IgnoreAddResults
+    | IgnoreDeleteResults
+    | IgnoreListResults
     | JwtCreateResults
     | KeyAddResults
     | KeyDeleteResults
@@ -449,13 +466,15 @@ def validator[T](t: type[T]) -> Callable[[Any], T]:
     return validate
 
 
-validate_checks_ignore_add = validator(ChecksIgnoreAddResults)
 validate_checks_list = validator(ChecksListResults)
 validate_checks_ongoing = validator(ChecksOngoingResults)
 validate_committee_get = validator(CommitteeGetResults)
 validate_committee_keys = validator(CommitteeKeysResults)
 validate_committee_projects = validator(CommitteeProjectsResults)
 validate_committees_list = validator(CommitteesListResults)
+validate_ignore_add = validator(IgnoreAddResults)
+validate_ignore_delete = validator(IgnoreDeleteResults)
+validate_ignore_list = validator(IgnoreListResults)
 validate_jwt_create = validator(JwtCreateResults)
 validate_key_add = validator(KeyAddResults)
 validate_key_delete = validator(KeyDeleteResults)
diff --git a/src/atrclient/models/sql.py b/src/atrclient/models/sql.py
index d58121c..e77d213 100644
--- a/src/atrclient/models/sql.py
+++ b/src/atrclient/models/sql.py
@@ -737,6 +737,10 @@ class CheckResultIgnore(sqlmodel.SQLModel, table=True):
     )
     message_glob: str | None = sqlmodel.Field(**example("sha512 matches for 
apache-example-0.0.1/*.xml"))
 
+    def model_post_init(self, _context):
+        if isinstance(self.created, str):
+            self.created = 
datetime.datetime.fromisoformat(self.created.rstrip("Z"))
+
 
 # DistributionChannel: Project
 class DistributionChannel(sqlmodel.SQLModel, table=True):
diff --git a/tests/cli_keys.t b/tests/cli_keys.t
index dce0ce8..02226a6 100644
--- a/tests/cli_keys.t
+++ b/tests/cli_keys.t
@@ -13,7 +13,7 @@ $ atr dev pat
 $ atr set tokens.pat <!pat!>
 Set tokens.pat to "<!pat!>".
 
-$ atr keys user
+$ atr key user
 <.etc.>
 
 $ atr dev pwd
@@ -22,14 +22,14 @@ $ atr dev pwd
 <# write a test key to tooling-public-test.asc #>
 $ atr dev key
 
-$ atr keys add tooling-public-test.asc
+$ atr key add tooling-public-test.asc
 E35604DD9E2892E5465B3D8A203F105A7B33A64F
 
-$ atr keys get E35604DD9E2892E5465B3D8A203F105A7B33A64F
+$ atr key get E35604DD9E2892E5465B3D8A203F105A7B33A64F
 <.skip.>e35604dd9e2892e5465b3d8a203f105a7b33a64f<.skip.>example.invalid<.skip.>
 
-* atr keys delete E35604DD9E2892E5465B3D8A203F105A7B33A64F
+* atr key delete E35604DD9E2892E5465B3D8A203F105A7B33A64F
 <.etc.>
 
-$ atr keys upload tooling-public-test.asc tooling
+$ atr key upload tooling-public-test.asc tooling
 <.etc.>
diff --git a/tests/cli_workflow.t b/tests/cli_workflow.t
index 927426b..ce762d4 100644
--- a/tests/cli_workflow.t
+++ b/tests/cli_workflow.t
@@ -32,14 +32,14 @@ $ atr config path
 $ atr upload tooling-test-example 0.3+cli atr-client.conf <!config_rel_path!>
 <.skip.>created<.skip.>
 
-$ atr checks wait tooling-test-example 0.3+cli -i 25
+$ atr check wait tooling-test-example 0.3+cli -i 25
 Checks completed.
 
-$ atr checks status tooling-test-example 0.3+cli
+$ atr check status tooling-test-example 0.3+cli
 Total checks: 1
   warning: 1
 
-$ atr checks status tooling-test-example 0.3+cli 00002
+$ atr check status tooling-test-example 0.3+cli 00002
 Total checks: 1
   warning: 1
 
diff --git a/tests/test_all.py b/tests/test_all.py
index a42bca4..ffb28eb 100755
--- a/tests/test_all.py
+++ b/tests/test_all.py
@@ -75,7 +75,7 @@ def test_app_checks_status_non_draft_phase(
             },
         )
 
-        client.app_checks_status("test-project", "2.3.0", "00001")
+        client.app_check_status("test-project", "2.3.0", "00001")
 
         captured = capsys.readouterr()
         assert "Checks are not applicable for this release phase." in 
captured.out
@@ -150,7 +150,7 @@ def test_app_checks_status_verbose(capsys: 
pytest.CaptureFixture[str], fixture_c
         mock.get(release_url, status=200, payload=release_payload)
         mock.get(checks_url, status=200, payload=checks_payload)
 
-        client.app_checks_status("test-project", "2.3.1", "00003", 
verbose=True)
+        client.app_check_status("test-project", "2.3.1", "00003", verbose=True)
 
         captured = capsys.readouterr()
         assert "(top-level" in captured.out
diff --git a/uv.lock b/uv.lock
index 3adf94c..a3c74dc 100644
--- a/uv.lock
+++ b/uv.lock
@@ -2,7 +2,7 @@ version = 1
 requires-python = ">=3.13"
 
 [options]
-exclude-newer = "2025-07-29T19:43:00Z"
+exclude-newer = "2025-07-30T14:48:00Z"
 
 [[package]]
 name = "aiohappyeyeballs"
@@ -83,7 +83,7 @@ wheels = [
 
 [[package]]
 name = "apache-trusted-releases"
-version = "0.20250729.1943"
+version = "0.20250730.1448"
 source = { editable = "." }
 dependencies = [
     { name = "aiohttp" },


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to