dailybot-cli 3.26.1__tar.gz → 3.27.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.
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/PKG-INFO +1 -1
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/api_client.py +178 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/board.py +203 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/plan.py +6 -3
- dailybot_cli-3.27.0/dailybot_cli/commands/plan_label.py +234 -0
- dailybot_cli-3.27.0/dailybot_cli/commands/plan_views.py +173 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/project.py +158 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/tasks.py +139 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli.egg-info/PKG-INFO +1 -1
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli.egg-info/SOURCES.txt +2 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/pyproject.toml +1 -1
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/LICENSE +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/README.md +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/__init__.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/__init__.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/_attachments.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/_beta.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/_briefing.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/_channels.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/_destructive.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/_favorites.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/_outbound.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/_paging.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/_refs.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/_rollups.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/_schedule.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/_sorting.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/_writes.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/agent.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/ask.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/auth.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/authoring_helpers.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/channels.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/chat.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/checkin.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/config.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/conversation.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/env.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/featured.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/form.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/goal.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/hook.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/identity.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/interactive.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/interactive_chat.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/kudos.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/label.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/public_api_helpers.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/query_options.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/status.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/task.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/tasks_settings.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/team.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/uninstall.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/update.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/upgrade.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/user.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/user_scoped_actions.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/version.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/commands/workflow.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/config.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/display.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/install_method.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/ledger.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/main.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/tui/__init__.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/tui/app.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/tui/commands.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/tui/conversation.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli/tui/intents.py +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli.egg-info/dependency_links.txt +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli.egg-info/entry_points.txt +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli.egg-info/requires.txt +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/dailybot_cli.egg-info/top_level.txt +0 -0
- {dailybot_cli-3.26.1 → dailybot_cli-3.27.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dailybot-cli
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.27.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
|
|
@@ -2565,6 +2565,26 @@ class DailyBotClient:
|
|
|
2565
2565
|
"""DELETE /v1/plan/views/<uuid>/ — permanent."""
|
|
2566
2566
|
return self._tasks_write("DELETE", f"views/{_path_segment(view_uuid)}/")
|
|
2567
2567
|
|
|
2568
|
+
def list_workspace_views(self) -> Any:
|
|
2569
|
+
"""GET /v1/plan/views/workspace/ — personal + shared workspace views (person-only)."""
|
|
2570
|
+
return self._tasks_read("views/workspace/")
|
|
2571
|
+
|
|
2572
|
+
def list_workspace_views_with_etag(self) -> tuple[Any, str | None]:
|
|
2573
|
+
"""GET /v1/plan/views/workspace/ plus the ETag a save must send back."""
|
|
2574
|
+
return self._tasks_read_with_etag("views/workspace/")
|
|
2575
|
+
|
|
2576
|
+
def save_workspace_views(self, views: list[Any], *, if_match: str) -> Any:
|
|
2577
|
+
"""PUT /v1/plan/views/workspace/ — replaces the caller's WHOLE workspace view array.
|
|
2578
|
+
|
|
2579
|
+
Person-only. `If-Match` is required (412 stale, 428 absent).
|
|
2580
|
+
"""
|
|
2581
|
+
return self._tasks_write(
|
|
2582
|
+
"PUT",
|
|
2583
|
+
"views/workspace/",
|
|
2584
|
+
json=views,
|
|
2585
|
+
headers={"If-Match": self._checked_if_match(if_match)},
|
|
2586
|
+
)
|
|
2587
|
+
|
|
2568
2588
|
def list_board_mentionables(self, board_uuid: str) -> Any:
|
|
2569
2589
|
"""GET /v1/plan/boards/<uuid>/mentionables/ — who this viewer may @mention."""
|
|
2570
2590
|
return self._tasks_read(f"boards/{_path_segment(board_uuid)}/mentionables/")
|
|
@@ -2981,6 +3001,78 @@ class DailyBotClient:
|
|
|
2981
3001
|
"POST", f"boards/{_path_segment(board_uuid)}/states/reorder/", json={"order": order}
|
|
2982
3002
|
)
|
|
2983
3003
|
|
|
3004
|
+
def reorder_board(
|
|
3005
|
+
self,
|
|
3006
|
+
board_uuid: str,
|
|
3007
|
+
*,
|
|
3008
|
+
before: str | None = None,
|
|
3009
|
+
after: str | None = None,
|
|
3010
|
+
project: str | None = None,
|
|
3011
|
+
) -> Any:
|
|
3012
|
+
"""POST /v1/plan/boards/<uuid>/reorder/ — move among siblings or into another project.
|
|
3013
|
+
|
|
3014
|
+
Name ONE neighbour (`before` / `after`); both is `400 reorder_anchor_invalid`.
|
|
3015
|
+
Neither moves the board last. `project` different from the current one MOVES
|
|
3016
|
+
the board (milestones of tasks that belong to the old project are cleared).
|
|
3017
|
+
"""
|
|
3018
|
+
payload: dict[str, Any] = {}
|
|
3019
|
+
if before is not None:
|
|
3020
|
+
payload["before"] = before
|
|
3021
|
+
if after is not None:
|
|
3022
|
+
payload["after"] = after
|
|
3023
|
+
if project is not None:
|
|
3024
|
+
payload["project"] = project
|
|
3025
|
+
return self._tasks_write(
|
|
3026
|
+
"POST", f"boards/{_path_segment(board_uuid)}/reorder/", json=payload
|
|
3027
|
+
)
|
|
3028
|
+
|
|
3029
|
+
def board_move_preview(self, board_uuid: str, *, project: str) -> dict[str, Any]:
|
|
3030
|
+
"""GET /v1/plan/boards/<uuid>/move-preview/?project= — what a move would change.
|
|
3031
|
+
|
|
3032
|
+
Writes nothing. `project` is required.
|
|
3033
|
+
"""
|
|
3034
|
+
return self._tasks_read(
|
|
3035
|
+
f"boards/{_path_segment(board_uuid)}/move-preview/",
|
|
3036
|
+
params={"project": project},
|
|
3037
|
+
)
|
|
3038
|
+
|
|
3039
|
+
def update_board_member(self, board_uuid: str, user_uuid: str) -> Any:
|
|
3040
|
+
"""PATCH …/members/<user>/ — inspect the grant row (role is read-only).
|
|
3041
|
+
|
|
3042
|
+
An empty body returns the current membership. Sending `role` is `400`.
|
|
3043
|
+
"""
|
|
3044
|
+
return self._tasks_write(
|
|
3045
|
+
"PATCH",
|
|
3046
|
+
f"boards/{_path_segment(board_uuid)}/members/{_path_segment(user_uuid)}/",
|
|
3047
|
+
json={},
|
|
3048
|
+
)
|
|
3049
|
+
|
|
3050
|
+
def create_board_view(
|
|
3051
|
+
self,
|
|
3052
|
+
board_uuid: str,
|
|
3053
|
+
*,
|
|
3054
|
+
name: str,
|
|
3055
|
+
filters: dict[str, Any] | None = None,
|
|
3056
|
+
idempotency_key: str | None = None,
|
|
3057
|
+
**fields: Any,
|
|
3058
|
+
) -> Any:
|
|
3059
|
+
"""POST /v1/plan/boards/<uuid>/views/ — create one saved view (no If-Match).
|
|
3060
|
+
|
|
3061
|
+
Accepts an Idempotency-Key. Default visibility is personal.
|
|
3062
|
+
"""
|
|
3063
|
+
payload: dict[str, Any] = {
|
|
3064
|
+
"name": name,
|
|
3065
|
+
"filters": filters if filters is not None else {},
|
|
3066
|
+
**{k: v for k, v in fields.items() if v is not None},
|
|
3067
|
+
}
|
|
3068
|
+
return self._tasks_write(
|
|
3069
|
+
"POST",
|
|
3070
|
+
f"boards/{_path_segment(board_uuid)}/views/",
|
|
3071
|
+
json=payload,
|
|
3072
|
+
idempotent=True,
|
|
3073
|
+
idempotency_key=idempotency_key,
|
|
3074
|
+
)
|
|
3075
|
+
|
|
2984
3076
|
def add_board_member(
|
|
2985
3077
|
self,
|
|
2986
3078
|
board_uuid: str,
|
|
@@ -3021,6 +3113,29 @@ class DailyBotClient:
|
|
|
3021
3113
|
"POST", f"boards/{_path_segment(board_uuid)}/labels/", json=payload
|
|
3022
3114
|
)
|
|
3023
3115
|
|
|
3116
|
+
def list_plan_labels(
|
|
3117
|
+
self,
|
|
3118
|
+
*,
|
|
3119
|
+
search: str | None = None,
|
|
3120
|
+
include_archived: bool = False,
|
|
3121
|
+
**page: Any,
|
|
3122
|
+
) -> PaginatedResult:
|
|
3123
|
+
"""GET /v1/plan/labels/ — organization labels under the Plan taxonomy."""
|
|
3124
|
+
params: dict[str, Any] = {}
|
|
3125
|
+
if search:
|
|
3126
|
+
params["search"] = search
|
|
3127
|
+
if include_archived:
|
|
3128
|
+
params["include_archived"] = "true"
|
|
3129
|
+
return self._tasks_list("labels/", params=params or None, **page)
|
|
3130
|
+
|
|
3131
|
+
def create_plan_label(self, *, name: str, **fields: Any) -> Any:
|
|
3132
|
+
"""POST /v1/plan/labels/ — create an organization label (Plan surface)."""
|
|
3133
|
+
payload: dict[str, Any] = {
|
|
3134
|
+
"name": name,
|
|
3135
|
+
**{k: v for k, v in fields.items() if v is not None},
|
|
3136
|
+
}
|
|
3137
|
+
return self._tasks_write("POST", "labels/", json=payload)
|
|
3138
|
+
|
|
3024
3139
|
def update_tasks_label(self, label_uuid: str, **fields: Any) -> dict[str, Any]:
|
|
3025
3140
|
"""PATCH /v1/plan/labels/<uuid>/ — name, color, description, is_archived."""
|
|
3026
3141
|
payload: dict[str, Any] = {k: v for k, v in fields.items() if v is not None}
|
|
@@ -3132,6 +3247,14 @@ class DailyBotClient:
|
|
|
3132
3247
|
"""GET /v1/plan/tasks/ — strict about parameters; only declared ones."""
|
|
3133
3248
|
return self._tasks_list("tasks/", params=filters, **page)
|
|
3134
3249
|
|
|
3250
|
+
def get_tasks_board(self, *, filters: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
3251
|
+
"""GET /v1/plan/tasks/board/ — grouped (optionally swimlaned) snapshot over a slice.
|
|
3252
|
+
|
|
3253
|
+
Flat-list filters apply; `group_by` / `sub_group_by` / `tasks_per_group` /
|
|
3254
|
+
`offset` / `group` / `lane` control the grid. Not a paginated envelope.
|
|
3255
|
+
"""
|
|
3256
|
+
return self._tasks_read("tasks/board/", params=filters)
|
|
3257
|
+
|
|
3135
3258
|
def get_task(self, task_uuid: str) -> dict[str, Any]:
|
|
3136
3259
|
"""GET /v1/plan/tasks/<uuid>/ — the most frequent call of all."""
|
|
3137
3260
|
return self._tasks_read(f"tasks/{_path_segment(task_uuid)}/")
|
|
@@ -4223,6 +4346,61 @@ class DailyBotClient:
|
|
|
4223
4346
|
"DELETE", f"projects/{_path_segment(project_uuid)}/members/{_path_segment(user_uuid)}/"
|
|
4224
4347
|
)
|
|
4225
4348
|
|
|
4349
|
+
def update_project_member(self, project_uuid: str, user_uuid: str) -> Any:
|
|
4350
|
+
"""PATCH …/members/<user>/ — inspect the grant row (role is read-only).
|
|
4351
|
+
|
|
4352
|
+
An empty body returns the current membership. Sending `role` is `400`.
|
|
4353
|
+
"""
|
|
4354
|
+
return self._tasks_write(
|
|
4355
|
+
"PATCH",
|
|
4356
|
+
f"projects/{_path_segment(project_uuid)}/members/{_path_segment(user_uuid)}/",
|
|
4357
|
+
json={},
|
|
4358
|
+
)
|
|
4359
|
+
|
|
4360
|
+
def create_project_view(
|
|
4361
|
+
self,
|
|
4362
|
+
project_uuid: str,
|
|
4363
|
+
*,
|
|
4364
|
+
name: str,
|
|
4365
|
+
filters: dict[str, Any] | None = None,
|
|
4366
|
+
idempotency_key: str | None = None,
|
|
4367
|
+
**fields: Any,
|
|
4368
|
+
) -> Any:
|
|
4369
|
+
"""POST /v1/plan/projects/<uuid>/views/ — create one saved view (no If-Match)."""
|
|
4370
|
+
payload: dict[str, Any] = {
|
|
4371
|
+
"name": name,
|
|
4372
|
+
"filters": filters if filters is not None else {},
|
|
4373
|
+
**{k: v for k, v in fields.items() if v is not None},
|
|
4374
|
+
}
|
|
4375
|
+
return self._tasks_write(
|
|
4376
|
+
"POST",
|
|
4377
|
+
f"projects/{_path_segment(project_uuid)}/views/",
|
|
4378
|
+
json=payload,
|
|
4379
|
+
idempotent=True,
|
|
4380
|
+
idempotency_key=idempotency_key,
|
|
4381
|
+
)
|
|
4382
|
+
|
|
4383
|
+
def reorder_project(
|
|
4384
|
+
self,
|
|
4385
|
+
project_uuid: str,
|
|
4386
|
+
*,
|
|
4387
|
+
before: str | None = None,
|
|
4388
|
+
after: str | None = None,
|
|
4389
|
+
) -> Any:
|
|
4390
|
+
"""POST /v1/plan/projects/<uuid>/reorder/ — move in the shared workspace order.
|
|
4391
|
+
|
|
4392
|
+
Name ONE neighbour; both is `400 reorder_anchor_invalid`. Neither moves last.
|
|
4393
|
+
Org-admin scope (`tasks:admin`).
|
|
4394
|
+
"""
|
|
4395
|
+
payload: dict[str, Any] = {}
|
|
4396
|
+
if before is not None:
|
|
4397
|
+
payload["before"] = before
|
|
4398
|
+
if after is not None:
|
|
4399
|
+
payload["after"] = after
|
|
4400
|
+
return self._tasks_write(
|
|
4401
|
+
"POST", f"projects/{_path_segment(project_uuid)}/reorder/", json=payload
|
|
4402
|
+
)
|
|
4403
|
+
|
|
4226
4404
|
def list_project_views_with_etag(self, project_uuid: str) -> tuple[Any, str | None]:
|
|
4227
4405
|
"""GET /v1/plan/projects/<uuid>/views/ plus the `ETag` a save must send back."""
|
|
4228
4406
|
return self._tasks_read_with_etag(f"projects/{_path_segment(project_uuid)}/views/")
|
|
@@ -214,6 +214,19 @@ _MEMBER_COLUMNS: list[tuple[str, str, bool]] = [
|
|
|
214
214
|
("Role", "role", True),
|
|
215
215
|
("User UUID", "user.uuid", True),
|
|
216
216
|
]
|
|
217
|
+
_MEMBER_DETAIL_FIELDS: list[tuple[str, str]] = [
|
|
218
|
+
("UUID", "uuid"),
|
|
219
|
+
("User UUID", "user_uuid"),
|
|
220
|
+
("Team UUID", "team_uuid"),
|
|
221
|
+
("Role", "role"),
|
|
222
|
+
]
|
|
223
|
+
_MOVE_PREVIEW_FIELDS: list[tuple[str, str]] = [
|
|
224
|
+
("From project", "from_project_uuid"),
|
|
225
|
+
("To project", "to_project_uuid"),
|
|
226
|
+
("Tasks", "tasks_count"),
|
|
227
|
+
("Foreign milestones", "tasks_with_foreign_milestone"),
|
|
228
|
+
("Goals affected", "goals_affected"),
|
|
229
|
+
]
|
|
217
230
|
_LABEL_COLUMNS: list[tuple[str, str, bool]] = [
|
|
218
231
|
("Name", "name", False),
|
|
219
232
|
("Color", "color", True),
|
|
@@ -789,6 +802,34 @@ def board_member_remove(
|
|
|
789
802
|
print_success("Member removed.")
|
|
790
803
|
|
|
791
804
|
|
|
805
|
+
@board_member.command("update")
|
|
806
|
+
@click.argument("board_uuid", metavar="BOARD")
|
|
807
|
+
@click.argument("user_uuid", metavar="USER")
|
|
808
|
+
@click.option("--json", "json_mode", is_flag=True, help="Emit machine-readable JSON to stdout.")
|
|
809
|
+
def board_member_update(board_uuid: str, user_uuid: str, json_mode: bool) -> None:
|
|
810
|
+
"""Inspect a board membership grant. Role is read-only (sending one is refused).
|
|
811
|
+
|
|
812
|
+
\b
|
|
813
|
+
An empty PATCH returns the current grant row. Board membership has no role
|
|
814
|
+
column — org roles plus board visibility are the access model.
|
|
815
|
+
|
|
816
|
+
\b
|
|
817
|
+
Examples:
|
|
818
|
+
dailybot plan board member update <board-uuid> <user-uuid>
|
|
819
|
+
dailybot plan board member update <board-uuid> <user-uuid> --json
|
|
820
|
+
"""
|
|
821
|
+
client = require_auth()
|
|
822
|
+
try:
|
|
823
|
+
with console.status("Reading the membership..."):
|
|
824
|
+
data: dict[str, Any] = client.update_board_member(board_uuid, user_uuid)
|
|
825
|
+
except APIError as exc:
|
|
826
|
+
exit_for_tasks_error(exc, json_mode)
|
|
827
|
+
if json_mode:
|
|
828
|
+
emit_json(data)
|
|
829
|
+
return
|
|
830
|
+
print_tasks_detail_panel("Board member", data, _MEMBER_DETAIL_FIELDS)
|
|
831
|
+
|
|
832
|
+
|
|
792
833
|
# ---------------------------------------------------------------------------
|
|
793
834
|
# Labels and saved views — person-only writes
|
|
794
835
|
# ---------------------------------------------------------------------------
|
|
@@ -935,6 +976,94 @@ def board_view() -> None:
|
|
|
935
976
|
"""
|
|
936
977
|
|
|
937
978
|
|
|
979
|
+
@board_view.command("create")
|
|
980
|
+
@click.argument("board_uuid", metavar="BOARD")
|
|
981
|
+
@click.option("-n", "--name", required=True, help="View name (max 64).")
|
|
982
|
+
@click.option(
|
|
983
|
+
"--view-mode",
|
|
984
|
+
default=None,
|
|
985
|
+
type=click.Choice(["list", "board", "kanban", "timeline", "calendar"], case_sensitive=False),
|
|
986
|
+
help="How the filtered set is drawn. `kanban` is accepted as an alias for `board`.",
|
|
987
|
+
)
|
|
988
|
+
@click.option("--group-by", default=None, help="Column dimension (state, owner, priority, …).")
|
|
989
|
+
@click.option("--sub-group-by", default=None, help="Swimlane dimension, or `none`.")
|
|
990
|
+
@click.option("--sort", default=None, help="Sort expression as the web app saves it.")
|
|
991
|
+
@click.option(
|
|
992
|
+
"--visibility",
|
|
993
|
+
default=None,
|
|
994
|
+
type=click.Choice(["personal", "shared", "board_default"], case_sensitive=False),
|
|
995
|
+
help="Default personal. shared/board_default need tasks:admin.",
|
|
996
|
+
)
|
|
997
|
+
@click.option(
|
|
998
|
+
"-f",
|
|
999
|
+
"--filters-file",
|
|
1000
|
+
type=click.File("r"),
|
|
1001
|
+
default=None,
|
|
1002
|
+
help="JSON object of filters (`-` reads stdin). Default empty object.",
|
|
1003
|
+
)
|
|
1004
|
+
@click.option("--idempotency-key", default=None, help="Reuse a key to make a retry safe.")
|
|
1005
|
+
@click.option("--json", "json_mode", is_flag=True, help="Emit machine-readable JSON to stdout.")
|
|
1006
|
+
def board_view_create(
|
|
1007
|
+
board_uuid: str,
|
|
1008
|
+
name: str,
|
|
1009
|
+
view_mode: str | None,
|
|
1010
|
+
group_by: str | None,
|
|
1011
|
+
sub_group_by: str | None,
|
|
1012
|
+
sort: str | None,
|
|
1013
|
+
visibility: str | None,
|
|
1014
|
+
filters_file: Any,
|
|
1015
|
+
idempotency_key: str | None,
|
|
1016
|
+
json_mode: bool,
|
|
1017
|
+
) -> None:
|
|
1018
|
+
"""Create one saved view on a board without replacing the whole list.
|
|
1019
|
+
|
|
1020
|
+
\b
|
|
1021
|
+
No If-Match is required (unlike `view save`). Send --idempotency-key so a
|
|
1022
|
+
retry cannot create a second view.
|
|
1023
|
+
|
|
1024
|
+
\b
|
|
1025
|
+
Examples:
|
|
1026
|
+
dailybot plan board view create <board-uuid> -n "My backlog" --view-mode board
|
|
1027
|
+
dailybot plan board view create <board-uuid> -n Blocked -f filters.json --json
|
|
1028
|
+
"""
|
|
1029
|
+
filters: dict[str, Any] = {}
|
|
1030
|
+
if filters_file is not None:
|
|
1031
|
+
try:
|
|
1032
|
+
loaded: Any = load_json_input(filters_file)
|
|
1033
|
+
except ValueError as exc:
|
|
1034
|
+
raise click.BadParameter(f"not valid JSON: {exc}", param_hint="--filters-file") from exc
|
|
1035
|
+
if not isinstance(loaded, dict):
|
|
1036
|
+
raise click.BadParameter("must be a JSON object.", param_hint="--filters-file")
|
|
1037
|
+
filters = loaded
|
|
1038
|
+
fields: dict[str, Any] = {
|
|
1039
|
+
k: v
|
|
1040
|
+
for k, v in (
|
|
1041
|
+
("view_mode", view_mode),
|
|
1042
|
+
("group_by", group_by),
|
|
1043
|
+
("sub_group_by", sub_group_by),
|
|
1044
|
+
("sort", sort),
|
|
1045
|
+
("visibility", visibility),
|
|
1046
|
+
)
|
|
1047
|
+
if v is not None
|
|
1048
|
+
}
|
|
1049
|
+
client = require_auth()
|
|
1050
|
+
try:
|
|
1051
|
+
with console.status("Creating the view..."):
|
|
1052
|
+
data: dict[str, Any] = client.create_board_view(
|
|
1053
|
+
board_uuid,
|
|
1054
|
+
name=name,
|
|
1055
|
+
filters=filters,
|
|
1056
|
+
idempotency_key=idempotency_key,
|
|
1057
|
+
**fields,
|
|
1058
|
+
)
|
|
1059
|
+
except APIError as exc:
|
|
1060
|
+
exit_for_tasks_error(exc, json_mode)
|
|
1061
|
+
if json_mode:
|
|
1062
|
+
emit_json(data)
|
|
1063
|
+
return
|
|
1064
|
+
report_write(data, f"Created view {named(data, name)}")
|
|
1065
|
+
|
|
1066
|
+
|
|
938
1067
|
@board_view.command("save")
|
|
939
1068
|
@click.argument("board_uuid", metavar="BOARD")
|
|
940
1069
|
@click.option(
|
|
@@ -1020,6 +1149,80 @@ def board_view_save(
|
|
|
1020
1149
|
print_tasks_rows("Views", rows_of(data), _VIEW_COLUMNS, empty="No saved views.")
|
|
1021
1150
|
|
|
1022
1151
|
|
|
1152
|
+
@board.command("reorder")
|
|
1153
|
+
@click.argument("board_uuid", metavar="BOARD")
|
|
1154
|
+
@click.option(
|
|
1155
|
+
"--before", default=None, help="Place this board immediately before this sibling board uuid."
|
|
1156
|
+
)
|
|
1157
|
+
@click.option(
|
|
1158
|
+
"--after", default=None, help="Place this board immediately after this sibling board uuid."
|
|
1159
|
+
)
|
|
1160
|
+
@click.option(
|
|
1161
|
+
"--project",
|
|
1162
|
+
default=None,
|
|
1163
|
+
help="Target project uuid. When different from the current one, the board is MOVED there.",
|
|
1164
|
+
)
|
|
1165
|
+
@click.option("--json", "json_mode", is_flag=True, help="Emit machine-readable JSON to stdout.")
|
|
1166
|
+
def board_reorder(
|
|
1167
|
+
board_uuid: str,
|
|
1168
|
+
before: str | None,
|
|
1169
|
+
after: str | None,
|
|
1170
|
+
project: str | None,
|
|
1171
|
+
json_mode: bool,
|
|
1172
|
+
) -> None:
|
|
1173
|
+
"""Move a board among siblings, or into another project. Org admin / tasks:admin.
|
|
1174
|
+
|
|
1175
|
+
\b
|
|
1176
|
+
Pass exactly one of --before or --after (or neither to move last). Both is
|
|
1177
|
+
refused (`reorder_anchor_invalid`). Moving to another project clears milestones
|
|
1178
|
+
on tasks that belonged only to the old project; the board key never changes.
|
|
1179
|
+
Preview first with `board move-preview`.
|
|
1180
|
+
|
|
1181
|
+
\b
|
|
1182
|
+
Examples:
|
|
1183
|
+
dailybot plan board reorder <board-uuid> --after <other-board-uuid>
|
|
1184
|
+
dailybot plan board reorder <board-uuid> --project <project-uuid> --before <board-uuid>
|
|
1185
|
+
"""
|
|
1186
|
+
if before is not None and after is not None:
|
|
1187
|
+
raise click.UsageError("Pass at most one of --before or --after.")
|
|
1188
|
+
client = require_auth()
|
|
1189
|
+
try:
|
|
1190
|
+
with console.status("Reordering the board..."):
|
|
1191
|
+
data: dict[str, Any] = client.reorder_board(
|
|
1192
|
+
board_uuid, before=before, after=after, project=project
|
|
1193
|
+
)
|
|
1194
|
+
except APIError as exc:
|
|
1195
|
+
exit_for_tasks_error(exc, json_mode)
|
|
1196
|
+
if json_mode:
|
|
1197
|
+
emit_json(data)
|
|
1198
|
+
return
|
|
1199
|
+
report_write(data, f"Reordered board {named(data, board_uuid)}")
|
|
1200
|
+
|
|
1201
|
+
|
|
1202
|
+
@board.command("move-preview")
|
|
1203
|
+
@click.argument("board_uuid", metavar="BOARD")
|
|
1204
|
+
@click.option("--project", required=True, help="Target project uuid to preview a move into.")
|
|
1205
|
+
@click.option("--json", "json_mode", is_flag=True, help="Emit machine-readable JSON to stdout.")
|
|
1206
|
+
def board_move_preview(board_uuid: str, project: str, json_mode: bool) -> None:
|
|
1207
|
+
"""Show what moving this board into another project would change. Writes nothing.
|
|
1208
|
+
|
|
1209
|
+
\b
|
|
1210
|
+
Examples:
|
|
1211
|
+
dailybot plan board move-preview <board-uuid> --project <project-uuid>
|
|
1212
|
+
dailybot plan board move-preview <board-uuid> --project <project-uuid> --json
|
|
1213
|
+
"""
|
|
1214
|
+
client = require_auth()
|
|
1215
|
+
try:
|
|
1216
|
+
with console.status("Building the move preview..."):
|
|
1217
|
+
data: dict[str, Any] = client.board_move_preview(board_uuid, project=project)
|
|
1218
|
+
except APIError as exc:
|
|
1219
|
+
exit_for_tasks_error(exc, json_mode)
|
|
1220
|
+
if json_mode:
|
|
1221
|
+
emit_json(data)
|
|
1222
|
+
return
|
|
1223
|
+
print_tasks_detail_panel("Board move preview", data, _MOVE_PREVIEW_FIELDS)
|
|
1224
|
+
|
|
1225
|
+
|
|
1023
1226
|
@board.command("snapshot")
|
|
1024
1227
|
@click.argument("board_uuid", metavar="BOARD")
|
|
1025
1228
|
@click.option("--sort", default=None, callback=parse_sort, help=SORT_HELP)
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
"""`dailybot plan`: the one root of the Dailybot Plan command groups.
|
|
2
2
|
|
|
3
3
|
Dailybot Plan (public API root ``/v1/plan/``). Its groups (``tasks``, ``task``, ``board``,
|
|
4
|
-
``project``, ``goal``) are reachable only under this root,
|
|
4
|
+
``project``, ``goal``, ``label``, ``views``) are reachable only under this root,
|
|
5
|
+
e.g. ``dailybot plan tasks routes list``.
|
|
5
6
|
"""
|
|
6
7
|
|
|
7
8
|
import click
|
|
8
9
|
|
|
9
10
|
from dailybot_cli.commands.board import board
|
|
10
11
|
from dailybot_cli.commands.goal import goal
|
|
12
|
+
from dailybot_cli.commands.plan_label import label
|
|
13
|
+
from dailybot_cli.commands.plan_views import views
|
|
11
14
|
from dailybot_cli.commands.project import project
|
|
12
15
|
from dailybot_cli.commands.task import task
|
|
13
16
|
from dailybot_cli.commands.tasks import tasks
|
|
@@ -21,11 +24,11 @@ def plan() -> None:
|
|
|
21
24
|
Every command lives under `dailybot plan`:
|
|
22
25
|
dailybot plan tasks status
|
|
23
26
|
dailybot plan task get ENG-142
|
|
24
|
-
dailybot plan board | project | goal ...
|
|
27
|
+
dailybot plan board | project | goal | label | views ...
|
|
25
28
|
Notifications, routes, reports and the briefing live under the tasks group:
|
|
26
29
|
dailybot plan tasks notifications get
|
|
27
30
|
"""
|
|
28
31
|
|
|
29
32
|
|
|
30
|
-
for _group in (tasks, task, board, project, goal):
|
|
33
|
+
for _group in (tasks, task, board, project, goal, label, views):
|
|
31
34
|
plan.add_command(_group)
|