dailybot-cli 3.19.0__tar.gz → 3.20.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/PKG-INFO +21 -19
  2. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/README.md +20 -18
  3. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/api_client.py +1 -1
  4. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/board.py +4 -37
  5. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/goal.py +3 -12
  6. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/project.py +10 -61
  7. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/public_api_helpers.py +17 -51
  8. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/task.py +2 -24
  9. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli.egg-info/PKG-INFO +21 -19
  10. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/pyproject.toml +1 -1
  11. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/LICENSE +0 -0
  12. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/__init__.py +0 -0
  13. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/__init__.py +0 -0
  14. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/_attachments.py +0 -0
  15. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/_beta.py +0 -0
  16. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/_briefing.py +0 -0
  17. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/_destructive.py +0 -0
  18. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/_favorites.py +0 -0
  19. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/_rollups.py +0 -0
  20. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/_writes.py +0 -0
  21. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/agent.py +0 -0
  22. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/ask.py +0 -0
  23. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/auth.py +0 -0
  24. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/authoring_helpers.py +0 -0
  25. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/channels.py +0 -0
  26. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/chat.py +0 -0
  27. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/checkin.py +0 -0
  28. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/config.py +0 -0
  29. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/conversation.py +0 -0
  30. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/env.py +0 -0
  31. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/featured.py +0 -0
  32. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/form.py +0 -0
  33. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/hook.py +0 -0
  34. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/identity.py +0 -0
  35. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/interactive.py +0 -0
  36. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/interactive_chat.py +0 -0
  37. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/kudos.py +0 -0
  38. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/label.py +0 -0
  39. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/query_options.py +0 -0
  40. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/status.py +0 -0
  41. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/tasks.py +0 -0
  42. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/team.py +0 -0
  43. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/uninstall.py +0 -0
  44. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/update.py +0 -0
  45. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/upgrade.py +0 -0
  46. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/user.py +0 -0
  47. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/user_scoped_actions.py +0 -0
  48. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/version.py +0 -0
  49. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/commands/workflow.py +0 -0
  50. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/config.py +0 -0
  51. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/display.py +0 -0
  52. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/install_method.py +0 -0
  53. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/ledger.py +0 -0
  54. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/main.py +0 -0
  55. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/tui/__init__.py +0 -0
  56. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/tui/app.py +0 -0
  57. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/tui/commands.py +0 -0
  58. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/tui/conversation.py +0 -0
  59. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli/tui/intents.py +0 -0
  60. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli.egg-info/SOURCES.txt +0 -0
  61. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli.egg-info/dependency_links.txt +0 -0
  62. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli.egg-info/entry_points.txt +0 -0
  63. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli.egg-info/requires.txt +0 -0
  64. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/dailybot_cli.egg-info/top_level.txt +0 -0
  65. {dailybot_cli-3.19.0 → dailybot_cli-3.20.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dailybot-cli
3
- Version: 3.19.0
3
+ Version: 3.20.0
4
4
  Summary: Dailybot CLI - The command-line bridge between humans and agents. Progress reports, observability, and workflow automation for modern teams across Slack, Google Chat, Discord, MS Teams, and more.
5
5
  Author-email: Dailybot <support@dailybot.com>
6
6
  License: MIT
@@ -905,11 +905,13 @@ Replies to agent emails land as messages retrievable via `dailybot agent message
905
905
  Projects, boards and tasks. Two groups: **`dailybot tasks`** answers questions about the
906
906
  workspace, **`dailybot task`** reads or changes one task.
907
907
 
908
- **Structure changes need a signed-in person (any non-guest member).** Creating, updating,
909
- archiving or restoring boards, columns, projects and goals, board and project membership,
910
- and linking goals to projects all work for every non-guest member after `dailybot login`.
911
- An organization API key can never hold `tasks:admin` and cannot change membership or
912
- participants — with only `DAILYBOT_API_KEY` those commands stop before sending anything.
908
+ **Your personal API key is you.** A login session (`dailybot login`) and a personal API key
909
+ (one bound to you) can do everything you can do in the web app: create, update, archive and
910
+ restore boards, columns, projects, goals and milestones, manage board and project
911
+ membership, link goals to projects, and work every task. That is every non-guest member, no
912
+ organization-admin prerequisite. Only an agent or organization key, with nobody behind it,
913
+ cannot act as a person: the server refuses it (`actor_required` exit 3, `insufficient_scope`
914
+ exit 4). Guests stay limited by their role.
913
915
  **Privacy is membership**, not org role: a `members` project or board is 404 (not visible)
914
916
  to anyone without a grant; invite a person or a team to close it.
915
917
  A board inside a `members` project follows the project's membership: whoever the project
@@ -954,9 +956,9 @@ humans who own them.
954
956
  | `dailybot task comments <uuid>` | List a task's comments |
955
957
  | `dailybot task link <a> <b> --type <rel>` | Relate two tasks (`blocks`, `relates_to`, `duplicates`) |
956
958
  | `dailybot task labels <uuid> --mode add\|remove\|replace` | Change a task's labels |
957
- | `dailybot task participants add\|list\|remove <task>` | Who is on a task (`--role participant\|watcher`); `list` works with a personal API key; add/remove **need `dailybot login`** |
959
+ | `dailybot task participants add\|list\|remove <task>` | Who is on a task (`--role participant\|watcher`); all need a person: `dailybot login` or a personal API key |
958
960
  | `dailybot task watch\|unwatch <task>` | Follow a task privately — **needs a person: `dailybot login` or a personal API key** |
959
- | `dailybot task mute\|unmute <task>` | Silence a task and stay on it (leaving is `participants remove`) — **needs `dailybot login`** |
961
+ | `dailybot task mute\|unmute <task>` | Silence a task and stay on it (leaving is `participants remove`) — **needs a person: `dailybot login` or a personal API key** |
960
962
  | `dailybot task relations <task>` · `task unlink <task> <relation>` | List links (direction included) / remove one; `unlink` confirms and has `--dry-run` |
961
963
  | `dailybot task comment-edit\|comment-delete <task> <comment>` | Edit (`-` reads stdin) or delete a comment; delete blanks the text and keeps the entry |
962
964
  | `dailybot task children <task>` · `task events <task>` · `task activity <task>` | Sub-tasks, raw event history, and the readable activity feed (`--updated-since`, `--type`) |
@@ -978,12 +980,12 @@ humans who own them.
978
980
  | `dailybot board labels <uuid>` | Labels available on the board — **needs a person: `dailybot login` or a personal API key** |
979
981
  | `dailybot board views <uuid>` | Your saved views on the board, plus the ETag a save needs (`--etag` prints only that) |
980
982
  | `dailybot board state create\|update\|archive\|restore\|reorder` | Manage columns. `archive` previews first and takes `--migrate-to <state>` to move the column's cards |
981
- | `dailybot board member add <board> <user>` (or `--team <team>`) · `board member remove <board> <user>` | Who can see the board — **needs `dailybot login`**. Membership is the privacy control (not org role). A team grant follows the team live. Last grant on a private board stays (`last_grant_cannot_be_removed`) |
983
+ | `dailybot board member add <board> <user>` (or `--team <team>`) · `board member remove <board> <user>` | Who can see the board — **needs a person: `dailybot login` or a personal API key**. Membership is the privacy control (not org role). A team grant follows the team live. Last grant on a private board stays (`last_grant_cannot_be_removed`) |
982
984
  | `dailybot board label create <board> -n <name>` | Create an organization label from the board — **needs a person: `dailybot login` or a personal API key** |
983
985
  | `dailybot board view save <board> -f views.json --if-match <etag>` | Replace your saved views (the whole list) — **needs a person: `dailybot login` or a personal API key** |
984
986
  | `dailybot board snapshot <uuid>` | The whole board in one request; carries the `delta_cursor` that `tasks changes` consumes |
985
987
  | `dailybot board update <uuid>` | Name, key (the old key stays reserved), visibility, estimate scale, auto-archive, project |
986
- | `dailybot board create --name <n> --project <uuid> --key <KEY>` | Create a board in a project; the key prefixes its tasks (`DSN-1`) — **needs `dailybot login`** (any non-guest member) |
988
+ | `dailybot board create --name <n> --project <uuid> --key <KEY>` | Create a board in a project; the key prefixes its tasks (`DSN-1`) — **needs a person: `dailybot login` or a personal API key** (any non-guest member) |
987
989
  | `dailybot board archive <uuid>` | Archive a board. **Cascade-archives its live tasks**, and restoring does not bring them back |
988
990
  | `dailybot board restore <uuid>` | Restore a board (cascaded tasks stay archived) |
989
991
  | `dailybot project list` | List projects (`--include progress`) |
@@ -993,28 +995,28 @@ humans who own them.
993
995
  | `dailybot project milestones [<uuid>]` | List milestones |
994
996
  | `dailybot project milestone-complete <p> <m>` | Complete a milestone. **Its open tasks stay open** |
995
997
  | `dailybot project milestone-reopen <p> <m>` | Reopen a milestone |
996
- | `dailybot project create --name <n>` | Create a project — **needs `dailybot login`** |
998
+ | `dailybot project create --name <n>` | Create a project — **needs a person: `dailybot login` or a personal API key** |
997
999
  | `dailybot project update <uuid>` | Name, description, lead, health, dates, visibility |
998
1000
  | `dailybot project restore <uuid>` | Restore an archived project (its boards and tasks stay archived) |
999
- | `dailybot project members <uuid>` · `project member add\|remove` | Who can see the project — people or whole teams (`--team`) — `members` works with a personal API key; `member add\|remove` **needs `dailybot login`** |
1000
- | `dailybot project views <uuid>` · `project view save` | Your saved views; save replaces the list and requires the ETag — **needs `dailybot login`** |
1001
+ | `dailybot project members <uuid>` · `project member add\|remove` | Who can see the project — people or whole teams (`--team`) — `members` works with a personal API key; `member add\|remove` **needs a person: `dailybot login` or a personal API key** |
1002
+ | `dailybot project views <uuid>` · `project view save` | Your saved views; save replaces the list and requires the ETag — **needs a person: `dailybot login` or a personal API key** |
1001
1003
  | `dailybot project milestone-create\|milestone-update\|milestone-delete` | Dated milestones; delete retires it (tasks keep pointing at it) |
1002
1004
  | `dailybot project archive <uuid>` | Archive a project |
1003
- | `dailybot project attach <uuid> <file>` · `project attachments` · `project attachment get\|delete` | Files on a project (≤5 MiB, one request). Attaching and deleting need `dailybot login` |
1005
+ | `dailybot project attach <uuid> <file>` · `project attachments` · `project attachment get\|delete` | Files on a project (≤5 MiB, one request). Attaching and deleting need a person (`dailybot login` or a personal API key) |
1004
1006
  | `dailybot goal list` | List goals (`--include` is repeatable: `--include progress --include projects`) |
1005
1007
  | `dailybot goal get <uuid>` | Show one goal, with its progress and linked projects (always included) |
1006
- | `dailybot goal create --name <n> --period-start <d> --period-end <d>` | Create a goal (a dated commitment) — **needs `dailybot login`** |
1008
+ | `dailybot goal create --name <n> --period-start <d> --period-end <d>` | Create a goal (a dated commitment) — **needs a person: `dailybot login` or a personal API key** |
1007
1009
  | `dailybot goal update <uuid>` | Name, period, owner, team, and the declared `--status` (not_started, on_track, at_risk, off_track, achieved, missed) |
1008
1010
  | `dailybot goal restore <uuid>` | Restore an archived goal |
1009
1011
  | `dailybot goal link\|unlink <goal> <project>` | Make a project count toward a goal (or stop it) |
1010
1012
  | `dailybot goal archive <uuid>` | Archive a goal (its projects are not archived) |
1011
- | `dailybot goal attach <uuid> <file>` · `goal attachments` · `goal attachment get\|delete` | Files on a goal (≤5 MiB, one request). Attaching and deleting need `dailybot login` |
1013
+ | `dailybot goal attach <uuid> <file>` · `goal attachments` · `goal attachment get\|delete` | Files on a goal (≤5 MiB, one request). Attaching and deleting need a person (`dailybot login` or a personal API key) |
1012
1014
 
1013
1015
  **Three things worth knowing before you script against this:**
1014
1016
 
1015
- - **Some doors need a person.** An organization API key has no answer for "my tasks" or
1016
- "my inbox", can never hold `tasks:admin`, and cannot change membership or participants —
1017
- so container creates and invites need `dailybot login` as a non-guest member. A 404 means
1017
+ - **Some doors need a person.** A login session or a personal API key is a person; an agent
1018
+ or organization key has no answer for "my tasks" or "my inbox", can never hold
1019
+ `tasks:admin`, and cannot change membership or participants. A 404 means
1018
1020
  not visible (wrong id, private without a grant, or another org) — never "not allowed".
1019
1021
  - **Roll-ups are opt-in.** A field you did not request with `--include` is **absent** from
1020
1022
  the payload. Absent, `null` and `0` are three different answers, and the CLI renders them
@@ -866,11 +866,13 @@ Replies to agent emails land as messages retrievable via `dailybot agent message
866
866
  Projects, boards and tasks. Two groups: **`dailybot tasks`** answers questions about the
867
867
  workspace, **`dailybot task`** reads or changes one task.
868
868
 
869
- **Structure changes need a signed-in person (any non-guest member).** Creating, updating,
870
- archiving or restoring boards, columns, projects and goals, board and project membership,
871
- and linking goals to projects all work for every non-guest member after `dailybot login`.
872
- An organization API key can never hold `tasks:admin` and cannot change membership or
873
- participants — with only `DAILYBOT_API_KEY` those commands stop before sending anything.
869
+ **Your personal API key is you.** A login session (`dailybot login`) and a personal API key
870
+ (one bound to you) can do everything you can do in the web app: create, update, archive and
871
+ restore boards, columns, projects, goals and milestones, manage board and project
872
+ membership, link goals to projects, and work every task. That is every non-guest member, no
873
+ organization-admin prerequisite. Only an agent or organization key, with nobody behind it,
874
+ cannot act as a person: the server refuses it (`actor_required` exit 3, `insufficient_scope`
875
+ exit 4). Guests stay limited by their role.
874
876
  **Privacy is membership**, not org role: a `members` project or board is 404 (not visible)
875
877
  to anyone without a grant; invite a person or a team to close it.
876
878
  A board inside a `members` project follows the project's membership: whoever the project
@@ -915,9 +917,9 @@ humans who own them.
915
917
  | `dailybot task comments <uuid>` | List a task's comments |
916
918
  | `dailybot task link <a> <b> --type <rel>` | Relate two tasks (`blocks`, `relates_to`, `duplicates`) |
917
919
  | `dailybot task labels <uuid> --mode add\|remove\|replace` | Change a task's labels |
918
- | `dailybot task participants add\|list\|remove <task>` | Who is on a task (`--role participant\|watcher`); `list` works with a personal API key; add/remove **need `dailybot login`** |
920
+ | `dailybot task participants add\|list\|remove <task>` | Who is on a task (`--role participant\|watcher`); all need a person: `dailybot login` or a personal API key |
919
921
  | `dailybot task watch\|unwatch <task>` | Follow a task privately — **needs a person: `dailybot login` or a personal API key** |
920
- | `dailybot task mute\|unmute <task>` | Silence a task and stay on it (leaving is `participants remove`) — **needs `dailybot login`** |
922
+ | `dailybot task mute\|unmute <task>` | Silence a task and stay on it (leaving is `participants remove`) — **needs a person: `dailybot login` or a personal API key** |
921
923
  | `dailybot task relations <task>` · `task unlink <task> <relation>` | List links (direction included) / remove one; `unlink` confirms and has `--dry-run` |
922
924
  | `dailybot task comment-edit\|comment-delete <task> <comment>` | Edit (`-` reads stdin) or delete a comment; delete blanks the text and keeps the entry |
923
925
  | `dailybot task children <task>` · `task events <task>` · `task activity <task>` | Sub-tasks, raw event history, and the readable activity feed (`--updated-since`, `--type`) |
@@ -939,12 +941,12 @@ humans who own them.
939
941
  | `dailybot board labels <uuid>` | Labels available on the board — **needs a person: `dailybot login` or a personal API key** |
940
942
  | `dailybot board views <uuid>` | Your saved views on the board, plus the ETag a save needs (`--etag` prints only that) |
941
943
  | `dailybot board state create\|update\|archive\|restore\|reorder` | Manage columns. `archive` previews first and takes `--migrate-to <state>` to move the column's cards |
942
- | `dailybot board member add <board> <user>` (or `--team <team>`) · `board member remove <board> <user>` | Who can see the board — **needs `dailybot login`**. Membership is the privacy control (not org role). A team grant follows the team live. Last grant on a private board stays (`last_grant_cannot_be_removed`) |
944
+ | `dailybot board member add <board> <user>` (or `--team <team>`) · `board member remove <board> <user>` | Who can see the board — **needs a person: `dailybot login` or a personal API key**. Membership is the privacy control (not org role). A team grant follows the team live. Last grant on a private board stays (`last_grant_cannot_be_removed`) |
943
945
  | `dailybot board label create <board> -n <name>` | Create an organization label from the board — **needs a person: `dailybot login` or a personal API key** |
944
946
  | `dailybot board view save <board> -f views.json --if-match <etag>` | Replace your saved views (the whole list) — **needs a person: `dailybot login` or a personal API key** |
945
947
  | `dailybot board snapshot <uuid>` | The whole board in one request; carries the `delta_cursor` that `tasks changes` consumes |
946
948
  | `dailybot board update <uuid>` | Name, key (the old key stays reserved), visibility, estimate scale, auto-archive, project |
947
- | `dailybot board create --name <n> --project <uuid> --key <KEY>` | Create a board in a project; the key prefixes its tasks (`DSN-1`) — **needs `dailybot login`** (any non-guest member) |
949
+ | `dailybot board create --name <n> --project <uuid> --key <KEY>` | Create a board in a project; the key prefixes its tasks (`DSN-1`) — **needs a person: `dailybot login` or a personal API key** (any non-guest member) |
948
950
  | `dailybot board archive <uuid>` | Archive a board. **Cascade-archives its live tasks**, and restoring does not bring them back |
949
951
  | `dailybot board restore <uuid>` | Restore a board (cascaded tasks stay archived) |
950
952
  | `dailybot project list` | List projects (`--include progress`) |
@@ -954,28 +956,28 @@ humans who own them.
954
956
  | `dailybot project milestones [<uuid>]` | List milestones |
955
957
  | `dailybot project milestone-complete <p> <m>` | Complete a milestone. **Its open tasks stay open** |
956
958
  | `dailybot project milestone-reopen <p> <m>` | Reopen a milestone |
957
- | `dailybot project create --name <n>` | Create a project — **needs `dailybot login`** |
959
+ | `dailybot project create --name <n>` | Create a project — **needs a person: `dailybot login` or a personal API key** |
958
960
  | `dailybot project update <uuid>` | Name, description, lead, health, dates, visibility |
959
961
  | `dailybot project restore <uuid>` | Restore an archived project (its boards and tasks stay archived) |
960
- | `dailybot project members <uuid>` · `project member add\|remove` | Who can see the project — people or whole teams (`--team`) — `members` works with a personal API key; `member add\|remove` **needs `dailybot login`** |
961
- | `dailybot project views <uuid>` · `project view save` | Your saved views; save replaces the list and requires the ETag — **needs `dailybot login`** |
962
+ | `dailybot project members <uuid>` · `project member add\|remove` | Who can see the project — people or whole teams (`--team`) — `members` works with a personal API key; `member add\|remove` **needs a person: `dailybot login` or a personal API key** |
963
+ | `dailybot project views <uuid>` · `project view save` | Your saved views; save replaces the list and requires the ETag — **needs a person: `dailybot login` or a personal API key** |
962
964
  | `dailybot project milestone-create\|milestone-update\|milestone-delete` | Dated milestones; delete retires it (tasks keep pointing at it) |
963
965
  | `dailybot project archive <uuid>` | Archive a project |
964
- | `dailybot project attach <uuid> <file>` · `project attachments` · `project attachment get\|delete` | Files on a project (≤5 MiB, one request). Attaching and deleting need `dailybot login` |
966
+ | `dailybot project attach <uuid> <file>` · `project attachments` · `project attachment get\|delete` | Files on a project (≤5 MiB, one request). Attaching and deleting need a person (`dailybot login` or a personal API key) |
965
967
  | `dailybot goal list` | List goals (`--include` is repeatable: `--include progress --include projects`) |
966
968
  | `dailybot goal get <uuid>` | Show one goal, with its progress and linked projects (always included) |
967
- | `dailybot goal create --name <n> --period-start <d> --period-end <d>` | Create a goal (a dated commitment) — **needs `dailybot login`** |
969
+ | `dailybot goal create --name <n> --period-start <d> --period-end <d>` | Create a goal (a dated commitment) — **needs a person: `dailybot login` or a personal API key** |
968
970
  | `dailybot goal update <uuid>` | Name, period, owner, team, and the declared `--status` (not_started, on_track, at_risk, off_track, achieved, missed) |
969
971
  | `dailybot goal restore <uuid>` | Restore an archived goal |
970
972
  | `dailybot goal link\|unlink <goal> <project>` | Make a project count toward a goal (or stop it) |
971
973
  | `dailybot goal archive <uuid>` | Archive a goal (its projects are not archived) |
972
- | `dailybot goal attach <uuid> <file>` · `goal attachments` · `goal attachment get\|delete` | Files on a goal (≤5 MiB, one request). Attaching and deleting need `dailybot login` |
974
+ | `dailybot goal attach <uuid> <file>` · `goal attachments` · `goal attachment get\|delete` | Files on a goal (≤5 MiB, one request). Attaching and deleting need a person (`dailybot login` or a personal API key) |
973
975
 
974
976
  **Three things worth knowing before you script against this:**
975
977
 
976
- - **Some doors need a person.** An organization API key has no answer for "my tasks" or
977
- "my inbox", can never hold `tasks:admin`, and cannot change membership or participants —
978
- so container creates and invites need `dailybot login` as a non-guest member. A 404 means
978
+ - **Some doors need a person.** A login session or a personal API key is a person; an agent
979
+ or organization key has no answer for "my tasks" or "my inbox", can never hold
980
+ `tasks:admin`, and cannot change membership or participants. A 404 means
979
981
  not visible (wrong id, private without a grant, or another org) — never "not allowed".
980
982
  - **Roll-ups are opt-in.** A field you did not request with `--include` is **absent** from
981
983
  the payload. Absent, `null` and `0` are three different answers, and the CLI renders them
@@ -2885,7 +2885,7 @@ class DailyBotClient:
2885
2885
  return self._tasks_write(
2886
2886
  "POST",
2887
2887
  f"tasks/{_path_segment(task_uuid)}/labels/batch/",
2888
- json={"mode": mode, "labels": labels},
2888
+ json={"mode": mode, "label_uuids": labels},
2889
2889
  idempotent=True,
2890
2890
  idempotency_key=idempotency_key,
2891
2891
  )
@@ -21,7 +21,6 @@ from dailybot_cli.commands.public_api_helpers import (
21
21
  emit_json,
22
22
  exit_for_tasks_error,
23
23
  load_json_input,
24
- refuse_without_person,
25
24
  require_auth,
26
25
  rows_of,
27
26
  )
@@ -32,7 +31,6 @@ from dailybot_cli.commands.query_options import (
32
31
  query_options,
33
32
  resolve_fetch_all,
34
33
  )
35
- from dailybot_cli.config import get_person_token
36
34
  from dailybot_cli.display import (
37
35
  console,
38
36
  print_board_snapshot,
@@ -479,7 +477,6 @@ def board_state_create(
479
477
  dailybot board state create <board-uuid> -n "In review" --category in_progress
480
478
  dailybot board state create <board-uuid> -n Blocked --category todo --position 2 --json
481
479
  """
482
- _require_person_for_admin("board state create", json_mode=json_mode)
483
480
  client = require_auth()
484
481
  try:
485
482
  with console.status("Adding the column..."):
@@ -528,7 +525,6 @@ def board_state_update(
528
525
  dailybot board state update <board-uuid> <state-uuid> --name "Shipped"
529
526
  dailybot board state update <board-uuid> <state-uuid> --position 1 --json
530
527
  """
531
- _require_person_for_admin("board state update", json_mode=json_mode)
532
528
  fields: dict[str, Any] = {
533
529
  k: v
534
530
  for k, v in {"name": name, "color": color, "position": position}.items()
@@ -578,7 +574,6 @@ def board_state_archive(
578
574
  dailybot board state archive <board-uuid> <state-uuid> --dry-run
579
575
  dailybot board state archive <board-uuid> <state-uuid> --migrate-to <other-state> --yes
580
576
  """
581
- _require_person_for_admin("board state archive", json_mode=json_mode)
582
577
  client = require_auth()
583
578
  if not preview_then_confirm(
584
579
  lambda: client.archive_board_state(
@@ -613,7 +608,6 @@ def board_state_restore(board_uuid: str, state_uuid: str, json_mode: bool) -> No
613
608
  Examples:
614
609
  dailybot board state restore <board-uuid> <state-uuid>
615
610
  """
616
- _require_person_for_admin("board state restore", json_mode=json_mode)
617
611
  client = require_auth()
618
612
  try:
619
613
  with console.status("Restoring the column..."):
@@ -642,7 +636,6 @@ def board_state_reorder(board_uuid: str, state_uuids: tuple[str, ...], json_mode
642
636
  Examples:
643
637
  dailybot board state reorder <board-uuid> <backlog> <todo> <doing> <done>
644
638
  """
645
- _require_person_for_admin("board state reorder", json_mode=json_mode)
646
639
  if len(set(state_uuids)) != len(state_uuids):
647
640
  raise click.UsageError("A column appears twice. List each live column exactly once.")
648
641
  client = require_auth()
@@ -669,7 +662,7 @@ _MEMBER_WRITE_REASON: str = (
669
662
 
670
663
  @board.group("member")
671
664
  def board_member() -> None:
672
- """Invite or remove who can see a board. Needs `dailybot login`.
665
+ """Invite or remove who can see a board. Needs a person: `dailybot login` or a personal API key.
673
666
 
674
667
  \b
675
668
  Membership is the privacy control: a `members` board is invisible (404) to
@@ -713,7 +706,6 @@ def board_member_add(
713
706
  dailybot board member add <board-uuid> <user-uuid>
714
707
  dailybot board member add <board-uuid> --team <team-uuid> --json
715
708
  """
716
- _require_person_for_admin("board member add", json_mode=json_mode)
717
709
  if (user_uuid is None) == (team_uuid is None):
718
710
  raise click.UsageError("Pass exactly one of USER or --team.")
719
711
  client = require_auth()
@@ -751,7 +743,6 @@ def board_member_remove(
751
743
  dailybot board member remove <board-uuid> <user-uuid> --dry-run
752
744
  dailybot board member remove <board-uuid> <user-uuid> --yes
753
745
  """
754
- _require_person_for_admin("board member remove", json_mode=json_mode)
755
746
  if not confirm_without_preview(
756
747
  f"remove user {user_uuid} from board {board_uuid}; they lose sight of it if it is private.",
757
748
  assume_yes=assume_yes,
@@ -930,26 +921,6 @@ def board_snapshot(board_uuid: str, json_mode: bool) -> None:
930
921
  print_board_snapshot(data)
931
922
 
932
923
 
933
- def _require_person_for_admin(action: str, *, json_mode: bool) -> None:
934
- """Refuse an organization API key on a structure or membership door.
935
-
936
- Open-org Tasks: every non-guest member holds `tasks:admin` on a person session,
937
- so the CLI never asks for an organization-admin role. An organization API key
938
- still cannot store that scope (or change membership / participants), so the
939
- message blames the **credential kind** — run `dailybot login` as a member.
940
- """
941
- # Gate on the absence of a person token, not on the presence of a key: both
942
- # can be configured at once, and Bearer is sent first when it exists.
943
- if get_person_token() is None:
944
- refuse_without_person(
945
- f"`{action}` needs a signed-in person. An organization API key can never hold "
946
- "the `tasks:admin` scope (it cannot even be stored on one), and keys cannot "
947
- "change membership. Run `dailybot login` and retry as a non-guest member.",
948
- json_mode=json_mode,
949
- admin=True,
950
- )
951
-
952
-
953
924
  @board.command("create")
954
925
  @click.option("-n", "--name", required=True, help="Board name.")
955
926
  # Boards have no description field (BoardWrite declares none), so the flag the CLI
@@ -977,11 +948,11 @@ def board_create(
977
948
  idempotency_key: str | None,
978
949
  json_mode: bool,
979
950
  ) -> None:
980
- """Create a board in a project. Needs a signed-in person (any non-guest member).
951
+ """Create a board in a project. Needs a person (any non-guest member): `dailybot login` or a personal API key.
981
952
 
982
953
  \b
983
- An organization API key cannot do this — run `dailybot login` first. Visibility
984
- and who can see the board are controlled by invite/remove, not by org role.
954
+ An agent or organization key cannot do this. Visibility and who can see the
955
+ board are controlled by invite/remove, not by org role.
985
956
 
986
957
  \b
987
958
  Examples:
@@ -992,7 +963,6 @@ def board_create(
992
963
  "Boards have no description. Put the context in a project "
993
964
  "(`dailybot project create -d ...`) and create the board under it."
994
965
  )
995
- _require_person_for_admin("board create", json_mode=json_mode)
996
966
  client = require_auth()
997
967
  try:
998
968
  with console.status("Creating the board..."):
@@ -1054,7 +1024,6 @@ def board_update(
1054
1024
  dailybot board update <board-uuid> --name "Design (Q4)"
1055
1025
  dailybot board update <board-uuid> --key DSN --visibility members --json
1056
1026
  """
1057
- _require_person_for_admin("board update", json_mode=json_mode)
1058
1027
  fields: dict[str, Any] = {
1059
1028
  k: v
1060
1029
  for k, v in {
@@ -1105,7 +1074,6 @@ def board_archive(
1105
1074
  Examples:
1106
1075
  dailybot board archive <board-uuid> --dry-run
1107
1076
  """
1108
- _require_person_for_admin("board archive", json_mode=json_mode)
1109
1077
  client = require_auth()
1110
1078
  if not preview_then_confirm(
1111
1079
  lambda: client.archive_board(board_uuid, dry_run=True),
@@ -1146,7 +1114,6 @@ def board_restore(board_uuid: str, idempotency_key: str | None, json_mode: bool)
1146
1114
  Examples:
1147
1115
  dailybot board restore <board-uuid>
1148
1116
  """
1149
- _require_person_for_admin("board restore", json_mode=json_mode)
1150
1117
  client = require_auth()
1151
1118
  try:
1152
1119
  with console.status("Restoring the board..."):
@@ -16,7 +16,6 @@ from dailybot_cli.commands.project import (
16
16
  GOAL_INCLUDE_VALUES,
17
17
  _envelope,
18
18
  _include_list,
19
- _require_person_for_admin,
20
19
  )
21
20
  from dailybot_cli.commands.public_api_helpers import (
22
21
  emit_json,
@@ -186,7 +185,7 @@ def goal_create(
186
185
  idempotency_key: str | None,
187
186
  json_mode: bool,
188
187
  ) -> None:
189
- """Create a goal. Needs a signed-in person (any non-guest member).
188
+ """Create a goal. Needs a person (any non-guest member): `dailybot login` or a personal API key.
190
189
 
191
190
  \b
192
191
  A goal is a dated commitment, so both ends of its period are required. It starts
@@ -198,7 +197,6 @@ def goal_create(
198
197
  """
199
198
  if period_end < period_start:
200
199
  raise click.UsageError("--period-end is before --period-start.")
201
- _require_person_for_admin("goal create", json_mode=json_mode)
202
200
  client = require_auth()
203
201
  try:
204
202
  with console.status("Creating the goal..."):
@@ -234,7 +232,6 @@ def goal_archive(
234
232
  Examples:
235
233
  dailybot goal archive <goal-uuid> --dry-run
236
234
  """
237
- _require_person_for_admin("goal archive", json_mode=json_mode)
238
235
  client = require_auth()
239
236
  if not preview_then_confirm(
240
237
  lambda: client.archive_goal(goal_uuid, dry_run=True),
@@ -305,7 +302,6 @@ def goal_update(
305
302
  dailybot goal update <goal-uuid> --status at_risk
306
303
  dailybot goal update <goal-uuid> --period-end 2027-01-31 --owner <user-uuid> --json
307
304
  """
308
- _require_person_for_admin("goal update", json_mode=json_mode)
309
305
  if period_start and period_end and period_end < period_start:
310
306
  raise click.UsageError("--period-end is before --period-start.")
311
307
  fields: dict[str, Any] = {
@@ -349,7 +345,6 @@ def goal_restore(goal_uuid: str, json_mode: bool) -> None:
349
345
  Examples:
350
346
  dailybot goal restore <goal-uuid>
351
347
  """
352
- _require_person_for_admin("goal restore", json_mode=json_mode)
353
348
  client = require_auth()
354
349
  try:
355
350
  with console.status("Restoring the goal..."):
@@ -377,7 +372,6 @@ def goal_link(goal_uuid: str, project_uuid: str, json_mode: bool) -> None:
377
372
  Examples:
378
373
  dailybot goal link <goal-uuid> <project-uuid>
379
374
  """
380
- _require_person_for_admin("goal link", json_mode=json_mode)
381
375
  client = require_auth()
382
376
  try:
383
377
  with console.status("Linking the project..."):
@@ -406,7 +400,6 @@ def goal_unlink(
406
400
  dailybot goal unlink <goal-uuid> <project-uuid> --dry-run
407
401
  dailybot goal unlink <goal-uuid> <project-uuid> --yes
408
402
  """
409
- _require_person_for_admin("goal unlink", json_mode=json_mode)
410
403
  if not confirm_without_preview(
411
404
  f"unlink project {project_uuid} from goal {goal_uuid}; its work stops counting toward "
412
405
  "the goal.",
@@ -443,7 +436,7 @@ def goal_unlink(
443
436
  @click.option("--caption", default=None, help="Short caption shown with the file.")
444
437
  @click.option("--json", "json_mode", is_flag=True, help="Emit machine-readable JSON to stdout.")
445
438
  def goal_attach(goal_uuid: str, file_path: Path, caption: str | None, json_mode: bool) -> None:
446
- """Attach a file to a goal. Needs a signed-in person (any non-guest member).
439
+ """Attach a file to a goal. Needs a person (any non-guest member): `dailybot login` or a personal API key.
447
440
 
448
441
  \b
449
442
  One request, up to 5 MiB. Your Dailybot credentials go only to the API.
@@ -453,7 +446,6 @@ def goal_attach(goal_uuid: str, file_path: Path, caption: str | None, json_mode:
453
446
  dailybot goal attach <goal-uuid> ./plan.pdf
454
447
  dailybot goal attach <goal-uuid> ./roadmap.png --caption "Q4 roadmap" --json
455
448
  """
456
- _require_person_for_admin("goal attach", json_mode=json_mode)
457
449
  run_attach(
458
450
  lambda client, **file: client.upload_goal_attachment(goal_uuid, **file),
459
451
  file_path,
@@ -535,14 +527,13 @@ def goal_attachment_get(
535
527
  def goal_attachment_delete(
536
528
  goal_uuid: str, attachment_uuid: str, dry_run: bool, assume_yes: bool, json_mode: bool
537
529
  ) -> None:
538
- """Remove an attachment from a goal. This cannot be undone. Needs a signed-in person.
530
+ """Remove an attachment from a goal. This cannot be undone. Needs a person: `dailybot login` or a personal API key.
539
531
 
540
532
  \b
541
533
  Examples:
542
534
  dailybot goal attachment delete <goal-uuid> <attachment-uuid> --dry-run
543
535
  dailybot goal attachment delete <goal-uuid> <attachment-uuid> --yes
544
536
  """
545
- _require_person_for_admin("goal attachment delete", json_mode=json_mode)
546
537
  run_delete(
547
538
  lambda client: client.delete_goal_attachment(goal_uuid, attachment_uuid),
548
539
  f"delete attachment {attachment_uuid} from goal {goal_uuid}.",
@@ -16,12 +16,10 @@ from dailybot_cli.commands.public_api_helpers import (
16
16
  emit_json,
17
17
  exit_for_tasks_error,
18
18
  load_json_input,
19
- refuse_without_person,
20
19
  require_auth,
21
20
  rows_of,
22
21
  )
23
22
  from dailybot_cli.commands.query_options import build_query_params, query_options, resolve_fetch_all
24
- from dailybot_cli.config import get_person_token
25
23
  from dailybot_cli.display import (
26
24
  console,
27
25
  present_untrusted,
@@ -381,24 +379,6 @@ def project_milestone_reopen(
381
379
  report_write(data, "Milestone reopened")
382
380
 
383
381
 
384
- def _require_person_for_admin(action: str, *, json_mode: bool) -> None:
385
- """Refuse an organization API key on a structure or membership door.
386
-
387
- See board.py: open-org Tasks gives every non-guest member structure access on
388
- a person session; keys still cannot store `tasks:admin` or change membership.
389
- """
390
- # Gate on the absence of a person token, not on the presence of a key: both
391
- # can be configured at once, and Bearer is sent first when it exists.
392
- if get_person_token() is None:
393
- refuse_without_person(
394
- f"`{action}` needs a signed-in person. An organization API key can never hold "
395
- "the `tasks:admin` scope (it cannot even be stored on one), and keys cannot "
396
- "change membership. Run `dailybot login` and retry as a non-guest member.",
397
- json_mode=json_mode,
398
- admin=True,
399
- )
400
-
401
-
402
382
  def _project_fields(
403
383
  visibility: str | None,
404
384
  lead: str | None,
@@ -461,7 +441,7 @@ def project_create(
461
441
  idempotency_key: str | None,
462
442
  json_mode: bool,
463
443
  ) -> None:
464
- """Create a project. Needs a signed-in person (any non-guest member).
444
+ """Create a project. Needs a person (any non-guest member): `dailybot login` or a personal API key.
465
445
 
466
446
  \b
467
447
  Examples:
@@ -469,7 +449,6 @@ def project_create(
469
449
  dailybot project create -n "Apollo" --lead <user-uuid> --target-date 2026-12-15 --json
470
450
  """
471
451
  extra: dict[str, Any] = _project_fields(visibility, lead, health, start_date, target_date)
472
- _require_person_for_admin("project create", json_mode=json_mode)
473
452
  client = require_auth()
474
453
  try:
475
454
  with console.status("Creating the project..."):
@@ -499,7 +478,6 @@ def project_archive(
499
478
  Examples:
500
479
  dailybot project archive <project-uuid> --dry-run
501
480
  """
502
- _require_person_for_admin("project archive", json_mode=json_mode)
503
481
  client = require_auth()
504
482
  if not preview_then_confirm(
505
483
  lambda: client.archive_project(project_uuid, dry_run=True),
@@ -543,8 +521,8 @@ def project_update(
543
521
  """Change a project's name, lead, health, dates or visibility.
544
522
 
545
523
  \b
546
- Only the fields you pass are sent. Any signed-in non-guest member can update;
547
- an organization API key cannot. Setting `--visibility members` privatizes the
524
+ Only the fields you pass are sent. Any non-guest member can update, with
525
+ `dailybot login` or a personal API key; an agent or organization key cannot. Setting `--visibility members` privatizes the
548
526
  project and auto-grants you; invite others with `project member add`. To post
549
527
  a status note for the team, use `dailybot project update-post` instead.
550
528
 
@@ -553,7 +531,6 @@ def project_update(
553
531
  dailybot project update <project-uuid> --health at_risk
554
532
  dailybot project update <project-uuid> --target-date 2027-01-15 --lead <user-uuid> --json
555
533
  """
556
- _require_person_for_admin("project update", json_mode=json_mode)
557
534
  fields: dict[str, Any] = _project_fields(visibility, lead, health, start_date, target_date)
558
535
  if name is not None:
559
536
  fields["name"] = name
@@ -590,7 +567,6 @@ def project_restore(project_uuid: str, idempotency_key: str | None, json_mode: b
590
567
  Examples:
591
568
  dailybot project restore <project-uuid>
592
569
  """
593
- _require_person_for_admin("project restore", json_mode=json_mode)
594
570
  client = require_auth()
595
571
  try:
596
572
  with console.status("Restoring the project..."):
@@ -606,13 +582,9 @@ def project_restore(project_uuid: str, idempotency_key: str | None, json_mode: b
606
582
 
607
583
 
608
584
  # ---------------------------------------------------------------------------
609
- # Members and saved views — person-only
585
+ # Members and saved views
610
586
  # ---------------------------------------------------------------------------
611
587
 
612
- _MEMBER_REASON: str = (
613
- "changes or reveals who can see a private project, and no organization API key may do "
614
- "that — there is no person behind it to be accountable."
615
- )
616
588
  _MEMBER_COLUMNS: list[tuple[str, str, bool]] = [
617
589
  ("Kind", "subject_type", True),
618
590
  ("Name", "name", False),
@@ -627,15 +599,6 @@ _VIEW_COLUMNS: list[tuple[str, str, bool]] = [
627
599
  ]
628
600
 
629
601
 
630
- def _require_person(action: str, reason: str, *, json_mode: bool) -> None:
631
- """Refuse an API key on a person-only project door, before any request."""
632
- if get_person_token() is None:
633
- refuse_without_person(
634
- f"`{action}` {reason} Run `dailybot login` and retry as a signed-in person.",
635
- json_mode=json_mode,
636
- )
637
-
638
-
639
602
  @project.command("members")
640
603
  @click.argument("project_uuid", metavar="PROJECT")
641
604
  @click.option("--json", "json_mode", is_flag=True, help="Emit machine-readable JSON to stdout.")
@@ -660,14 +623,14 @@ def project_members(project_uuid: str, json_mode: bool) -> None:
660
623
 
661
624
  @project.group("member")
662
625
  def project_member() -> None:
663
- """Invite or remove people and teams on a project. Needs `dailybot login`.
626
+ """Invite or remove people and teams on a project. Needs a person: `dailybot login` or a personal API key.
664
627
 
665
628
  \b
666
629
  Membership is the privacy control: a `members` project is not visible (404)
667
630
  to anyone without a grant. Org-wide projects are a shared workspace. Invite a
668
631
  person or a team to close a private project; the last grant cannot be removed
669
- (`last_grant_cannot_be_removed`). There is no project role to edit. An
670
- organization API key cannot change membership.
632
+ (`last_grant_cannot_be_removed`). There is no project role to edit. An agent or
633
+ organization key cannot change membership.
671
634
 
672
635
  \b
673
636
  Examples:
@@ -696,7 +659,6 @@ def project_member_add(
696
659
  dailybot project member add <project-uuid> --user <user-uuid>
697
660
  dailybot project member add <project-uuid> --team <team-uuid> --json
698
661
  """
699
- _require_person_for_admin("project member add", json_mode=json_mode)
700
662
  if (user_uuid is None) == (team_uuid is None):
701
663
  raise click.UsageError("Pass exactly one of --user or --team.")
702
664
  client = require_auth()
@@ -729,7 +691,6 @@ def project_member_remove(
729
691
  dailybot project member remove <project-uuid> <user-uuid> --dry-run
730
692
  dailybot project member remove <project-uuid> <user-uuid> --yes
731
693
  """
732
- _require_person_for_admin("project member remove", json_mode=json_mode)
733
694
  if not confirm_without_preview(
734
695
  f"remove user {user_uuid} from project {project_uuid}; they lose sight of it if it "
735
696
  "is private.",
@@ -767,11 +728,6 @@ def project_views(project_uuid: str, etag_only: bool, json_mode: bool) -> None:
767
728
  dailybot project views <project-uuid>
768
729
  ETAG=$(dailybot project views <project-uuid> --etag)
769
730
  """
770
- _require_person(
771
- "project views",
772
- "lists saved views, which belong to a person, and an organization API key is not one.",
773
- json_mode=json_mode,
774
- )
775
731
  client = require_auth()
776
732
  try:
777
733
  with console.status("Reading the views..."):
@@ -791,7 +747,7 @@ def project_views(project_uuid: str, etag_only: bool, json_mode: bool) -> None:
791
747
 
792
748
  @project.group("view")
793
749
  def project_view() -> None:
794
- """Save your views of a project. Needs `dailybot login`.
750
+ """Save your views of a project. Needs a person: `dailybot login` or a personal API key.
795
751
 
796
752
  \b
797
753
  Examples:
@@ -834,11 +790,6 @@ def project_view_save(
834
790
  raise click.BadParameter(f"not valid JSON: {exc}", param_hint="--file") from exc
835
791
  if not isinstance(views, list):
836
792
  raise click.BadParameter("must be a JSON array of views.", param_hint="--file")
837
- _require_person(
838
- "project view save",
839
- "saves views that belong to a person, and an organization API key is not one.",
840
- json_mode=json_mode,
841
- )
842
793
  client = require_auth()
843
794
  try:
844
795
  etag: str | None = if_match
@@ -995,7 +946,7 @@ def project_milestone_delete(
995
946
  def project_attach(
996
947
  project_uuid: str, file_path: Path, caption: str | None, json_mode: bool
997
948
  ) -> None:
998
- """Attach a file to a project. Needs a signed-in person (any non-guest member).
949
+ """Attach a file to a project. Needs a person (any non-guest member): `dailybot login` or a personal API key.
999
950
 
1000
951
  \b
1001
952
  One request, up to 5 MiB. Your Dailybot credentials go only to the API.
@@ -1005,7 +956,6 @@ def project_attach(
1005
956
  dailybot project attach <project-uuid> ./plan.pdf
1006
957
  dailybot project attach <project-uuid> ./roadmap.png --caption "Q4 roadmap" --json
1007
958
  """
1008
- _require_person_for_admin("project attach", json_mode=json_mode)
1009
959
  run_attach(
1010
960
  lambda client, **file: client.upload_project_attachment(project_uuid, **file),
1011
961
  file_path,
@@ -1087,14 +1037,13 @@ def project_attachment_get(
1087
1037
  def project_attachment_delete(
1088
1038
  project_uuid: str, attachment_uuid: str, dry_run: bool, assume_yes: bool, json_mode: bool
1089
1039
  ) -> None:
1090
- """Remove an attachment from a project. This cannot be undone. Needs a signed-in person.
1040
+ """Remove an attachment from a project. This cannot be undone. Needs a person: `dailybot login` or a personal API key.
1091
1041
 
1092
1042
  \b
1093
1043
  Examples:
1094
1044
  dailybot project attachment delete <project-uuid> <attachment-uuid> --dry-run
1095
1045
  dailybot project attachment delete <project-uuid> <attachment-uuid> --yes
1096
1046
  """
1097
- _require_person_for_admin("project attachment delete", json_mode=json_mode)
1098
1047
  run_delete(
1099
1048
  lambda client: client.delete_project_attachment(project_uuid, attachment_uuid),
1100
1049
  f"delete attachment {attachment_uuid} from project {project_uuid}.",
@@ -58,10 +58,11 @@ ERROR_CODE_MESSAGES: dict[str, str] = {
58
58
  #
59
59
  # Agent attribution (--agent-name / DAILYBOT_AGENT_NAME)
60
60
  "invalid_agent_attribution": (
61
- "The agent name was refused. It must be 128 characters or fewer, and it "
62
- "only works with a credential that belongs to a person (a login session "
63
- "or a personal API key), not an agent key. Shorten --agent-name, or drop "
64
- "it when using an agent key."
61
+ "The agent name was refused. Use a plain name of 128 characters or fewer: "
62
+ "letters, numbers, spaces and . - _ ( ) ' # + / & , : only. The name must "
63
+ "not belong to a deactivated agent, and it only works with a credential that "
64
+ "belongs to a person (a login session or a personal API key), not an agent "
65
+ "key. Fix --agent-name / DAILYBOT_AGENT_NAME, or drop it."
65
66
  ),
66
67
  # Auth taxonomy
67
68
  "credential_absent": (
@@ -592,54 +593,18 @@ _PERSON_SHAPED_GUIDANCE: str = (
592
593
  "that belongs to you, and retry."
593
594
  )
594
595
 
595
- # Keys still cannot store `tasks:admin`. Every non-guest member holds it on a
596
- # person session, so structure creates/updates go through `dailybot login` — not
597
- # an organization-admin role check in the CLI.
596
+ # Every non-guest member holds `tasks:admin`, on a login session and on their own
597
+ # personal API key alike. Only a credential with nobody behind it (an agent or
598
+ # organization key) cannot — so the fix is a person, never an admin grant.
598
599
  _ADMIN_SCOPE_GUIDANCE: str = (
599
- "This action needs a signed-in person: an organization API key can never hold the "
600
- "`tasks:admin` scope (it cannot even be stored on one). Run `dailybot login` and retry "
601
- "as a non-guest member. Membership and participant changes stay person-only too."
600
+ "This key cannot change Tasks structure. Either it is an agent or organization key, "
601
+ "which can never hold `tasks:admin` (use `dailybot login` or a personal API key that "
602
+ "belongs to you), or it is a personal key whose own scopes are narrower (for example "
603
+ "read-only); widen or remove that key's Tasks scopes, or use `dailybot login`. Guests are "
604
+ "refused either way; that is a role limit."
602
605
  )
603
606
 
604
607
 
605
- # Machine-readable codes for the two client-side preflight refusals. They match
606
- # what the server would answer for the same condition, so a caller branching on
607
- # `code` cannot tell whether the request was spent — and does not need to.
608
- PREFLIGHT_ACTOR_REQUIRED_CODE: str = "actor_required"
609
- PREFLIGHT_ADMIN_SCOPE_CODE: str = "insufficient_scope"
610
-
611
-
612
- def refuse_without_person(message: str, *, json_mode: bool, admin: bool = False) -> NoReturn:
613
- """End a command that a bare API key can never satisfy.
614
-
615
- One helper, four call sites. Each module used to carry its own copy, and every
616
- copy ignored ``--json`` — so `dailybot board create --json` with a key-only
617
- session exited 3 with **empty stdout** while `exit_for_tasks_error` promised a
618
- parseable document on every refusal path. A caller that parses stdout on any
619
- non-zero exit read that as a crash rather than a credential problem.
620
-
621
- ``admin`` selects the code the server would have used for the same condition.
622
- """
623
- if json_mode:
624
- emit_json(
625
- {
626
- "status": "error",
627
- "code": (PREFLIGHT_ADMIN_SCOPE_CODE if admin else PREFLIGHT_ACTOR_REQUIRED_CODE),
628
- "detail": message,
629
- "message": message,
630
- }
631
- )
632
- else:
633
- print_error(message)
634
- # The exit must match what the SERVER would return for the same condition, or
635
- # the pre-flight becomes observable: an agent branching "3 → re-login,
636
- # 4 → permission" saw 3 from the pre-flight and 4 from the server for one
637
- # condition. A `tasks:admin` refusal is a 403 on the wire, so it is 4 here too;
638
- # a person-shaped door answers `actor_required`, which is a credential problem
639
- # and stays 3.
640
- raise SystemExit(EXIT_PERMISSION_DENIED if admin else EXIT_NOT_AUTHENTICATED)
641
-
642
-
643
608
  def is_person_shaped_refusal(exc: APIError, *, door: str | None = None) -> bool:
644
609
  """Is this refusal the "you are not a person" condition, in either shape?
645
610
 
@@ -658,9 +623,10 @@ def is_person_shaped_refusal(exc: APIError, *, door: str | None = None) -> bool:
658
623
  # New organization API keys start with no Tasks scopes; they are granted to the key
659
624
  # itself. Said once here so every Tasks refusal on a key reads the same.
660
625
  _KEY_WITHOUT_TASKS_SCOPES_GUIDANCE: str = (
661
- "This API key has no Tasks scopes for this action{scope} — new keys start with none. "
662
- "Ask an organization admin to grant Tasks scopes to the key, or write to "
663
- "support@dailybot.com. Signing in with `dailybot login` also works for your own account."
626
+ "This API key has no Tasks scopes for this action{scope}. A personal API key acts as "
627
+ "you and needs no grant; an agent or organization key starts with none, and an "
628
+ "organization admin grants them to the key (or write to support@dailybot.com). Signing "
629
+ "in with `dailybot login` also works for your own account."
664
630
  )
665
631
 
666
632
 
@@ -49,7 +49,6 @@ from dailybot_cli.commands.public_api_helpers import (
49
49
  emit_json,
50
50
  exit_for_tasks_error,
51
51
  load_json_input,
52
- refuse_without_person,
53
52
  require_auth,
54
53
  rows_of,
55
54
  )
@@ -60,7 +59,6 @@ from dailybot_cli.commands.query_options import (
60
59
  query_options,
61
60
  resolve_fetch_all,
62
61
  )
63
- from dailybot_cli.config import get_person_token
64
62
  from dailybot_cli.display import (
65
63
  console,
66
64
  error_console,
@@ -724,22 +722,6 @@ def task_assign(task_ref: str, owner: str, idempotency_key: str | None, json_mod
724
722
  _set_owner(task_ref, owner, idempotency_key, json_mode)
725
723
 
726
724
 
727
- def _require_person_for(action: str, *, json_mode: bool) -> None:
728
- """Refuse a key on a person-only door.
729
-
730
- Published policy: two writes no organization API key may ever make — changing
731
- who can see, and changing who is notified. Participants are the second.
732
- """
733
- # Gate on the absence of a person token, not on the presence of a key: both
734
- # can be configured at once, and Bearer is sent first when it exists.
735
- if get_person_token() is None:
736
- refuse_without_person(
737
- f"`{action}` changes who is notified, and no organization API key may do that — "
738
- "there is no person behind it to be accountable. Run `dailybot login` and retry.",
739
- json_mode=json_mode,
740
- )
741
-
742
-
743
725
  def _read_body(value: str) -> str:
744
726
  """Read a body argument, or stdin when it is `-`."""
745
727
  if value == "-":
@@ -928,7 +910,6 @@ def participants_add(
928
910
  Examples:
929
911
  dailybot task participants add ENG-142 --user <user-uuid>
930
912
  """
931
- _require_person_for("task participants add", json_mode=json_mode)
932
913
  client = require_auth()
933
914
  try:
934
915
  with console.status("Adding the participant..."):
@@ -1003,7 +984,6 @@ def participants_remove(
1003
984
  dailybot task participants remove ENG-142 <user-uuid> --dry-run
1004
985
  dailybot task participants remove ENG-142 <user-uuid> --yes
1005
986
  """
1006
- _require_person_for("task participants remove", json_mode=json_mode)
1007
987
  if not confirm_without_preview(
1008
988
  f"take user {user_uuid} off task {task_uuid}; they stop being notified about it.",
1009
989
  assume_yes=assume_yes,
@@ -1025,8 +1005,6 @@ def participants_remove(
1025
1005
 
1026
1006
  def _set_own_mute(task_uuid: str, muted: bool, json_mode: bool) -> None:
1027
1007
  """Record the caller's mute on a task: POST participants/ for yourself with `is_muted`."""
1028
- action: str = "task mute" if muted else "task unmute"
1029
- _require_person_for(action, json_mode=json_mode)
1030
1008
  client = require_auth()
1031
1009
  try:
1032
1010
  with console.status("Reading who you are..."):
@@ -1050,7 +1028,7 @@ def _set_own_mute(task_uuid: str, muted: bool, json_mode: bool) -> None:
1050
1028
  @click.argument("task_uuid", metavar="TASK")
1051
1029
  @click.option("--json", "json_mode", is_flag=True, help="Emit machine-readable JSON to stdout.")
1052
1030
  def task_mute(task_uuid: str, json_mode: bool) -> None:
1053
- """Stop notifications from a task while staying on it. Needs `dailybot login`.
1031
+ """Stop notifications from a task while staying on it. Needs a person: `dailybot login` or a personal API key.
1054
1032
 
1055
1033
  \b
1056
1034
  Examples:
@@ -1063,7 +1041,7 @@ def task_mute(task_uuid: str, json_mode: bool) -> None:
1063
1041
  @click.argument("task_uuid", metavar="TASK")
1064
1042
  @click.option("--json", "json_mode", is_flag=True, help="Emit machine-readable JSON to stdout.")
1065
1043
  def task_unmute(task_uuid: str, json_mode: bool) -> None:
1066
- """Resume notifications from a task you muted. Needs `dailybot login`.
1044
+ """Resume notifications from a task you muted. Needs a person: `dailybot login` or a personal API key.
1067
1045
 
1068
1046
  \b
1069
1047
  Examples:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dailybot-cli
3
- Version: 3.19.0
3
+ Version: 3.20.0
4
4
  Summary: Dailybot CLI - The command-line bridge between humans and agents. Progress reports, observability, and workflow automation for modern teams across Slack, Google Chat, Discord, MS Teams, and more.
5
5
  Author-email: Dailybot <support@dailybot.com>
6
6
  License: MIT
@@ -905,11 +905,13 @@ Replies to agent emails land as messages retrievable via `dailybot agent message
905
905
  Projects, boards and tasks. Two groups: **`dailybot tasks`** answers questions about the
906
906
  workspace, **`dailybot task`** reads or changes one task.
907
907
 
908
- **Structure changes need a signed-in person (any non-guest member).** Creating, updating,
909
- archiving or restoring boards, columns, projects and goals, board and project membership,
910
- and linking goals to projects all work for every non-guest member after `dailybot login`.
911
- An organization API key can never hold `tasks:admin` and cannot change membership or
912
- participants — with only `DAILYBOT_API_KEY` those commands stop before sending anything.
908
+ **Your personal API key is you.** A login session (`dailybot login`) and a personal API key
909
+ (one bound to you) can do everything you can do in the web app: create, update, archive and
910
+ restore boards, columns, projects, goals and milestones, manage board and project
911
+ membership, link goals to projects, and work every task. That is every non-guest member, no
912
+ organization-admin prerequisite. Only an agent or organization key, with nobody behind it,
913
+ cannot act as a person: the server refuses it (`actor_required` exit 3, `insufficient_scope`
914
+ exit 4). Guests stay limited by their role.
913
915
  **Privacy is membership**, not org role: a `members` project or board is 404 (not visible)
914
916
  to anyone without a grant; invite a person or a team to close it.
915
917
  A board inside a `members` project follows the project's membership: whoever the project
@@ -954,9 +956,9 @@ humans who own them.
954
956
  | `dailybot task comments <uuid>` | List a task's comments |
955
957
  | `dailybot task link <a> <b> --type <rel>` | Relate two tasks (`blocks`, `relates_to`, `duplicates`) |
956
958
  | `dailybot task labels <uuid> --mode add\|remove\|replace` | Change a task's labels |
957
- | `dailybot task participants add\|list\|remove <task>` | Who is on a task (`--role participant\|watcher`); `list` works with a personal API key; add/remove **need `dailybot login`** |
959
+ | `dailybot task participants add\|list\|remove <task>` | Who is on a task (`--role participant\|watcher`); all need a person: `dailybot login` or a personal API key |
958
960
  | `dailybot task watch\|unwatch <task>` | Follow a task privately — **needs a person: `dailybot login` or a personal API key** |
959
- | `dailybot task mute\|unmute <task>` | Silence a task and stay on it (leaving is `participants remove`) — **needs `dailybot login`** |
961
+ | `dailybot task mute\|unmute <task>` | Silence a task and stay on it (leaving is `participants remove`) — **needs a person: `dailybot login` or a personal API key** |
960
962
  | `dailybot task relations <task>` · `task unlink <task> <relation>` | List links (direction included) / remove one; `unlink` confirms and has `--dry-run` |
961
963
  | `dailybot task comment-edit\|comment-delete <task> <comment>` | Edit (`-` reads stdin) or delete a comment; delete blanks the text and keeps the entry |
962
964
  | `dailybot task children <task>` · `task events <task>` · `task activity <task>` | Sub-tasks, raw event history, and the readable activity feed (`--updated-since`, `--type`) |
@@ -978,12 +980,12 @@ humans who own them.
978
980
  | `dailybot board labels <uuid>` | Labels available on the board — **needs a person: `dailybot login` or a personal API key** |
979
981
  | `dailybot board views <uuid>` | Your saved views on the board, plus the ETag a save needs (`--etag` prints only that) |
980
982
  | `dailybot board state create\|update\|archive\|restore\|reorder` | Manage columns. `archive` previews first and takes `--migrate-to <state>` to move the column's cards |
981
- | `dailybot board member add <board> <user>` (or `--team <team>`) · `board member remove <board> <user>` | Who can see the board — **needs `dailybot login`**. Membership is the privacy control (not org role). A team grant follows the team live. Last grant on a private board stays (`last_grant_cannot_be_removed`) |
983
+ | `dailybot board member add <board> <user>` (or `--team <team>`) · `board member remove <board> <user>` | Who can see the board — **needs a person: `dailybot login` or a personal API key**. Membership is the privacy control (not org role). A team grant follows the team live. Last grant on a private board stays (`last_grant_cannot_be_removed`) |
982
984
  | `dailybot board label create <board> -n <name>` | Create an organization label from the board — **needs a person: `dailybot login` or a personal API key** |
983
985
  | `dailybot board view save <board> -f views.json --if-match <etag>` | Replace your saved views (the whole list) — **needs a person: `dailybot login` or a personal API key** |
984
986
  | `dailybot board snapshot <uuid>` | The whole board in one request; carries the `delta_cursor` that `tasks changes` consumes |
985
987
  | `dailybot board update <uuid>` | Name, key (the old key stays reserved), visibility, estimate scale, auto-archive, project |
986
- | `dailybot board create --name <n> --project <uuid> --key <KEY>` | Create a board in a project; the key prefixes its tasks (`DSN-1`) — **needs `dailybot login`** (any non-guest member) |
988
+ | `dailybot board create --name <n> --project <uuid> --key <KEY>` | Create a board in a project; the key prefixes its tasks (`DSN-1`) — **needs a person: `dailybot login` or a personal API key** (any non-guest member) |
987
989
  | `dailybot board archive <uuid>` | Archive a board. **Cascade-archives its live tasks**, and restoring does not bring them back |
988
990
  | `dailybot board restore <uuid>` | Restore a board (cascaded tasks stay archived) |
989
991
  | `dailybot project list` | List projects (`--include progress`) |
@@ -993,28 +995,28 @@ humans who own them.
993
995
  | `dailybot project milestones [<uuid>]` | List milestones |
994
996
  | `dailybot project milestone-complete <p> <m>` | Complete a milestone. **Its open tasks stay open** |
995
997
  | `dailybot project milestone-reopen <p> <m>` | Reopen a milestone |
996
- | `dailybot project create --name <n>` | Create a project — **needs `dailybot login`** |
998
+ | `dailybot project create --name <n>` | Create a project — **needs a person: `dailybot login` or a personal API key** |
997
999
  | `dailybot project update <uuid>` | Name, description, lead, health, dates, visibility |
998
1000
  | `dailybot project restore <uuid>` | Restore an archived project (its boards and tasks stay archived) |
999
- | `dailybot project members <uuid>` · `project member add\|remove` | Who can see the project — people or whole teams (`--team`) — `members` works with a personal API key; `member add\|remove` **needs `dailybot login`** |
1000
- | `dailybot project views <uuid>` · `project view save` | Your saved views; save replaces the list and requires the ETag — **needs `dailybot login`** |
1001
+ | `dailybot project members <uuid>` · `project member add\|remove` | Who can see the project — people or whole teams (`--team`) — `members` works with a personal API key; `member add\|remove` **needs a person: `dailybot login` or a personal API key** |
1002
+ | `dailybot project views <uuid>` · `project view save` | Your saved views; save replaces the list and requires the ETag — **needs a person: `dailybot login` or a personal API key** |
1001
1003
  | `dailybot project milestone-create\|milestone-update\|milestone-delete` | Dated milestones; delete retires it (tasks keep pointing at it) |
1002
1004
  | `dailybot project archive <uuid>` | Archive a project |
1003
- | `dailybot project attach <uuid> <file>` · `project attachments` · `project attachment get\|delete` | Files on a project (≤5 MiB, one request). Attaching and deleting need `dailybot login` |
1005
+ | `dailybot project attach <uuid> <file>` · `project attachments` · `project attachment get\|delete` | Files on a project (≤5 MiB, one request). Attaching and deleting need a person (`dailybot login` or a personal API key) |
1004
1006
  | `dailybot goal list` | List goals (`--include` is repeatable: `--include progress --include projects`) |
1005
1007
  | `dailybot goal get <uuid>` | Show one goal, with its progress and linked projects (always included) |
1006
- | `dailybot goal create --name <n> --period-start <d> --period-end <d>` | Create a goal (a dated commitment) — **needs `dailybot login`** |
1008
+ | `dailybot goal create --name <n> --period-start <d> --period-end <d>` | Create a goal (a dated commitment) — **needs a person: `dailybot login` or a personal API key** |
1007
1009
  | `dailybot goal update <uuid>` | Name, period, owner, team, and the declared `--status` (not_started, on_track, at_risk, off_track, achieved, missed) |
1008
1010
  | `dailybot goal restore <uuid>` | Restore an archived goal |
1009
1011
  | `dailybot goal link\|unlink <goal> <project>` | Make a project count toward a goal (or stop it) |
1010
1012
  | `dailybot goal archive <uuid>` | Archive a goal (its projects are not archived) |
1011
- | `dailybot goal attach <uuid> <file>` · `goal attachments` · `goal attachment get\|delete` | Files on a goal (≤5 MiB, one request). Attaching and deleting need `dailybot login` |
1013
+ | `dailybot goal attach <uuid> <file>` · `goal attachments` · `goal attachment get\|delete` | Files on a goal (≤5 MiB, one request). Attaching and deleting need a person (`dailybot login` or a personal API key) |
1012
1014
 
1013
1015
  **Three things worth knowing before you script against this:**
1014
1016
 
1015
- - **Some doors need a person.** An organization API key has no answer for "my tasks" or
1016
- "my inbox", can never hold `tasks:admin`, and cannot change membership or participants —
1017
- so container creates and invites need `dailybot login` as a non-guest member. A 404 means
1017
+ - **Some doors need a person.** A login session or a personal API key is a person; an agent
1018
+ or organization key has no answer for "my tasks" or "my inbox", can never hold
1019
+ `tasks:admin`, and cannot change membership or participants. A 404 means
1018
1020
  not visible (wrong id, private without a grant, or another org) — never "not allowed".
1019
1021
  - **Roll-ups are opt-in.** A field you did not request with `--include` is **absent** from
1020
1022
  the payload. Absent, `null` and `0` are three different answers, and the CLI renders them
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dailybot-cli"
7
- version = "3.19.0"
7
+ version = "3.20.0"
8
8
  description = "Dailybot CLI - The command-line bridge between humans and agents. Progress reports, observability, and workflow automation for modern teams across Slack, Google Chat, Discord, MS Teams, and more."
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
File without changes
File without changes