dailybot-cli 3.22.1__tar.gz → 3.23.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.22.1 → dailybot_cli-3.23.0}/PKG-INFO +3 -1
  2. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/README.md +2 -0
  3. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/api_client.py +44 -0
  4. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/project.py +131 -2
  5. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/task.py +53 -0
  6. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/display.py +63 -0
  7. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli.egg-info/PKG-INFO +3 -1
  8. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/pyproject.toml +1 -1
  9. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/LICENSE +0 -0
  10. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/__init__.py +0 -0
  11. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/__init__.py +0 -0
  12. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/_attachments.py +0 -0
  13. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/_beta.py +0 -0
  14. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/_briefing.py +0 -0
  15. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/_destructive.py +0 -0
  16. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/_favorites.py +0 -0
  17. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/_rollups.py +0 -0
  18. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/_writes.py +0 -0
  19. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/agent.py +0 -0
  20. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/ask.py +0 -0
  21. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/auth.py +0 -0
  22. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/authoring_helpers.py +0 -0
  23. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/board.py +0 -0
  24. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/channels.py +0 -0
  25. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/chat.py +0 -0
  26. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/checkin.py +0 -0
  27. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/config.py +0 -0
  28. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/conversation.py +0 -0
  29. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/env.py +0 -0
  30. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/featured.py +0 -0
  31. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/form.py +0 -0
  32. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/goal.py +0 -0
  33. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/hook.py +0 -0
  34. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/identity.py +0 -0
  35. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/interactive.py +0 -0
  36. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/interactive_chat.py +0 -0
  37. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/kudos.py +0 -0
  38. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/label.py +0 -0
  39. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/public_api_helpers.py +0 -0
  40. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/query_options.py +0 -0
  41. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/status.py +0 -0
  42. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/tasks.py +0 -0
  43. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/team.py +0 -0
  44. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/uninstall.py +0 -0
  45. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/update.py +0 -0
  46. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/upgrade.py +0 -0
  47. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/user.py +0 -0
  48. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/user_scoped_actions.py +0 -0
  49. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/version.py +0 -0
  50. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/commands/workflow.py +0 -0
  51. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/config.py +0 -0
  52. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/install_method.py +0 -0
  53. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/ledger.py +0 -0
  54. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/main.py +0 -0
  55. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/tui/__init__.py +0 -0
  56. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/tui/app.py +0 -0
  57. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/tui/commands.py +0 -0
  58. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/tui/conversation.py +0 -0
  59. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli/tui/intents.py +0 -0
  60. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli.egg-info/SOURCES.txt +0 -0
  61. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli.egg-info/dependency_links.txt +0 -0
  62. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli.egg-info/entry_points.txt +0 -0
  63. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli.egg-info/requires.txt +0 -0
  64. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/dailybot_cli.egg-info/top_level.txt +0 -0
  65. {dailybot_cli-3.22.1 → dailybot_cli-3.23.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dailybot-cli
3
- Version: 3.22.1
3
+ Version: 3.23.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
@@ -991,6 +991,7 @@ humans who own them.
991
991
  | `dailybot task comment <uuid> <body>` | Comment (`-` reads the body from stdin) |
992
992
  | `dailybot task comments <uuid>` | List a task's comments |
993
993
  | `dailybot task comment-react\|comment-unreact <task> <comment> <emoji>` | React to a comment with one emoji (👍, 🚀…), or remove your reaction — **needs a person: `dailybot login` or a personal API key** |
994
+ | `dailybot task comment-reactions <task> <comment> [--emoji 👍]` | Everyone who reacted to a comment, oldest first, with the agent that reacted for them (a comment itself shows the first 10 per emoji and the true count) |
994
995
  | `dailybot task link <a> <b> --type <rel>` | Relate two tasks (`blocks`, `relates_to`, `duplicates`) |
995
996
  | `dailybot task labels <uuid> --mode add\|remove\|replace` | Change a task's labels |
996
997
  | `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 |
@@ -1033,6 +1034,7 @@ humans who own them.
1033
1034
  | `dailybot project update-post <uuid> <body>` | **Post a project update** — how the team sees what was done (`--health`; sends an idempotency key) |
1034
1035
  | `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`) |
1035
1036
  | `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>` |
1037
+ | `dailybot project update-react\|update-unreact <project> <update> <emoji>` · `update-reactions <project> <update> [--emoji 👍]` | React to a project update with one emoji or remove your reaction — **needs a person: `dailybot login` or a personal API key**; list everyone who reacted |
1036
1038
  | `dailybot project milestones [<uuid>]` | List milestones |
1037
1039
  | `dailybot project milestone-complete <p> <m>` | Complete a milestone. **Its open tasks stay open** |
1038
1040
  | `dailybot project milestone-reopen <p> <m>` | Reopen a milestone |
@@ -952,6 +952,7 @@ humans who own them.
952
952
  | `dailybot task comment <uuid> <body>` | Comment (`-` reads the body from stdin) |
953
953
  | `dailybot task comments <uuid>` | List a task's comments |
954
954
  | `dailybot task comment-react\|comment-unreact <task> <comment> <emoji>` | React to a comment with one emoji (👍, 🚀…), or remove your reaction — **needs a person: `dailybot login` or a personal API key** |
955
+ | `dailybot task comment-reactions <task> <comment> [--emoji 👍]` | Everyone who reacted to a comment, oldest first, with the agent that reacted for them (a comment itself shows the first 10 per emoji and the true count) |
955
956
  | `dailybot task link <a> <b> --type <rel>` | Relate two tasks (`blocks`, `relates_to`, `duplicates`) |
956
957
  | `dailybot task labels <uuid> --mode add\|remove\|replace` | Change a task's labels |
957
958
  | `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 |
@@ -994,6 +995,7 @@ humans who own them.
994
995
  | `dailybot project update-post <uuid> <body>` | **Post a project update** — how the team sees what was done (`--health`; sends an idempotency key) |
995
996
  | `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
997
  | `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>` |
998
+ | `dailybot project update-react\|update-unreact <project> <update> <emoji>` · `update-reactions <project> <update> [--emoji 👍]` | React to a project update with one emoji or remove your reaction — **needs a person: `dailybot login` or a personal API key**; list everyone who reacted |
997
999
  | `dailybot project milestones [<uuid>]` | List milestones |
998
1000
  | `dailybot project milestone-complete <p> <m>` | Complete a milestone. **Its open tasks stay open** |
999
1001
  | `dailybot project milestone-reopen <p> <m>` | Reopen a milestone |
@@ -406,6 +406,11 @@ def _checked_emoji(value: str) -> str:
406
406
  return value
407
407
 
408
408
 
409
+ def _reaction_filter(emoji: str | None) -> dict[str, Any] | None:
410
+ """The `?emoji=` filter of a reactions list, or no filter (every emoji)."""
411
+ return {"emoji": _checked_emoji(emoji)} if emoji else None
412
+
413
+
409
414
  def _path_segment(value: Any) -> str:
410
415
  """One validated path segment: a key, a uuid or a slug — never `/`, `..`, `?`, `#`."""
411
416
  text: str = str(value)
@@ -2756,6 +2761,45 @@ class DailyBotClient:
2756
2761
  target: str = f"{base}{quote(_checked_emoji(emoji), safe='')}/"
2757
2762
  return self._tasks_write("DELETE", "", url=target)
2758
2763
 
2764
+ def list_comment_reactions(
2765
+ self, task_uuid: str, comment_uuid: str, *, emoji: str | None = None, **page: Any
2766
+ ) -> PaginatedResult:
2767
+ """GET …/comments/<c>/reactions/ — everyone who reacted, oldest first."""
2768
+ return self._tasks_list(
2769
+ f"tasks/{_path_segment(task_uuid)}/comments/{_path_segment(comment_uuid)}/reactions/",
2770
+ params=_reaction_filter(emoji),
2771
+ **page,
2772
+ )
2773
+
2774
+ def add_update_reaction(self, project_uuid: str, update_uuid: str, emoji: str) -> Any:
2775
+ """POST …/updates/<u>/reactions/ — idempotent; answers with the whole update."""
2776
+ return self._tasks_write(
2777
+ "POST",
2778
+ f"projects/{_path_segment(project_uuid)}/updates/{_path_segment(update_uuid)}"
2779
+ "/reactions/",
2780
+ json={"emoji": _checked_emoji(emoji)},
2781
+ )
2782
+
2783
+ def remove_update_reaction(self, project_uuid: str, update_uuid: str, emoji: str) -> Any:
2784
+ """DELETE …/updates/<u>/reactions/<emoji>/ — the emoji travels percent-encoded."""
2785
+ base: str = self._tasks_url(
2786
+ f"projects/{_path_segment(project_uuid)}/updates/{_path_segment(update_uuid)}"
2787
+ "/reactions/"
2788
+ )
2789
+ target: str = f"{base}{quote(_checked_emoji(emoji), safe='')}/"
2790
+ return self._tasks_write("DELETE", "", url=target)
2791
+
2792
+ def list_update_reactions(
2793
+ self, project_uuid: str, update_uuid: str, *, emoji: str | None = None, **page: Any
2794
+ ) -> PaginatedResult:
2795
+ """GET …/updates/<u>/reactions/ — everyone who reacted, oldest first."""
2796
+ return self._tasks_list(
2797
+ f"projects/{_path_segment(project_uuid)}/updates/{_path_segment(update_uuid)}"
2798
+ "/reactions/",
2799
+ params=_reaction_filter(emoji),
2800
+ **page,
2801
+ )
2802
+
2759
2803
  def get_board(self, board_uuid: str) -> dict[str, Any]:
2760
2804
  """GET /v1/tasks/boards/<uuid>/."""
2761
2805
  return self._tasks_read(f"boards/{_path_segment(board_uuid)}/")
@@ -6,7 +6,12 @@ from typing import Any
6
6
 
7
7
  import click
8
8
 
9
- from dailybot_cli.api_client import ATTACHMENT_MULTIPART_MAX_BYTES, APIError, PaginatedResult
9
+ from dailybot_cli.api_client import (
10
+ ATTACHMENT_MULTIPART_MAX_BYTES,
11
+ APIError,
12
+ PaginatedResult,
13
+ is_reaction_emoji,
14
+ )
10
15
  from dailybot_cli.commands._attachments import (
11
16
  run_attach,
12
17
  run_delete,
@@ -25,7 +30,13 @@ from dailybot_cli.commands.public_api_helpers import (
25
30
  require_auth,
26
31
  rows_of,
27
32
  )
28
- from dailybot_cli.commands.query_options import build_query_params, query_options, resolve_fetch_all
33
+ from dailybot_cli.commands.query_options import (
34
+ PAGING_ONLY_MORE_HINT,
35
+ build_query_params,
36
+ paging_options,
37
+ query_options,
38
+ resolve_fetch_all,
39
+ )
29
40
  from dailybot_cli.display import (
30
41
  console,
31
42
  print_info,
@@ -34,6 +45,7 @@ from dailybot_cli.display import (
34
45
  print_project_updates,
35
46
  print_projects_table,
36
47
  print_raw_value,
48
+ print_reaction_list,
37
49
  print_success,
38
50
  print_tasks_detail_panel,
39
51
  print_tasks_rows,
@@ -1331,6 +1343,123 @@ def project_update_edit(
1331
1343
  report_write(data, "Project update edited")
1332
1344
 
1333
1345
 
1346
+ _NOT_ONE_EMOJI: str = "A reaction must be one emoji, e.g. 👍 (no text or :shortcodes:)."
1347
+
1348
+
1349
+ @project.command("update-react")
1350
+ @click.argument("project_uuid", metavar="PROJECT")
1351
+ @click.argument("update_uuid", metavar="UPDATE")
1352
+ @click.argument("emoji")
1353
+ @click.option("--json", "json_mode", is_flag=True, help="Emit machine-readable JSON to stdout.")
1354
+ def project_update_react(project_uuid: str, update_uuid: str, emoji: str, json_mode: bool) -> None:
1355
+ """React to a project update with one emoji. Needs a person: `dailybot login` or a personal API key.
1356
+
1357
+ \b
1358
+ Emoji only (no text or :shortcodes:). Reacting twice with the same emoji is
1359
+ safe: nothing changes.
1360
+
1361
+ \b
1362
+ Examples:
1363
+ dailybot project update-react <project-uuid> <update-uuid> 👍
1364
+ dailybot project update-react <project-uuid> <update-uuid> 🚀 --json
1365
+ """
1366
+ if not is_reaction_emoji(emoji):
1367
+ raise click.UsageError(_NOT_ONE_EMOJI)
1368
+ client = require_auth()
1369
+ try:
1370
+ with console.status("Reacting..."):
1371
+ data: Any = client.add_update_reaction(project_uuid, update_uuid, emoji)
1372
+ except APIError as exc:
1373
+ exit_for_tasks_error(exc, json_mode)
1374
+ if json_mode:
1375
+ emit_json(data)
1376
+ return
1377
+ print_success(f"Reacted {emoji}.")
1378
+
1379
+
1380
+ @project.command("update-unreact")
1381
+ @click.argument("project_uuid", metavar="PROJECT")
1382
+ @click.argument("update_uuid", metavar="UPDATE")
1383
+ @click.argument("emoji")
1384
+ @click.option("--json", "json_mode", is_flag=True, help="Emit machine-readable JSON to stdout.")
1385
+ def project_update_unreact(
1386
+ project_uuid: str, update_uuid: str, emoji: str, json_mode: bool
1387
+ ) -> None:
1388
+ """Remove your emoji reaction from a project update. Needs a person: `dailybot login` or a personal API key.
1389
+
1390
+ \b
1391
+ Safe to repeat: removing a reaction you did not leave changes nothing.
1392
+
1393
+ \b
1394
+ Examples:
1395
+ dailybot project update-unreact <project-uuid> <update-uuid> 👍
1396
+ """
1397
+ if not is_reaction_emoji(emoji):
1398
+ raise click.UsageError(_NOT_ONE_EMOJI)
1399
+ client = require_auth()
1400
+ try:
1401
+ with console.status("Removing the reaction..."):
1402
+ client.remove_update_reaction(project_uuid, update_uuid, emoji)
1403
+ except APIError as exc:
1404
+ exit_for_tasks_error(exc, json_mode)
1405
+ if json_mode:
1406
+ emit_json({"removed": True, "project": project_uuid, "update": update_uuid, "emoji": emoji})
1407
+ return
1408
+ print_success(f"Removed {emoji}.")
1409
+
1410
+
1411
+ @project.command("update-reactions")
1412
+ @click.argument("project_uuid", metavar="PROJECT")
1413
+ @click.argument("update_uuid", metavar="UPDATE")
1414
+ @click.option("--emoji", default=None, help="Only this emoji (all emojis when omitted).")
1415
+ @click.option("--all", "-a", "fetch_all", is_flag=True, help="Fetch every page.")
1416
+ @paging_options
1417
+ @click.option("--json", "json_mode", is_flag=True, help="Emit machine-readable JSON to stdout.")
1418
+ def project_update_reactions(
1419
+ project_uuid: str, update_uuid: str, emoji: str | None, json_mode: bool, **flags: Any
1420
+ ) -> None:
1421
+ """Everyone who reacted to a project update, oldest first, with the agent that reacted for them.
1422
+
1423
+ \b
1424
+ An update itself carries at most the first few reactors per emoji; this lists
1425
+ them all.
1426
+
1427
+ \b
1428
+ Examples:
1429
+ dailybot project update-reactions <project-uuid> <update-uuid>
1430
+ dailybot project update-reactions <project-uuid> <update-uuid> --emoji 👍 --json
1431
+ """
1432
+ if emoji is not None and not is_reaction_emoji(emoji):
1433
+ raise click.UsageError("--emoji must be one emoji, e.g. 👍 (no text or :shortcodes:).")
1434
+ client = require_auth()
1435
+ try:
1436
+ spec = build_query_params(**flags)
1437
+ with console.status("Reading reactions..."):
1438
+ result: PaginatedResult = client.list_update_reactions(
1439
+ project_uuid,
1440
+ update_uuid,
1441
+ emoji=emoji,
1442
+ page=spec.page,
1443
+ page_size=spec.page_size,
1444
+ fetch_all=spec.fetch_all,
1445
+ limit=spec.limit,
1446
+ )
1447
+ except ValueError as exc:
1448
+ raise click.BadParameter(str(exc)) from exc
1449
+ except APIError as exc:
1450
+ exit_for_tasks_error(exc, json_mode)
1451
+ if json_mode:
1452
+ emit_json(_envelope(result))
1453
+ return
1454
+ print_reaction_list(result.results)
1455
+ print_pagination_footer(
1456
+ len(result.results),
1457
+ result.count,
1458
+ has_more=bool(result.next),
1459
+ more_hint=PAGING_ONLY_MORE_HINT,
1460
+ )
1461
+
1462
+
1334
1463
  @project.command("update-delete")
1335
1464
  @click.argument("project_uuid", metavar="PROJECT")
1336
1465
  @click.argument("update_uuid", metavar="UPDATE")
@@ -67,6 +67,7 @@ from dailybot_cli.display import (
67
67
  print_deprecation,
68
68
  print_error,
69
69
  print_pagination_footer,
70
+ print_reaction_list,
70
71
  print_success,
71
72
  print_task_briefing,
72
73
  print_task_comments,
@@ -1267,6 +1268,58 @@ def task_comment_unreact(task_uuid: str, comment_uuid: str, emoji: str, json_mod
1267
1268
  print_success(f"Removed {emoji}.")
1268
1269
 
1269
1270
 
1271
+ @task.command("comment-reactions")
1272
+ @click.argument("task_uuid", metavar="TASK")
1273
+ @click.argument("comment_uuid", metavar="COMMENT")
1274
+ @click.option("--emoji", default=None, help="Only this emoji (all emojis when omitted).")
1275
+ @click.option("--all", "-a", "fetch_all", is_flag=True, help="Fetch every page.")
1276
+ @paging_options
1277
+ @click.option("--json", "json_mode", is_flag=True, help="Emit machine-readable JSON to stdout.")
1278
+ def task_comment_reactions(
1279
+ task_uuid: str, comment_uuid: str, emoji: str | None, json_mode: bool, **flags: Any
1280
+ ) -> None:
1281
+ """Everyone who reacted to a comment, oldest first, with the agent that reacted for them.
1282
+
1283
+ \b
1284
+ A comment itself carries at most the first few reactors per emoji; this lists
1285
+ them all.
1286
+
1287
+ \b
1288
+ Examples:
1289
+ dailybot task comment-reactions ENG-142 <comment-uuid>
1290
+ dailybot task comment-reactions ENG-142 <comment-uuid> --emoji 👍 --json
1291
+ """
1292
+ if emoji is not None and not is_reaction_emoji(emoji):
1293
+ raise click.UsageError("--emoji must be one emoji, e.g. 👍 (no text or :shortcodes:).")
1294
+ client = require_auth()
1295
+ try:
1296
+ spec = build_query_params(**flags)
1297
+ with console.status("Reading reactions..."):
1298
+ result: PaginatedResult = client.list_comment_reactions(
1299
+ task_uuid,
1300
+ comment_uuid,
1301
+ emoji=emoji,
1302
+ page=spec.page,
1303
+ page_size=spec.page_size,
1304
+ fetch_all=spec.fetch_all,
1305
+ limit=spec.limit,
1306
+ )
1307
+ except ValueError as exc:
1308
+ raise click.BadParameter(str(exc)) from exc
1309
+ except APIError as exc:
1310
+ exit_for_tasks_error(exc, json_mode)
1311
+ if json_mode:
1312
+ emit_json(_envelope(result))
1313
+ return
1314
+ print_reaction_list(result.results)
1315
+ print_pagination_footer(
1316
+ len(result.results),
1317
+ result.count,
1318
+ has_more=bool(result.next),
1319
+ more_hint=PAGING_ONLY_MORE_HINT,
1320
+ )
1321
+
1322
+
1270
1323
  @task.command("comment-delete")
1271
1324
  @click.argument("task_uuid", metavar="TASK")
1272
1325
  @click.argument("comment_uuid", metavar="COMMENT")
@@ -1623,6 +1623,63 @@ def _person_name(person: dict[str, Any]) -> Any:
1623
1623
  return person.get("full_name") or person.get("name")
1624
1624
 
1625
1625
 
1626
+ def _reactor(user: Any, agent: Any = None) -> str:
1627
+ """One person who reacted, with the agent that reacted for them when stamped."""
1628
+ name: Any = _person_name(user) if isinstance(user, dict) else user
1629
+ who: str = present_untrusted(name, limit=24)
1630
+ stamp: Any = (
1631
+ agent
1632
+ if agent is not None
1633
+ else (user.get("executed_by_agent") if isinstance(user, dict) else None)
1634
+ )
1635
+ if isinstance(stamp, dict) and stamp.get("name"):
1636
+ who += f" [dim]via {present_untrusted(stamp.get('name'), limit=24)}[/dim]"
1637
+ return who
1638
+
1639
+
1640
+ def format_reactions(reactions: Any) -> str:
1641
+ """One line for a comment's or update's reactions: emoji, true count, who reacted.
1642
+
1643
+ `users` holds at most the first few reactors; `count` is always the true total,
1644
+ so the list was cut exactly when `count > len(users)`. Names are user-authored
1645
+ and quoted as data. An older server sends no `users` at all: the count alone is shown.
1646
+ """
1647
+ if not isinstance(reactions, list):
1648
+ return ""
1649
+ parts: list[str] = []
1650
+ for entry in reactions:
1651
+ if not isinstance(entry, dict) or not entry.get("emoji"):
1652
+ continue
1653
+ count: Any = entry.get("count")
1654
+ users: Any = entry.get("users")
1655
+ part: str = (
1656
+ f"{safe_text(entry.get('emoji'))} {safe_text(count if count is not None else '')}"
1657
+ )
1658
+ if isinstance(users, list):
1659
+ names: list[str] = [_reactor(u) for u in users]
1660
+ if isinstance(count, int) and count > len(users):
1661
+ names.append(f"+{count - len(users)} more")
1662
+ if names:
1663
+ part += f" ({', '.join(names)})"
1664
+ if entry.get("reacted"):
1665
+ part += " [dim]you reacted[/dim]"
1666
+ parts.append(part.strip())
1667
+ return " · ".join(parts)
1668
+
1669
+
1670
+ def print_reaction_list(entries: list[dict[str, Any]]) -> None:
1671
+ """Render a full reactor list: emoji, the person (and agent), and when."""
1672
+ if not entries:
1673
+ print_info("No reactions.")
1674
+ return
1675
+ for entry in entries:
1676
+ who: str = _reactor(entry.get("user") or {}, entry.get("executed_by_agent"))
1677
+ console.print(
1678
+ f"{safe_text(entry.get('emoji') or '')} {who} "
1679
+ f"[dim]{safe_text(entry.get('created_at') or '')}[/dim]"
1680
+ )
1681
+
1682
+
1626
1683
  def _state_name(task: dict[str, Any]) -> str:
1627
1684
  state: Any = task.get("state")
1628
1685
  if isinstance(state, dict):
@@ -2106,6 +2163,9 @@ def print_project_updates(updates: list[dict[str, Any]]) -> None:
2106
2163
  meta.append(f"{count} file(s)")
2107
2164
  console.print(f"[dim]{' · '.join(m for m in meta if m)}[/dim] {who}")
2108
2165
  console.print(f" {present_untrusted(update.get('body'), limit=400)}")
2166
+ reacted: str = format_reactions(update.get("reactions"))
2167
+ if reacted:
2168
+ console.print(f" {reacted}")
2109
2169
  if update.get("uuid"):
2110
2170
  console.print(f" [dim]{safe_text(update.get('uuid'))}[/dim]")
2111
2171
 
@@ -2160,3 +2220,6 @@ def print_task_comments(comments: list[dict[str, Any]]) -> None:
2160
2220
  # A reply names its thread's root in `parent_comment`; `_threaded` put it there.
2161
2221
  thread: str = " ↳ " if comment.get("parent_comment") else ""
2162
2222
  console.print(f"{thread}{attribution}: {present_untrusted(comment.get('body'), limit=400)}")
2223
+ reacted: str = format_reactions(comment.get("reactions"))
2224
+ if reacted:
2225
+ console.print(f"{' ' if thread else ' '}{reacted}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dailybot-cli
3
- Version: 3.22.1
3
+ Version: 3.23.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
@@ -991,6 +991,7 @@ humans who own them.
991
991
  | `dailybot task comment <uuid> <body>` | Comment (`-` reads the body from stdin) |
992
992
  | `dailybot task comments <uuid>` | List a task's comments |
993
993
  | `dailybot task comment-react\|comment-unreact <task> <comment> <emoji>` | React to a comment with one emoji (👍, 🚀…), or remove your reaction — **needs a person: `dailybot login` or a personal API key** |
994
+ | `dailybot task comment-reactions <task> <comment> [--emoji 👍]` | Everyone who reacted to a comment, oldest first, with the agent that reacted for them (a comment itself shows the first 10 per emoji and the true count) |
994
995
  | `dailybot task link <a> <b> --type <rel>` | Relate two tasks (`blocks`, `relates_to`, `duplicates`) |
995
996
  | `dailybot task labels <uuid> --mode add\|remove\|replace` | Change a task's labels |
996
997
  | `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 |
@@ -1033,6 +1034,7 @@ humans who own them.
1033
1034
  | `dailybot project update-post <uuid> <body>` | **Post a project update** — how the team sees what was done (`--health`; sends an idempotency key) |
1034
1035
  | `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`) |
1035
1036
  | `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>` |
1037
+ | `dailybot project update-react\|update-unreact <project> <update> <emoji>` · `update-reactions <project> <update> [--emoji 👍]` | React to a project update with one emoji or remove your reaction — **needs a person: `dailybot login` or a personal API key**; list everyone who reacted |
1036
1038
  | `dailybot project milestones [<uuid>]` | List milestones |
1037
1039
  | `dailybot project milestone-complete <p> <m>` | Complete a milestone. **Its open tasks stay open** |
1038
1040
  | `dailybot project milestone-reopen <p> <m>` | Reopen a milestone |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dailybot-cli"
7
- version = "3.22.1"
7
+ version = "3.23.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