dailybot-cli 3.19.1__tar.gz → 3.21.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.1 → dailybot_cli-3.21.0}/PKG-INFO +24 -19
  2. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/README.md +23 -18
  3. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/api_client.py +148 -1
  4. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/_attachments.py +32 -3
  5. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/board.py +4 -37
  6. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/goal.py +3 -12
  7. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/project.py +486 -71
  8. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/public_api_helpers.py +17 -47
  9. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/task.py +2 -24
  10. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/display.py +37 -0
  11. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli.egg-info/PKG-INFO +24 -19
  12. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/pyproject.toml +1 -1
  13. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/LICENSE +0 -0
  14. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/__init__.py +0 -0
  15. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/__init__.py +0 -0
  16. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/_beta.py +0 -0
  17. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/_briefing.py +0 -0
  18. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/_destructive.py +0 -0
  19. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/_favorites.py +0 -0
  20. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/_rollups.py +0 -0
  21. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/_writes.py +0 -0
  22. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/agent.py +0 -0
  23. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/ask.py +0 -0
  24. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/auth.py +0 -0
  25. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/authoring_helpers.py +0 -0
  26. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/channels.py +0 -0
  27. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/chat.py +0 -0
  28. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/checkin.py +0 -0
  29. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/config.py +0 -0
  30. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/conversation.py +0 -0
  31. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/env.py +0 -0
  32. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/featured.py +0 -0
  33. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/form.py +0 -0
  34. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/hook.py +0 -0
  35. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/identity.py +0 -0
  36. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/interactive.py +0 -0
  37. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/interactive_chat.py +0 -0
  38. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/kudos.py +0 -0
  39. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/label.py +0 -0
  40. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/query_options.py +0 -0
  41. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/status.py +0 -0
  42. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/tasks.py +0 -0
  43. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/team.py +0 -0
  44. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/uninstall.py +0 -0
  45. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/update.py +0 -0
  46. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/upgrade.py +0 -0
  47. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/user.py +0 -0
  48. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/user_scoped_actions.py +0 -0
  49. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/version.py +0 -0
  50. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/commands/workflow.py +0 -0
  51. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/config.py +0 -0
  52. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/install_method.py +0 -0
  53. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/ledger.py +0 -0
  54. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/main.py +0 -0
  55. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/tui/__init__.py +0 -0
  56. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/tui/app.py +0 -0
  57. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/tui/commands.py +0 -0
  58. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/tui/conversation.py +0 -0
  59. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli/tui/intents.py +0 -0
  60. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli.egg-info/SOURCES.txt +0 -0
  61. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli.egg-info/dependency_links.txt +0 -0
  62. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli.egg-info/entry_points.txt +0 -0
  63. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli.egg-info/requires.txt +0 -0
  64. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/dailybot_cli.egg-info/top_level.txt +0 -0
  65. {dailybot_cli-3.19.1 → dailybot_cli-3.21.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dailybot-cli
3
- Version: 3.19.1
3
+ Version: 3.21.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,43 +980,46 @@ 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`) |
990
992
  | `dailybot project get <uuid>` | Show one project |
991
993
  | `dailybot project updates [<project>]` | Batched update digest, or one project's feed |
992
994
  | `dailybot project update-post <uuid> <body>` | **Post a project update** — how the team sees what was done (`--health`; sends an idempotency key) |
995
+ | `dailybot project update-get\|update-edit\|update-delete <project> <update>` | Read one update (author, `via <agent>`, health, files); edit its text or health (author only); delete it (author or org admin; `--dry-run`) |
996
+ | `dailybot project update-attach\|update-attachments <project> <update>` · `update-attachment get\|rename\|delete` | Files on an update (≤5 MiB). Attach and rename: author only; delete: author or an organization admin. For an inline image, attach it, then `update-edit` the body with `attachment:<uuid>` |
993
997
  | `dailybot project milestones [<uuid>]` | List milestones |
994
998
  | `dailybot project milestone-complete <p> <m>` | Complete a milestone. **Its open tasks stay open** |
995
999
  | `dailybot project milestone-reopen <p> <m>` | Reopen a milestone |
996
- | `dailybot project create --name <n>` | Create a project — **needs `dailybot login`** |
1000
+ | `dailybot project create --name <n>` | Create a project — **needs a person: `dailybot login` or a personal API key** |
997
1001
  | `dailybot project update <uuid>` | Name, description, lead, health, dates, visibility |
998
1002
  | `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`** |
1003
+ | `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** |
1004
+ | `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
1005
  | `dailybot project milestone-create\|milestone-update\|milestone-delete` | Dated milestones; delete retires it (tasks keep pointing at it) |
1006
+ | `dailybot project milestone-attach\|milestone-attachments <project> <milestone>` · `milestone-attachment get\|rename\|delete` | Files on a milestone (≤5 MiB); reference one in the description with `attachment:<uuid>`. `project milestone-restore` brings a retired milestone back |
1002
1007
  | `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` |
1008
+ | `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
1009
  | `dailybot goal list` | List goals (`--include` is repeatable: `--include progress --include projects`) |
1005
1010
  | `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`** |
1011
+ | `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
1012
  | `dailybot goal update <uuid>` | Name, period, owner, team, and the declared `--status` (not_started, on_track, at_risk, off_track, achieved, missed) |
1008
1013
  | `dailybot goal restore <uuid>` | Restore an archived goal |
1009
1014
  | `dailybot goal link\|unlink <goal> <project>` | Make a project count toward a goal (or stop it) |
1010
1015
  | `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` |
1016
+ | `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
1017
 
1013
1018
  **Three things worth knowing before you script against this:**
1014
1019
 
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
1020
+ - **Some doors need a person.** A login session or a personal API key is a person; an agent
1021
+ or organization key has no answer for "my tasks" or "my inbox", can never hold
1022
+ `tasks:admin`, and cannot change membership or participants. A 404 means
1018
1023
  not visible (wrong id, private without a grant, or another org) — never "not allowed".
1019
1024
  - **Roll-ups are opt-in.** A field you did not request with `--include` is **absent** from
1020
1025
  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,43 +941,46 @@ 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`) |
951
953
  | `dailybot project get <uuid>` | Show one project |
952
954
  | `dailybot project updates [<project>]` | Batched update digest, or one project's feed |
953
955
  | `dailybot project update-post <uuid> <body>` | **Post a project update** — how the team sees what was done (`--health`; sends an idempotency key) |
956
+ | `dailybot project update-get\|update-edit\|update-delete <project> <update>` | Read one update (author, `via <agent>`, health, files); edit its text or health (author only); delete it (author or org admin; `--dry-run`) |
957
+ | `dailybot project update-attach\|update-attachments <project> <update>` · `update-attachment get\|rename\|delete` | Files on an update (≤5 MiB). Attach and rename: author only; delete: author or an organization admin. For an inline image, attach it, then `update-edit` the body with `attachment:<uuid>` |
954
958
  | `dailybot project milestones [<uuid>]` | List milestones |
955
959
  | `dailybot project milestone-complete <p> <m>` | Complete a milestone. **Its open tasks stay open** |
956
960
  | `dailybot project milestone-reopen <p> <m>` | Reopen a milestone |
957
- | `dailybot project create --name <n>` | Create a project — **needs `dailybot login`** |
961
+ | `dailybot project create --name <n>` | Create a project — **needs a person: `dailybot login` or a personal API key** |
958
962
  | `dailybot project update <uuid>` | Name, description, lead, health, dates, visibility |
959
963
  | `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`** |
964
+ | `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** |
965
+ | `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
966
  | `dailybot project milestone-create\|milestone-update\|milestone-delete` | Dated milestones; delete retires it (tasks keep pointing at it) |
967
+ | `dailybot project milestone-attach\|milestone-attachments <project> <milestone>` · `milestone-attachment get\|rename\|delete` | Files on a milestone (≤5 MiB); reference one in the description with `attachment:<uuid>`. `project milestone-restore` brings a retired milestone back |
963
968
  | `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` |
969
+ | `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
970
  | `dailybot goal list` | List goals (`--include` is repeatable: `--include progress --include projects`) |
966
971
  | `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`** |
972
+ | `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
973
  | `dailybot goal update <uuid>` | Name, period, owner, team, and the declared `--status` (not_started, on_track, at_risk, off_track, achieved, missed) |
969
974
  | `dailybot goal restore <uuid>` | Restore an archived goal |
970
975
  | `dailybot goal link\|unlink <goal> <project>` | Make a project count toward a goal (or stop it) |
971
976
  | `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` |
977
+ | `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
978
 
974
979
  **Three things worth knowing before you script against this:**
975
980
 
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
981
+ - **Some doors need a person.** A login session or a personal API key is a person; an agent
982
+ or organization key has no answer for "my tasks" or "my inbox", can never hold
983
+ `tasks:admin`, and cannot change membership or participants. A 404 means
979
984
  not visible (wrong id, private without a grant, or another org) — never "not allowed".
980
985
  - **Roll-ups are opt-in.** A field you did not request with `--include` is **absent** from
981
986
  the payload. Absent, `null` and `0` are three different answers, and the CLI renders them
@@ -100,6 +100,8 @@ SAME_ORIGIN_UPLOAD_METHODS: frozenset[str] = frozenset({"PUT", "POST"})
100
100
  # BLAST_RADIUS.md records this as THE volume guard for unattended destructive
101
101
  # loops — the CLI adds no second ceiling of its own.
102
102
  TASKS_BULK_MAX_ITEMS: int = 100
103
+ # Body field an attachment rename (PATCH …/attachments/<uuid>/) carries.
104
+ ATTACHMENT_RENAME_FIELD: str = "filename"
103
105
  # Collections the task detail door can embed in one read (first page each).
104
106
  TASK_BRIEFING_INCLUDES: tuple[str, ...] = (
105
107
  "relations",
@@ -2885,7 +2887,7 @@ class DailyBotClient:
2885
2887
  return self._tasks_write(
2886
2888
  "POST",
2887
2889
  f"tasks/{_path_segment(task_uuid)}/labels/batch/",
2888
- json={"mode": mode, "labels": labels},
2890
+ json={"mode": mode, "label_uuids": labels},
2889
2891
  idempotent=True,
2890
2892
  idempotency_key=idempotency_key,
2891
2893
  )
@@ -3321,6 +3323,143 @@ class DailyBotClient:
3321
3323
  """GET /v1/tasks/projects/<p>/attachments/<a>/content/."""
3322
3324
  return self._download_attachment_of(self._project_parent(project_uuid), attachment_uuid)
3323
3325
 
3326
+ # Milestone and project-update attachments: the same row shape as project ones.
3327
+ @staticmethod
3328
+ def _milestone_parent(project_uuid: str, milestone_uuid: str) -> str:
3329
+ return f"projects/{_path_segment(project_uuid)}/milestones/{_path_segment(milestone_uuid)}"
3330
+
3331
+ @staticmethod
3332
+ def _update_parent(project_uuid: str, update_uuid: str) -> str:
3333
+ return f"projects/{_path_segment(project_uuid)}/updates/{_path_segment(update_uuid)}"
3334
+
3335
+ def _rename_attachment_of(self, parent: str, attachment_uuid: str, *, filename: str) -> Any:
3336
+ """PATCH <parent>/attachments/<uuid>/ — rename the file."""
3337
+ return self._tasks_write(
3338
+ "PATCH",
3339
+ f"{parent}/attachments/{_path_segment(attachment_uuid)}/",
3340
+ json={ATTACHMENT_RENAME_FIELD: filename},
3341
+ )
3342
+
3343
+ def upload_milestone_attachment(
3344
+ self,
3345
+ project_uuid: str,
3346
+ milestone_uuid: str,
3347
+ *,
3348
+ filename: str,
3349
+ content_type: str,
3350
+ data: bytes,
3351
+ caption: str | None = None,
3352
+ ) -> dict[str, Any]:
3353
+ """POST …/milestones/<m>/attachments/ as multipart (≤5 MiB)."""
3354
+ return self._upload_attachment_to(
3355
+ self._milestone_parent(project_uuid, milestone_uuid),
3356
+ filename=filename,
3357
+ content_type=content_type,
3358
+ data=data,
3359
+ caption=caption,
3360
+ )
3361
+
3362
+ def list_milestone_attachments(self, project_uuid: str, milestone_uuid: str) -> Any:
3363
+ """GET …/milestones/<m>/attachments/."""
3364
+ return self._list_attachments_of(self._milestone_parent(project_uuid, milestone_uuid))
3365
+
3366
+ def download_milestone_attachment(
3367
+ self, project_uuid: str, milestone_uuid: str, attachment_uuid: str
3368
+ ) -> bytes:
3369
+ """GET …/milestones/<m>/attachments/<a>/content/."""
3370
+ return self._download_attachment_of(
3371
+ self._milestone_parent(project_uuid, milestone_uuid), attachment_uuid
3372
+ )
3373
+
3374
+ def rename_milestone_attachment(
3375
+ self, project_uuid: str, milestone_uuid: str, attachment_uuid: str, *, filename: str
3376
+ ) -> Any:
3377
+ """PATCH …/milestones/<m>/attachments/<a>/."""
3378
+ return self._rename_attachment_of(
3379
+ self._milestone_parent(project_uuid, milestone_uuid), attachment_uuid, filename=filename
3380
+ )
3381
+
3382
+ def delete_milestone_attachment(
3383
+ self, project_uuid: str, milestone_uuid: str, attachment_uuid: str
3384
+ ) -> Any:
3385
+ """DELETE …/milestones/<m>/attachments/<a>/."""
3386
+ return self._delete_attachment_of(
3387
+ self._milestone_parent(project_uuid, milestone_uuid), attachment_uuid
3388
+ )
3389
+
3390
+ def get_project_update(self, project_uuid: str, update_uuid: str) -> dict[str, Any]:
3391
+ """GET /v1/tasks/projects/<p>/updates/<u>/."""
3392
+ return self._tasks_read(f"{self._update_parent(project_uuid, update_uuid)}/")
3393
+
3394
+ def edit_project_update(
3395
+ self,
3396
+ project_uuid: str,
3397
+ update_uuid: str,
3398
+ *,
3399
+ body: str | None = None,
3400
+ health: str | None = None,
3401
+ ) -> Any:
3402
+ """PATCH /v1/tasks/projects/<p>/updates/<u>/ — author only (`update_not_author`)."""
3403
+ payload: dict[str, Any] = {}
3404
+ if body is not None:
3405
+ payload["body"] = body
3406
+ if health is not None:
3407
+ payload["health"] = health
3408
+ return self._tasks_write(
3409
+ "PATCH", f"{self._update_parent(project_uuid, update_uuid)}/", json=payload
3410
+ )
3411
+
3412
+ def delete_project_update(self, project_uuid: str, update_uuid: str) -> Any:
3413
+ """DELETE /v1/tasks/projects/<p>/updates/<u>/ — the author or an org admin."""
3414
+ return self._tasks_write("DELETE", f"{self._update_parent(project_uuid, update_uuid)}/")
3415
+
3416
+ def upload_update_attachment(
3417
+ self,
3418
+ project_uuid: str,
3419
+ update_uuid: str,
3420
+ *,
3421
+ filename: str,
3422
+ content_type: str,
3423
+ data: bytes,
3424
+ caption: str | None = None,
3425
+ ) -> dict[str, Any]:
3426
+ """POST …/updates/<u>/attachments/ as multipart (≤5 MiB) — author only."""
3427
+ return self._upload_attachment_to(
3428
+ self._update_parent(project_uuid, update_uuid),
3429
+ filename=filename,
3430
+ content_type=content_type,
3431
+ data=data,
3432
+ caption=caption,
3433
+ )
3434
+
3435
+ def list_update_attachments(self, project_uuid: str, update_uuid: str) -> Any:
3436
+ """GET …/updates/<u>/attachments/."""
3437
+ return self._list_attachments_of(self._update_parent(project_uuid, update_uuid))
3438
+
3439
+ def download_update_attachment(
3440
+ self, project_uuid: str, update_uuid: str, attachment_uuid: str
3441
+ ) -> bytes:
3442
+ """GET …/updates/<u>/attachments/<a>/content/."""
3443
+ return self._download_attachment_of(
3444
+ self._update_parent(project_uuid, update_uuid), attachment_uuid
3445
+ )
3446
+
3447
+ def rename_update_attachment(
3448
+ self, project_uuid: str, update_uuid: str, attachment_uuid: str, *, filename: str
3449
+ ) -> Any:
3450
+ """PATCH …/updates/<u>/attachments/<a>/."""
3451
+ return self._rename_attachment_of(
3452
+ self._update_parent(project_uuid, update_uuid), attachment_uuid, filename=filename
3453
+ )
3454
+
3455
+ def delete_update_attachment(
3456
+ self, project_uuid: str, update_uuid: str, attachment_uuid: str
3457
+ ) -> Any:
3458
+ """DELETE …/updates/<u>/attachments/<a>/."""
3459
+ return self._delete_attachment_of(
3460
+ self._update_parent(project_uuid, update_uuid), attachment_uuid
3461
+ )
3462
+
3324
3463
  # Goal attachments (POST / DELETE: signed-in person; reads: anyone who can see it)
3325
3464
  def upload_goal_attachment(
3326
3465
  self,
@@ -3599,6 +3738,14 @@ class DailyBotClient:
3599
3738
  json={k: v for k, v in fields.items() if v is not None},
3600
3739
  )
3601
3740
 
3741
+ def restore_milestone(self, project_uuid: str, milestone_uuid: str) -> dict[str, Any]:
3742
+ """POST …/milestones/<uuid>/restore/ — bring a retired milestone back (idempotent)."""
3743
+ return self._tasks_write(
3744
+ "POST",
3745
+ f"projects/{_path_segment(project_uuid)}/milestones/{_path_segment(milestone_uuid)}/"
3746
+ "restore/",
3747
+ )
3748
+
3602
3749
  def delete_milestone(self, project_uuid: str, milestone_uuid: str) -> Any:
3603
3750
  """DELETE …/milestones/<uuid>/ — retires (archives); tasks keep pointing at it."""
3604
3751
  return self._tasks_write(
@@ -1,9 +1,10 @@
1
- """Shared attachment flows for tasks, comments, projects and goals.
1
+ """Shared attachment flows for tasks, comments, projects, goals, milestones and updates.
2
2
 
3
- Four parents own an `attachments/` collection with the same row shape, so the
3
+ Six parents own an `attachments/` collection with the same row shape, so the
4
4
  commands share one implementation of each step: read the local file under a
5
5
  hard limit, upload, list, download to a path that is never overwritten by
6
- surprise, and delete with a stated consequence. Each command supplies only the
6
+ surprise, rename within the server's bounds, and delete with a stated
7
+ consequence. Each command supplies only the
7
8
  client call for its parent.
8
9
  """
9
10
 
@@ -190,6 +191,34 @@ def run_get(
190
191
  print_success(f"Saved {len(content)} bytes to {output}.")
191
192
 
192
193
 
194
+ ATTACHMENT_NAME_MAX_CHARS: int = 255
195
+
196
+
197
+ def run_rename(
198
+ rename: Callable[[Any, str], Any],
199
+ filename: str,
200
+ *,
201
+ json_mode: bool,
202
+ require_auth: Callable[[], Any],
203
+ ) -> None:
204
+ """Rename one attachment. The name is checked locally against the server's bounds."""
205
+ cleaned: str = filename.strip()
206
+ if not cleaned or len(cleaned) > ATTACHMENT_NAME_MAX_CHARS:
207
+ raise click.UsageError(
208
+ f"The new name must be 1 to {ATTACHMENT_NAME_MAX_CHARS} characters. Nothing was sent."
209
+ )
210
+ client: Any = require_auth()
211
+ try:
212
+ with console.status("Renaming the attachment..."):
213
+ data: Any = rename(client, cleaned)
214
+ except APIError as exc:
215
+ exit_for_tasks_error(exc, json_mode)
216
+ if json_mode:
217
+ emit_json(data)
218
+ return
219
+ print_success(f"Attachment renamed to {cleaned}.")
220
+
221
+
193
222
  def run_delete(
194
223
  delete: Callable[[Any], Any],
195
224
  consequence: str,
@@ -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..."):