dailybot-cli 3.20.0__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.
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/PKG-INFO +4 -1
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/README.md +3 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/api_client.py +147 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/_attachments.py +32 -3
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/project.py +476 -10
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/public_api_helpers.py +5 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/display.py +37 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli.egg-info/PKG-INFO +4 -1
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/pyproject.toml +1 -1
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/LICENSE +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/__init__.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/__init__.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/_beta.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/_briefing.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/_destructive.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/_favorites.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/_rollups.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/_writes.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/agent.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/ask.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/auth.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/authoring_helpers.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/board.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/channels.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/chat.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/checkin.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/config.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/conversation.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/env.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/featured.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/form.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/goal.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/hook.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/identity.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/interactive.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/interactive_chat.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/kudos.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/label.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/query_options.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/status.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/task.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/tasks.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/team.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/uninstall.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/update.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/upgrade.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/user.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/user_scoped_actions.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/version.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/commands/workflow.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/config.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/install_method.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/ledger.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/main.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/tui/__init__.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/tui/app.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/tui/commands.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/tui/conversation.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli/tui/intents.py +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli.egg-info/SOURCES.txt +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli.egg-info/dependency_links.txt +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli.egg-info/entry_points.txt +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli.egg-info/requires.txt +0 -0
- {dailybot_cli-3.20.0 → dailybot_cli-3.21.0}/dailybot_cli.egg-info/top_level.txt +0 -0
- {dailybot_cli-3.20.0 → 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.
|
|
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
|
|
@@ -992,6 +992,8 @@ humans who own them.
|
|
|
992
992
|
| `dailybot project get <uuid>` | Show one project |
|
|
993
993
|
| `dailybot project updates [<project>]` | Batched update digest, or one project's feed |
|
|
994
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>` |
|
|
995
997
|
| `dailybot project milestones [<uuid>]` | List milestones |
|
|
996
998
|
| `dailybot project milestone-complete <p> <m>` | Complete a milestone. **Its open tasks stay open** |
|
|
997
999
|
| `dailybot project milestone-reopen <p> <m>` | Reopen a milestone |
|
|
@@ -1001,6 +1003,7 @@ humans who own them.
|
|
|
1001
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** |
|
|
1002
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** |
|
|
1003
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 |
|
|
1004
1007
|
| `dailybot project archive <uuid>` | Archive a project |
|
|
1005
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) |
|
|
1006
1009
|
| `dailybot goal list` | List goals (`--include` is repeatable: `--include progress --include projects`) |
|
|
@@ -953,6 +953,8 @@ humans who own them.
|
|
|
953
953
|
| `dailybot project get <uuid>` | Show one project |
|
|
954
954
|
| `dailybot project updates [<project>]` | Batched update digest, or one project's feed |
|
|
955
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>` |
|
|
956
958
|
| `dailybot project milestones [<uuid>]` | List milestones |
|
|
957
959
|
| `dailybot project milestone-complete <p> <m>` | Complete a milestone. **Its open tasks stay open** |
|
|
958
960
|
| `dailybot project milestone-reopen <p> <m>` | Reopen a milestone |
|
|
@@ -962,6 +964,7 @@ humans who own them.
|
|
|
962
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** |
|
|
963
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** |
|
|
964
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 |
|
|
965
968
|
| `dailybot project archive <uuid>` | Archive a project |
|
|
966
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) |
|
|
967
970
|
| `dailybot goal list` | List goals (`--include` is repeatable: `--include progress --include projects`) |
|
|
@@ -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",
|
|
@@ -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
|
|
1
|
+
"""Shared attachment flows for tasks, comments, projects, goals, milestones and updates.
|
|
2
2
|
|
|
3
|
-
|
|
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
|
|
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,
|
|
@@ -7,7 +7,13 @@ from typing import Any
|
|
|
7
7
|
import click
|
|
8
8
|
|
|
9
9
|
from dailybot_cli.api_client import ATTACHMENT_MULTIPART_MAX_BYTES, APIError, PaginatedResult
|
|
10
|
-
from dailybot_cli.commands._attachments import
|
|
10
|
+
from dailybot_cli.commands._attachments import (
|
|
11
|
+
run_attach,
|
|
12
|
+
run_delete,
|
|
13
|
+
run_get,
|
|
14
|
+
run_list,
|
|
15
|
+
run_rename,
|
|
16
|
+
)
|
|
11
17
|
from dailybot_cli.commands._beta import mark_beta
|
|
12
18
|
from dailybot_cli.commands._destructive import confirm_without_preview, preview_then_confirm
|
|
13
19
|
from dailybot_cli.commands._rollups import render_rollup
|
|
@@ -22,16 +28,15 @@ from dailybot_cli.commands.public_api_helpers import (
|
|
|
22
28
|
from dailybot_cli.commands.query_options import build_query_params, query_options, resolve_fetch_all
|
|
23
29
|
from dailybot_cli.display import (
|
|
24
30
|
console,
|
|
25
|
-
present_untrusted,
|
|
26
31
|
print_info,
|
|
27
32
|
print_milestones_table,
|
|
28
33
|
print_pagination_footer,
|
|
34
|
+
print_project_updates,
|
|
29
35
|
print_projects_table,
|
|
30
36
|
print_raw_value,
|
|
31
37
|
print_success,
|
|
32
38
|
print_tasks_detail_panel,
|
|
33
39
|
print_tasks_rows,
|
|
34
|
-
safe_text,
|
|
35
40
|
)
|
|
36
41
|
|
|
37
42
|
# Split deliberately. `projects` is a goal-shaped selector: a project has no
|
|
@@ -193,11 +198,7 @@ def project_updates(project_uuid: str | None, json_mode: bool, **flags: Any) ->
|
|
|
193
198
|
if json_mode:
|
|
194
199
|
emit_json(_envelope(result))
|
|
195
200
|
return
|
|
196
|
-
|
|
197
|
-
console.print(
|
|
198
|
-
f"[dim]{safe_text(update.get('created_at', ''))}[/dim] "
|
|
199
|
-
f"{present_untrusted(update.get('body'), limit=160)}"
|
|
200
|
-
)
|
|
201
|
+
print_project_updates(result.results)
|
|
201
202
|
print_pagination_footer(len(result.results), result.count, has_more=bool(result.next))
|
|
202
203
|
|
|
203
204
|
|
|
@@ -928,9 +929,32 @@ def project_milestone_delete(
|
|
|
928
929
|
print_success("Milestone retired.")
|
|
929
930
|
|
|
930
931
|
|
|
932
|
+
@project.command("milestone-restore")
|
|
933
|
+
@click.argument("project_uuid", metavar="PROJECT")
|
|
934
|
+
@click.argument("milestone_uuid", metavar="MILESTONE")
|
|
935
|
+
@click.option("--json", "json_mode", is_flag=True, help="Emit machine-readable JSON to stdout.")
|
|
936
|
+
def project_milestone_restore(project_uuid: str, milestone_uuid: str, json_mode: bool) -> None:
|
|
937
|
+
"""Bring a retired milestone back. Safe to repeat.
|
|
938
|
+
|
|
939
|
+
\b
|
|
940
|
+
Examples:
|
|
941
|
+
dailybot project milestone-restore <project-uuid> <milestone-uuid>
|
|
942
|
+
"""
|
|
943
|
+
client = require_auth()
|
|
944
|
+
try:
|
|
945
|
+
with console.status("Restoring the milestone..."):
|
|
946
|
+
data: dict[str, Any] = client.restore_milestone(project_uuid, milestone_uuid)
|
|
947
|
+
except APIError as exc:
|
|
948
|
+
exit_for_tasks_error(exc, json_mode)
|
|
949
|
+
if json_mode:
|
|
950
|
+
emit_json(data)
|
|
951
|
+
return
|
|
952
|
+
print_success("Milestone restored.")
|
|
953
|
+
|
|
954
|
+
|
|
931
955
|
# ---------------------------------------------------------------------------
|
|
932
|
-
# Attachments. Reading needs only visibility; attaching and deleting
|
|
933
|
-
#
|
|
956
|
+
# Attachments. Reading needs only visibility; attaching and deleting need a
|
|
957
|
+
# person (a login or a personal API key) who can change the project.
|
|
934
958
|
# ---------------------------------------------------------------------------
|
|
935
959
|
|
|
936
960
|
|
|
@@ -1053,3 +1077,445 @@ def project_attachment_delete(
|
|
|
1053
1077
|
json_mode=json_mode,
|
|
1054
1078
|
require_auth=require_auth,
|
|
1055
1079
|
)
|
|
1080
|
+
|
|
1081
|
+
|
|
1082
|
+
# ---------------------------------------------------------------------------
|
|
1083
|
+
# Milestone attachments. Anyone who can see the project and edit its milestones
|
|
1084
|
+
# (a login or a personal API key) can attach, rename and delete.
|
|
1085
|
+
# ---------------------------------------------------------------------------
|
|
1086
|
+
|
|
1087
|
+
_OUTPUT_OPTION = click.option(
|
|
1088
|
+
"-o",
|
|
1089
|
+
"--output",
|
|
1090
|
+
"output",
|
|
1091
|
+
type=click.Path(dir_okay=False, writable=True, path_type=Path),
|
|
1092
|
+
required=True,
|
|
1093
|
+
help="Where to write the file.",
|
|
1094
|
+
)
|
|
1095
|
+
|
|
1096
|
+
|
|
1097
|
+
@project.command("milestone-attach")
|
|
1098
|
+
@click.argument("project_uuid", metavar="PROJECT")
|
|
1099
|
+
@click.argument("milestone_uuid", metavar="MILESTONE")
|
|
1100
|
+
@click.argument(
|
|
1101
|
+
"file_path",
|
|
1102
|
+
metavar="FILE",
|
|
1103
|
+
type=click.Path(exists=True, dir_okay=False, readable=True, path_type=Path),
|
|
1104
|
+
)
|
|
1105
|
+
@click.option("--caption", default=None, help="Short caption shown with the file.")
|
|
1106
|
+
@click.option("--json", "json_mode", is_flag=True, help="Emit machine-readable JSON to stdout.")
|
|
1107
|
+
def project_milestone_attach(
|
|
1108
|
+
project_uuid: str, milestone_uuid: str, file_path: Path, caption: str | None, json_mode: bool
|
|
1109
|
+
) -> None:
|
|
1110
|
+
"""Attach a file to a milestone. Needs a person: `dailybot login` or a personal API key.
|
|
1111
|
+
|
|
1112
|
+
\b
|
|
1113
|
+
One request, up to 5 MiB. Reference it in the milestone description with
|
|
1114
|
+
`attachment:<uuid>` to show it inline.
|
|
1115
|
+
|
|
1116
|
+
\b
|
|
1117
|
+
Examples:
|
|
1118
|
+
dailybot project milestone-attach <project-uuid> <milestone-uuid> ./spec.pdf
|
|
1119
|
+
"""
|
|
1120
|
+
run_attach(
|
|
1121
|
+
lambda client, **file: client.upload_milestone_attachment(
|
|
1122
|
+
project_uuid, milestone_uuid, **file
|
|
1123
|
+
),
|
|
1124
|
+
file_path,
|
|
1125
|
+
caption=caption,
|
|
1126
|
+
limit=ATTACHMENT_MULTIPART_MAX_BYTES,
|
|
1127
|
+
where="per file on a milestone",
|
|
1128
|
+
json_mode=json_mode,
|
|
1129
|
+
require_auth=require_auth,
|
|
1130
|
+
)
|
|
1131
|
+
|
|
1132
|
+
|
|
1133
|
+
@project.command("milestone-attachments")
|
|
1134
|
+
@click.argument("project_uuid", metavar="PROJECT")
|
|
1135
|
+
@click.argument("milestone_uuid", metavar="MILESTONE")
|
|
1136
|
+
@click.option("--json", "json_mode", is_flag=True, help="Emit machine-readable JSON to stdout.")
|
|
1137
|
+
def project_milestone_attachments(project_uuid: str, milestone_uuid: str, json_mode: bool) -> None:
|
|
1138
|
+
"""List a milestone's attachments.
|
|
1139
|
+
|
|
1140
|
+
\b
|
|
1141
|
+
Examples:
|
|
1142
|
+
dailybot project milestone-attachments <project-uuid> <milestone-uuid> --json
|
|
1143
|
+
"""
|
|
1144
|
+
run_list(
|
|
1145
|
+
lambda client: client.list_milestone_attachments(project_uuid, milestone_uuid),
|
|
1146
|
+
json_mode=json_mode,
|
|
1147
|
+
require_auth=require_auth,
|
|
1148
|
+
)
|
|
1149
|
+
|
|
1150
|
+
|
|
1151
|
+
@project.group("milestone-attachment")
|
|
1152
|
+
def project_milestone_attachment() -> None:
|
|
1153
|
+
"""Download, rename or delete one attachment on a milestone.
|
|
1154
|
+
|
|
1155
|
+
\b
|
|
1156
|
+
Examples:
|
|
1157
|
+
dailybot project milestone-attachment get <project> <milestone> <attachment> -o ./spec.pdf
|
|
1158
|
+
dailybot project milestone-attachment rename <project> <milestone> <attachment> spec-v2.pdf
|
|
1159
|
+
dailybot project milestone-attachment delete <project> <milestone> <attachment> --dry-run
|
|
1160
|
+
"""
|
|
1161
|
+
|
|
1162
|
+
|
|
1163
|
+
@project_milestone_attachment.command("get")
|
|
1164
|
+
@click.argument("project_uuid", metavar="PROJECT")
|
|
1165
|
+
@click.argument("milestone_uuid", metavar="MILESTONE")
|
|
1166
|
+
@click.argument("attachment_uuid", metavar="ATTACHMENT")
|
|
1167
|
+
@_OUTPUT_OPTION
|
|
1168
|
+
@click.option("--force", is_flag=True, help="Overwrite the output file if it exists.")
|
|
1169
|
+
@click.option("--json", "json_mode", is_flag=True, help="Emit machine-readable JSON to stdout.")
|
|
1170
|
+
def project_milestone_attachment_get(
|
|
1171
|
+
project_uuid: str,
|
|
1172
|
+
milestone_uuid: str,
|
|
1173
|
+
attachment_uuid: str,
|
|
1174
|
+
output: Path,
|
|
1175
|
+
force: bool,
|
|
1176
|
+
json_mode: bool,
|
|
1177
|
+
) -> None:
|
|
1178
|
+
"""Download a milestone's attachment to a file. Never overwrites without --force.
|
|
1179
|
+
|
|
1180
|
+
\b
|
|
1181
|
+
Examples:
|
|
1182
|
+
dailybot project milestone-attachment get <project> <milestone> <attachment> -o ./spec.pdf
|
|
1183
|
+
"""
|
|
1184
|
+
run_get(
|
|
1185
|
+
lambda client: client.download_milestone_attachment(
|
|
1186
|
+
project_uuid, milestone_uuid, attachment_uuid
|
|
1187
|
+
),
|
|
1188
|
+
output,
|
|
1189
|
+
attachment_uuid=attachment_uuid,
|
|
1190
|
+
force=force,
|
|
1191
|
+
json_mode=json_mode,
|
|
1192
|
+
require_auth=require_auth,
|
|
1193
|
+
)
|
|
1194
|
+
|
|
1195
|
+
|
|
1196
|
+
@project_milestone_attachment.command("rename")
|
|
1197
|
+
@click.argument("project_uuid", metavar="PROJECT")
|
|
1198
|
+
@click.argument("milestone_uuid", metavar="MILESTONE")
|
|
1199
|
+
@click.argument("attachment_uuid", metavar="ATTACHMENT")
|
|
1200
|
+
@click.argument("filename")
|
|
1201
|
+
@click.option("--json", "json_mode", is_flag=True, help="Emit machine-readable JSON to stdout.")
|
|
1202
|
+
def project_milestone_attachment_rename(
|
|
1203
|
+
project_uuid: str, milestone_uuid: str, attachment_uuid: str, filename: str, json_mode: bool
|
|
1204
|
+
) -> None:
|
|
1205
|
+
"""Rename a milestone's attachment (1 to 255 characters).
|
|
1206
|
+
|
|
1207
|
+
\b
|
|
1208
|
+
Examples:
|
|
1209
|
+
dailybot project milestone-attachment rename <project> <milestone> <attachment> spec-v2.pdf
|
|
1210
|
+
"""
|
|
1211
|
+
run_rename(
|
|
1212
|
+
lambda client, name: client.rename_milestone_attachment(
|
|
1213
|
+
project_uuid, milestone_uuid, attachment_uuid, filename=name
|
|
1214
|
+
),
|
|
1215
|
+
filename,
|
|
1216
|
+
json_mode=json_mode,
|
|
1217
|
+
require_auth=require_auth,
|
|
1218
|
+
)
|
|
1219
|
+
|
|
1220
|
+
|
|
1221
|
+
@project_milestone_attachment.command("delete")
|
|
1222
|
+
@click.argument("project_uuid", metavar="PROJECT")
|
|
1223
|
+
@click.argument("milestone_uuid", metavar="MILESTONE")
|
|
1224
|
+
@click.argument("attachment_uuid", metavar="ATTACHMENT")
|
|
1225
|
+
@click.option("--dry-run", is_flag=True, help="Say what would happen and send nothing.")
|
|
1226
|
+
@click.option("-y", "--yes", "assume_yes", is_flag=True, help="Skip the confirmation.")
|
|
1227
|
+
@click.option("--json", "json_mode", is_flag=True, help="Emit machine-readable JSON to stdout.")
|
|
1228
|
+
def project_milestone_attachment_delete(
|
|
1229
|
+
project_uuid: str,
|
|
1230
|
+
milestone_uuid: str,
|
|
1231
|
+
attachment_uuid: str,
|
|
1232
|
+
dry_run: bool,
|
|
1233
|
+
assume_yes: bool,
|
|
1234
|
+
json_mode: bool,
|
|
1235
|
+
) -> None:
|
|
1236
|
+
"""Remove an attachment from a milestone. This cannot be undone.
|
|
1237
|
+
|
|
1238
|
+
\b
|
|
1239
|
+
Examples:
|
|
1240
|
+
dailybot project milestone-attachment delete <project> <milestone> <attachment> --dry-run
|
|
1241
|
+
dailybot project milestone-attachment delete <project> <milestone> <attachment> --yes
|
|
1242
|
+
"""
|
|
1243
|
+
run_delete(
|
|
1244
|
+
lambda client: client.delete_milestone_attachment(
|
|
1245
|
+
project_uuid, milestone_uuid, attachment_uuid
|
|
1246
|
+
),
|
|
1247
|
+
f"delete attachment {attachment_uuid} from milestone {milestone_uuid}.",
|
|
1248
|
+
receipt={
|
|
1249
|
+
"project": project_uuid,
|
|
1250
|
+
"milestone": milestone_uuid,
|
|
1251
|
+
"attachment": attachment_uuid,
|
|
1252
|
+
},
|
|
1253
|
+
dry_run=dry_run,
|
|
1254
|
+
assume_yes=assume_yes,
|
|
1255
|
+
json_mode=json_mode,
|
|
1256
|
+
require_auth=require_auth,
|
|
1257
|
+
)
|
|
1258
|
+
|
|
1259
|
+
|
|
1260
|
+
# ---------------------------------------------------------------------------
|
|
1261
|
+
# One project update: read, edit (author only), delete (author or org admin),
|
|
1262
|
+
# and its attachments (attach and rename: author only; delete: author or org
|
|
1263
|
+
# admin). `--agent-name` stamps every write.
|
|
1264
|
+
# ---------------------------------------------------------------------------
|
|
1265
|
+
|
|
1266
|
+
|
|
1267
|
+
@project.command("update-get")
|
|
1268
|
+
@click.argument("project_uuid", metavar="PROJECT")
|
|
1269
|
+
@click.argument("update_uuid", metavar="UPDATE")
|
|
1270
|
+
@click.option("--json", "json_mode", is_flag=True, help="Emit machine-readable JSON to stdout.")
|
|
1271
|
+
def project_update_get(project_uuid: str, update_uuid: str, json_mode: bool) -> None:
|
|
1272
|
+
"""Show one project update, with its author, agent, health and attachments.
|
|
1273
|
+
|
|
1274
|
+
\b
|
|
1275
|
+
Examples:
|
|
1276
|
+
dailybot project update-get <project-uuid> <update-uuid> --json
|
|
1277
|
+
"""
|
|
1278
|
+
client = require_auth()
|
|
1279
|
+
try:
|
|
1280
|
+
with console.status("Reading the update..."):
|
|
1281
|
+
data: dict[str, Any] = client.get_project_update(project_uuid, update_uuid)
|
|
1282
|
+
except APIError as exc:
|
|
1283
|
+
exit_for_tasks_error(exc, json_mode)
|
|
1284
|
+
if json_mode:
|
|
1285
|
+
emit_json(data)
|
|
1286
|
+
return
|
|
1287
|
+
print_project_updates([data])
|
|
1288
|
+
|
|
1289
|
+
|
|
1290
|
+
@project.command("update-edit")
|
|
1291
|
+
@click.argument("project_uuid", metavar="PROJECT")
|
|
1292
|
+
@click.argument("update_uuid", metavar="UPDATE")
|
|
1293
|
+
@click.argument("body", required=False)
|
|
1294
|
+
@click.option(
|
|
1295
|
+
"--health",
|
|
1296
|
+
type=click.Choice(PROJECT_HEALTH),
|
|
1297
|
+
default=None,
|
|
1298
|
+
help="Change the health this update claims.",
|
|
1299
|
+
)
|
|
1300
|
+
@click.option("--json", "json_mode", is_flag=True, help="Emit machine-readable JSON to stdout.")
|
|
1301
|
+
def project_update_edit(
|
|
1302
|
+
project_uuid: str, update_uuid: str, body: str | None, health: str | None, json_mode: bool
|
|
1303
|
+
) -> None:
|
|
1304
|
+
"""Edit your project update's text and/or health. Only its author can.
|
|
1305
|
+
|
|
1306
|
+
\b
|
|
1307
|
+
Pass `-` as the body to read it from stdin. To show an attached image inline,
|
|
1308
|
+
put `attachment:<uuid>` in the body.
|
|
1309
|
+
|
|
1310
|
+
\b
|
|
1311
|
+
Examples:
|
|
1312
|
+
dailybot project update-edit <project-uuid> <update-uuid> "Shipped, with the fix"
|
|
1313
|
+
dailybot project update-edit <project-uuid> <update-uuid> --health at_risk
|
|
1314
|
+
"""
|
|
1315
|
+
if body is None and health is None:
|
|
1316
|
+
raise click.UsageError("Pass a new BODY, --health, or both. Nothing was sent.")
|
|
1317
|
+
client = require_auth()
|
|
1318
|
+
try:
|
|
1319
|
+
with console.status("Editing the update..."):
|
|
1320
|
+
data: Any = client.edit_project_update(
|
|
1321
|
+
project_uuid,
|
|
1322
|
+
update_uuid,
|
|
1323
|
+
body=_read_body(body) if body is not None else None,
|
|
1324
|
+
health=health,
|
|
1325
|
+
)
|
|
1326
|
+
except APIError as exc:
|
|
1327
|
+
exit_for_tasks_error(exc, json_mode)
|
|
1328
|
+
if json_mode:
|
|
1329
|
+
emit_json(data)
|
|
1330
|
+
return
|
|
1331
|
+
report_write(data, "Project update edited")
|
|
1332
|
+
|
|
1333
|
+
|
|
1334
|
+
@project.command("update-delete")
|
|
1335
|
+
@click.argument("project_uuid", metavar="PROJECT")
|
|
1336
|
+
@click.argument("update_uuid", metavar="UPDATE")
|
|
1337
|
+
@click.option("--dry-run", is_flag=True, help="Say what would happen and send nothing.")
|
|
1338
|
+
@click.option("-y", "--yes", "assume_yes", is_flag=True, help="Skip the confirmation.")
|
|
1339
|
+
@click.option("--json", "json_mode", is_flag=True, help="Emit machine-readable JSON to stdout.")
|
|
1340
|
+
def project_update_delete(
|
|
1341
|
+
project_uuid: str, update_uuid: str, dry_run: bool, assume_yes: bool, json_mode: bool
|
|
1342
|
+
) -> None:
|
|
1343
|
+
"""Delete a project update. Its author or an organization admin can. Cannot be undone.
|
|
1344
|
+
|
|
1345
|
+
\b
|
|
1346
|
+
Examples:
|
|
1347
|
+
dailybot project update-delete <project-uuid> <update-uuid> --dry-run
|
|
1348
|
+
dailybot project update-delete <project-uuid> <update-uuid> --yes
|
|
1349
|
+
"""
|
|
1350
|
+
if not confirm_without_preview(
|
|
1351
|
+
f"delete project update {update_uuid} and its attachments.",
|
|
1352
|
+
assume_yes=assume_yes,
|
|
1353
|
+
dry_run=dry_run,
|
|
1354
|
+
json_mode=json_mode,
|
|
1355
|
+
):
|
|
1356
|
+
return
|
|
1357
|
+
client = require_auth()
|
|
1358
|
+
try:
|
|
1359
|
+
with console.status("Deleting the update..."):
|
|
1360
|
+
client.delete_project_update(project_uuid, update_uuid)
|
|
1361
|
+
except APIError as exc:
|
|
1362
|
+
exit_for_tasks_error(exc, json_mode)
|
|
1363
|
+
if json_mode:
|
|
1364
|
+
emit_json({"deleted": True, "project": project_uuid, "update": update_uuid})
|
|
1365
|
+
return
|
|
1366
|
+
print_success("Project update deleted.")
|
|
1367
|
+
|
|
1368
|
+
|
|
1369
|
+
@project.command("update-attach")
|
|
1370
|
+
@click.argument("project_uuid", metavar="PROJECT")
|
|
1371
|
+
@click.argument("update_uuid", metavar="UPDATE")
|
|
1372
|
+
@click.argument(
|
|
1373
|
+
"file_path",
|
|
1374
|
+
metavar="FILE",
|
|
1375
|
+
type=click.Path(exists=True, dir_okay=False, readable=True, path_type=Path),
|
|
1376
|
+
)
|
|
1377
|
+
@click.option("--caption", default=None, help="Short caption shown with the file.")
|
|
1378
|
+
@click.option("--json", "json_mode", is_flag=True, help="Emit machine-readable JSON to stdout.")
|
|
1379
|
+
def project_update_attach(
|
|
1380
|
+
project_uuid: str, update_uuid: str, file_path: Path, caption: str | None, json_mode: bool
|
|
1381
|
+
) -> None:
|
|
1382
|
+
"""Attach a file to your project update. Only its author can.
|
|
1383
|
+
|
|
1384
|
+
\b
|
|
1385
|
+
One request, up to 5 MiB. For an inline image: post the update, attach the
|
|
1386
|
+
file, then `update-edit` the body with `attachment:<uuid>`.
|
|
1387
|
+
|
|
1388
|
+
\b
|
|
1389
|
+
Examples:
|
|
1390
|
+
dailybot project update-attach <project-uuid> <update-uuid> ./chart.png
|
|
1391
|
+
"""
|
|
1392
|
+
run_attach(
|
|
1393
|
+
lambda client, **file: client.upload_update_attachment(project_uuid, update_uuid, **file),
|
|
1394
|
+
file_path,
|
|
1395
|
+
caption=caption,
|
|
1396
|
+
limit=ATTACHMENT_MULTIPART_MAX_BYTES,
|
|
1397
|
+
where="per file on a project update",
|
|
1398
|
+
json_mode=json_mode,
|
|
1399
|
+
require_auth=require_auth,
|
|
1400
|
+
)
|
|
1401
|
+
|
|
1402
|
+
|
|
1403
|
+
@project.command("update-attachments")
|
|
1404
|
+
@click.argument("project_uuid", metavar="PROJECT")
|
|
1405
|
+
@click.argument("update_uuid", metavar="UPDATE")
|
|
1406
|
+
@click.option("--json", "json_mode", is_flag=True, help="Emit machine-readable JSON to stdout.")
|
|
1407
|
+
def project_update_attachments(project_uuid: str, update_uuid: str, json_mode: bool) -> None:
|
|
1408
|
+
"""List a project update's attachments.
|
|
1409
|
+
|
|
1410
|
+
\b
|
|
1411
|
+
Examples:
|
|
1412
|
+
dailybot project update-attachments <project-uuid> <update-uuid> --json
|
|
1413
|
+
"""
|
|
1414
|
+
run_list(
|
|
1415
|
+
lambda client: client.list_update_attachments(project_uuid, update_uuid),
|
|
1416
|
+
json_mode=json_mode,
|
|
1417
|
+
require_auth=require_auth,
|
|
1418
|
+
)
|
|
1419
|
+
|
|
1420
|
+
|
|
1421
|
+
@project.group("update-attachment")
|
|
1422
|
+
def project_update_attachment() -> None:
|
|
1423
|
+
"""Download, rename or delete one attachment on a project update.
|
|
1424
|
+
|
|
1425
|
+
\b
|
|
1426
|
+
Examples:
|
|
1427
|
+
dailybot project update-attachment get <project> <update> <attachment> -o ./chart.png
|
|
1428
|
+
dailybot project update-attachment rename <project> <update> <attachment> chart-q4.png
|
|
1429
|
+
dailybot project update-attachment delete <project> <update> <attachment> --yes
|
|
1430
|
+
"""
|
|
1431
|
+
|
|
1432
|
+
|
|
1433
|
+
@project_update_attachment.command("get")
|
|
1434
|
+
@click.argument("project_uuid", metavar="PROJECT")
|
|
1435
|
+
@click.argument("update_uuid", metavar="UPDATE")
|
|
1436
|
+
@click.argument("attachment_uuid", metavar="ATTACHMENT")
|
|
1437
|
+
@_OUTPUT_OPTION
|
|
1438
|
+
@click.option("--force", is_flag=True, help="Overwrite the output file if it exists.")
|
|
1439
|
+
@click.option("--json", "json_mode", is_flag=True, help="Emit machine-readable JSON to stdout.")
|
|
1440
|
+
def project_update_attachment_get(
|
|
1441
|
+
project_uuid: str,
|
|
1442
|
+
update_uuid: str,
|
|
1443
|
+
attachment_uuid: str,
|
|
1444
|
+
output: Path,
|
|
1445
|
+
force: bool,
|
|
1446
|
+
json_mode: bool,
|
|
1447
|
+
) -> None:
|
|
1448
|
+
"""Download a project update's attachment. Never overwrites without --force.
|
|
1449
|
+
|
|
1450
|
+
\b
|
|
1451
|
+
Examples:
|
|
1452
|
+
dailybot project update-attachment get <project> <update> <attachment> -o ./chart.png
|
|
1453
|
+
"""
|
|
1454
|
+
run_get(
|
|
1455
|
+
lambda client: client.download_update_attachment(
|
|
1456
|
+
project_uuid, update_uuid, attachment_uuid
|
|
1457
|
+
),
|
|
1458
|
+
output,
|
|
1459
|
+
attachment_uuid=attachment_uuid,
|
|
1460
|
+
force=force,
|
|
1461
|
+
json_mode=json_mode,
|
|
1462
|
+
require_auth=require_auth,
|
|
1463
|
+
)
|
|
1464
|
+
|
|
1465
|
+
|
|
1466
|
+
@project_update_attachment.command("rename")
|
|
1467
|
+
@click.argument("project_uuid", metavar="PROJECT")
|
|
1468
|
+
@click.argument("update_uuid", metavar="UPDATE")
|
|
1469
|
+
@click.argument("attachment_uuid", metavar="ATTACHMENT")
|
|
1470
|
+
@click.argument("filename")
|
|
1471
|
+
@click.option("--json", "json_mode", is_flag=True, help="Emit machine-readable JSON to stdout.")
|
|
1472
|
+
def project_update_attachment_rename(
|
|
1473
|
+
project_uuid: str, update_uuid: str, attachment_uuid: str, filename: str, json_mode: bool
|
|
1474
|
+
) -> None:
|
|
1475
|
+
"""Rename a project update's attachment (author only; 1 to 255 characters).
|
|
1476
|
+
|
|
1477
|
+
\b
|
|
1478
|
+
Examples:
|
|
1479
|
+
dailybot project update-attachment rename <project> <update> <attachment> chart-q4.png
|
|
1480
|
+
"""
|
|
1481
|
+
run_rename(
|
|
1482
|
+
lambda client, name: client.rename_update_attachment(
|
|
1483
|
+
project_uuid, update_uuid, attachment_uuid, filename=name
|
|
1484
|
+
),
|
|
1485
|
+
filename,
|
|
1486
|
+
json_mode=json_mode,
|
|
1487
|
+
require_auth=require_auth,
|
|
1488
|
+
)
|
|
1489
|
+
|
|
1490
|
+
|
|
1491
|
+
@project_update_attachment.command("delete")
|
|
1492
|
+
@click.argument("project_uuid", metavar="PROJECT")
|
|
1493
|
+
@click.argument("update_uuid", metavar="UPDATE")
|
|
1494
|
+
@click.argument("attachment_uuid", metavar="ATTACHMENT")
|
|
1495
|
+
@click.option("--dry-run", is_flag=True, help="Say what would happen and send nothing.")
|
|
1496
|
+
@click.option("-y", "--yes", "assume_yes", is_flag=True, help="Skip the confirmation.")
|
|
1497
|
+
@click.option("--json", "json_mode", is_flag=True, help="Emit machine-readable JSON to stdout.")
|
|
1498
|
+
def project_update_attachment_delete(
|
|
1499
|
+
project_uuid: str,
|
|
1500
|
+
update_uuid: str,
|
|
1501
|
+
attachment_uuid: str,
|
|
1502
|
+
dry_run: bool,
|
|
1503
|
+
assume_yes: bool,
|
|
1504
|
+
json_mode: bool,
|
|
1505
|
+
) -> None:
|
|
1506
|
+
"""Remove an attachment from a project update. Its author, or an organization admin. Cannot be undone.
|
|
1507
|
+
|
|
1508
|
+
\b
|
|
1509
|
+
Examples:
|
|
1510
|
+
dailybot project update-attachment delete <project> <update> <attachment> --dry-run
|
|
1511
|
+
dailybot project update-attachment delete <project> <update> <attachment> --yes
|
|
1512
|
+
"""
|
|
1513
|
+
run_delete(
|
|
1514
|
+
lambda client: client.delete_update_attachment(project_uuid, update_uuid, attachment_uuid),
|
|
1515
|
+
f"delete attachment {attachment_uuid} from project update {update_uuid}.",
|
|
1516
|
+
receipt={"project": project_uuid, "update": update_uuid, "attachment": attachment_uuid},
|
|
1517
|
+
dry_run=dry_run,
|
|
1518
|
+
assume_yes=assume_yes,
|
|
1519
|
+
json_mode=json_mode,
|
|
1520
|
+
require_auth=require_auth,
|
|
1521
|
+
)
|
|
@@ -56,6 +56,11 @@ ERROR_CODE_MESSAGES: dict[str, str] = {
|
|
|
56
56
|
# Written against what the server actually returned in the live probe
|
|
57
57
|
# recorded in the plan's PERMISSION_MATRIX_OBSERVED.md, not against prose.
|
|
58
58
|
#
|
|
59
|
+
# Project update authorship
|
|
60
|
+
"update_not_author": (
|
|
61
|
+
"Only the person who posted this project update can edit it or change its "
|
|
62
|
+
"attachments (an organization admin can delete it). Post a new update instead."
|
|
63
|
+
),
|
|
59
64
|
# Agent attribution (--agent-name / DAILYBOT_AGENT_NAME)
|
|
60
65
|
"invalid_agent_attribution": (
|
|
61
66
|
"The agent name was refused. Use a plain name of 128 characters or fewer: "
|
|
@@ -2062,17 +2062,54 @@ def print_milestones_table(milestones: list[dict[str, Any]], *, rollup: Any = No
|
|
|
2062
2062
|
table.add_column("Name")
|
|
2063
2063
|
table.add_column("Status", no_wrap=True)
|
|
2064
2064
|
table.add_column("Open", no_wrap=True)
|
|
2065
|
+
table.add_column("Files", no_wrap=True)
|
|
2065
2066
|
table.add_column("UUID", no_wrap=True)
|
|
2066
2067
|
for row in milestones:
|
|
2068
|
+
files: Any = row.get("attachment_count")
|
|
2067
2069
|
table.add_row(
|
|
2068
2070
|
present_untrusted(row.get("name")),
|
|
2069
2071
|
present_untrusted(row.get("status"), limit=16),
|
|
2070
2072
|
rollup(row, "open_task_count") if rollup else "",
|
|
2073
|
+
str(files) if isinstance(files, int) else "",
|
|
2071
2074
|
safe_text(row.get("uuid") or ""),
|
|
2072
2075
|
)
|
|
2073
2076
|
console.print(table)
|
|
2074
2077
|
|
|
2075
2078
|
|
|
2079
|
+
def print_project_updates(updates: list[dict[str, Any]]) -> None:
|
|
2080
|
+
"""Render project updates: the person, the agent companion, health, and files.
|
|
2081
|
+
|
|
2082
|
+
The body is user-authored text and is quoted as data. The person who posted
|
|
2083
|
+
stays primary; `via <agent>` names the agent that executed the post.
|
|
2084
|
+
"""
|
|
2085
|
+
if not updates:
|
|
2086
|
+
print_info("No updates.")
|
|
2087
|
+
return
|
|
2088
|
+
for update in updates:
|
|
2089
|
+
author: Any = update.get("created_by") or update.get("author") or {}
|
|
2090
|
+
who: str = present_untrusted(
|
|
2091
|
+
_person_name(author) if isinstance(author, dict) else author, limit=40
|
|
2092
|
+
)
|
|
2093
|
+
agent: Any = update.get("executed_by_agent")
|
|
2094
|
+
if isinstance(agent, dict) and agent.get("name"):
|
|
2095
|
+
who += f" [dim]via {present_untrusted(agent.get('name'), limit=40)}[/dim]"
|
|
2096
|
+
meta: list[str] = [safe_text(update.get("created_at") or "")]
|
|
2097
|
+
if update.get("health"):
|
|
2098
|
+
meta.append(safe_text(update.get("health")))
|
|
2099
|
+
if update.get("edited_at"):
|
|
2100
|
+
meta.append("edited")
|
|
2101
|
+
attachments: Any = update.get("attachments")
|
|
2102
|
+
count: Any = update.get("attachment_count")
|
|
2103
|
+
if isinstance(attachments, list) and attachments:
|
|
2104
|
+
meta.append(f"{len(attachments)} file(s)")
|
|
2105
|
+
elif isinstance(count, int) and count > 0:
|
|
2106
|
+
meta.append(f"{count} file(s)")
|
|
2107
|
+
console.print(f"[dim]{' · '.join(m for m in meta if m)}[/dim] {who}")
|
|
2108
|
+
console.print(f" {present_untrusted(update.get('body'), limit=400)}")
|
|
2109
|
+
if update.get("uuid"):
|
|
2110
|
+
console.print(f" [dim]{safe_text(update.get('uuid'))}[/dim]")
|
|
2111
|
+
|
|
2112
|
+
|
|
2076
2113
|
def _threaded(comments: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
2077
2114
|
"""Each root comment followed by its replies, in list order.
|
|
2078
2115
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dailybot-cli
|
|
3
|
-
Version: 3.
|
|
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
|
|
@@ -992,6 +992,8 @@ humans who own them.
|
|
|
992
992
|
| `dailybot project get <uuid>` | Show one project |
|
|
993
993
|
| `dailybot project updates [<project>]` | Batched update digest, or one project's feed |
|
|
994
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>` |
|
|
995
997
|
| `dailybot project milestones [<uuid>]` | List milestones |
|
|
996
998
|
| `dailybot project milestone-complete <p> <m>` | Complete a milestone. **Its open tasks stay open** |
|
|
997
999
|
| `dailybot project milestone-reopen <p> <m>` | Reopen a milestone |
|
|
@@ -1001,6 +1003,7 @@ humans who own them.
|
|
|
1001
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** |
|
|
1002
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** |
|
|
1003
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 |
|
|
1004
1007
|
| `dailybot project archive <uuid>` | Archive a project |
|
|
1005
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) |
|
|
1006
1009
|
| `dailybot goal list` | List goals (`--include` is repeatable: `--include progress --include projects`) |
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "dailybot-cli"
|
|
7
|
-
version = "3.
|
|
7
|
+
version = "3.21.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|