dailybot-cli 3.2.2__tar.gz → 3.4.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 (50) hide show
  1. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/PKG-INFO +1 -1
  2. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/api_client.py +20 -2
  3. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/commands/checkin.py +1 -1
  4. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/commands/conversation.py +21 -0
  5. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/commands/form.py +53 -3
  6. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/commands/query_options.py +6 -1
  7. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/commands/status.py +12 -1
  8. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/config.py +18 -0
  9. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/display.py +4 -1
  10. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/main.py +10 -2
  11. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli.egg-info/PKG-INFO +1 -1
  12. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/pyproject.toml +1 -1
  13. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/LICENSE +0 -0
  14. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/README.md +0 -0
  15. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/__init__.py +0 -0
  16. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/commands/__init__.py +0 -0
  17. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/commands/agent.py +0 -0
  18. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/commands/ask.py +0 -0
  19. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/commands/auth.py +0 -0
  20. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/commands/authoring_helpers.py +0 -0
  21. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/commands/channels.py +0 -0
  22. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/commands/chat.py +0 -0
  23. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/commands/config.py +0 -0
  24. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/commands/hook.py +0 -0
  25. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/commands/identity.py +0 -0
  26. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/commands/interactive.py +0 -0
  27. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/commands/interactive_chat.py +0 -0
  28. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/commands/kudos.py +0 -0
  29. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/commands/public_api_helpers.py +0 -0
  30. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/commands/team.py +0 -0
  31. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/commands/uninstall.py +0 -0
  32. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/commands/update.py +0 -0
  33. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/commands/upgrade.py +0 -0
  34. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/commands/user.py +0 -0
  35. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/commands/user_scoped_actions.py +0 -0
  36. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/commands/version.py +0 -0
  37. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/commands/workflow.py +0 -0
  38. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/install_method.py +0 -0
  39. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/ledger.py +0 -0
  40. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/tui/__init__.py +0 -0
  41. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/tui/app.py +0 -0
  42. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/tui/commands.py +0 -0
  43. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/tui/conversation.py +0 -0
  44. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli/tui/intents.py +0 -0
  45. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli.egg-info/SOURCES.txt +0 -0
  46. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli.egg-info/dependency_links.txt +0 -0
  47. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli.egg-info/entry_points.txt +0 -0
  48. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli.egg-info/requires.txt +0 -0
  49. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/dailybot_cli.egg-info/top_level.txt +0 -0
  50. {dailybot_cli-3.2.2 → dailybot_cli-3.4.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dailybot-cli
3
- Version: 3.2.2
3
+ Version: 3.4.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
@@ -19,6 +19,7 @@ DEFAULT_RETRY_AFTER_SECS: float = 1.0 # backoff floor when a 429 omits Retry-Af
19
19
  FREE_PLAN_DAILY_LIMIT_CODE: str = "free_plan_daily_limit_exceeded"
20
20
 
21
21
  MAX_FALLBACK_DETAIL_CHARS: int = 160 # cap for a non-JSON error body echoed to the user
22
+ MAX_SEARCH_QUERY_LENGTH: int = 256 # server rejects search queries longer than this
22
23
 
23
24
 
24
25
  def _fallback_detail(response: httpx.Response) -> str:
@@ -67,8 +68,20 @@ def _merge_list_query(
67
68
  start_date: str | None = None,
68
69
  end_date: str | None = None,
69
70
  ) -> dict[str, Any]:
70
- """Merge the shared list query params (search / date range) into ``params``."""
71
+ """Merge the shared list query params (search / date range) into ``params``.
72
+
73
+ Raises :class:`APIError` with ``search_query_too_long`` if *search* exceeds
74
+ :data:`MAX_SEARCH_QUERY_LENGTH` — matching the server-side validation so the
75
+ user gets instant feedback instead of a round-trip 400.
76
+ """
71
77
  if search is not None:
78
+ normalized: str = " ".join(search.split())
79
+ if len(normalized) > MAX_SEARCH_QUERY_LENGTH:
80
+ raise APIError(
81
+ 400,
82
+ "Search query is too long.",
83
+ code="search_query_too_long",
84
+ )
72
85
  params["search"] = search
73
86
  if start_date is not None:
74
87
  params["start_date"] = start_date
@@ -1571,7 +1584,12 @@ class DailyBotClient:
1571
1584
  )
1572
1585
  if response.status_code >= 400:
1573
1586
  self._handle_response(response)
1574
- return response.json()
1587
+ data: Any = response.json()
1588
+ if isinstance(data, dict) and "results" in data:
1589
+ return data["results"]
1590
+ if isinstance(data, list):
1591
+ return data
1592
+ return []
1575
1593
 
1576
1594
  def mark_agent_messages_read(
1577
1595
  self,
@@ -468,7 +468,7 @@ def checkin_create(
468
468
  """Create a check-in with a schedule, participants, questions, and config.
469
469
 
470
470
  \b
471
- Creating check-ins is role-gated server-side (admins/managers). A check-in must
471
+ Creating check-ins is role-gated server-side. A check-in must
472
472
  have at least one question at create time — seed them with --questions-file or
473
473
  --interactive (add/edit/remove more later with `dailybot checkin questions`). The
474
474
  scheduling/behavior flags (frequency, reminders, timezone mode, submission rules,
@@ -26,6 +26,7 @@ from dailybot_cli.display import (
26
26
  )
27
27
 
28
28
  GROUP_CHAT_TYPE: str = "group_chat"
29
+ MAX_CONVERSATION_PARTICIPANTS: int = 7 # Slack MPIM limit: 8 total, minus the bot
29
30
 
30
31
 
31
32
  def _split_identifiers(values: tuple[str, ...]) -> list[str]:
@@ -67,6 +68,11 @@ def _exit_for_conversation_error(exc: APIError, json_mode: bool) -> NoReturn:
67
68
  message = "One or more users were not found or aren't active in your organization."
68
69
  elif code == "no_valid_users":
69
70
  message = "The participant list contains invalid user UUIDs."
71
+ elif code == "conversation_too_many_participants":
72
+ message = (
73
+ f"Too many participants. Slack group DMs support at most "
74
+ f"{MAX_CONVERSATION_PARTICIPANTS} users plus the Dailybot bot (8 total)."
75
+ )
70
76
  elif code == "conversation_can_not_be_opened":
71
77
  message = "Slack rejected the request. A participant may be deactivated on the Slack side."
72
78
  elif exc.status_code == 403:
@@ -147,6 +153,14 @@ def conversation_open(
147
153
  print_error("Provide at least one participant with --user (UUID, email, or name).")
148
154
  raise SystemExit(1)
149
155
 
156
+ if len(identifiers) > MAX_CONVERSATION_PARTICIPANTS:
157
+ print_error(
158
+ f"Too many participants ({len(identifiers)}). "
159
+ f"Slack group DMs support at most {MAX_CONVERSATION_PARTICIPANTS} "
160
+ "users plus the Dailybot bot (8 total)."
161
+ )
162
+ raise SystemExit(1)
163
+
150
164
  client: DailyBotClient = require_auth()
151
165
  try:
152
166
  participants: list[tuple[str, str]] = _resolve_participants(client, identifiers)
@@ -155,6 +169,13 @@ def conversation_open(
155
169
  raise SystemExit(1)
156
170
 
157
171
  uuids: list[str] = [uuid for uuid, _name in participants]
172
+ if len(uuids) > MAX_CONVERSATION_PARTICIPANTS:
173
+ print_error(
174
+ f"Too many participants ({len(uuids)}). "
175
+ f"Slack group DMs support at most {MAX_CONVERSATION_PARTICIPANTS} "
176
+ "users plus the Dailybot bot (8 total)."
177
+ )
178
+ raise SystemExit(1)
158
179
  try:
159
180
  with console.status("Opening group conversation..."):
160
181
  result: dict[str, Any] = client.open_conversation(uuids)
@@ -510,6 +510,47 @@ def form_update(
510
510
  print_form_response_state(result, form_data)
511
511
 
512
512
 
513
+ def _resolve_workflow_state(client: DailyBotClient, form_uuid: str, user_input: str) -> str:
514
+ """Resolve a user-provided state label or key to the canonical state key.
515
+
516
+ Fetches the form's workflow states and tries, in order:
517
+ 1. Exact key match (e.g. ``"done"`` matches key ``"done"``).
518
+ 2. Case-insensitive label match (e.g. ``"Done"`` matches label ``"Done"``
519
+ → key ``"done"``).
520
+ 3. Case-insensitive key match (e.g. ``"Done"`` matches key ``"done"``).
521
+ Falls back to the original input if no match, letting the API decide.
522
+ """
523
+ try:
524
+ form_data: dict[str, Any] = client.get_form(form_uuid)
525
+ except APIError:
526
+ return user_input
527
+
528
+ workflow: dict[str, Any] | None = form_data.get("workflow")
529
+ if not workflow or not workflow.get("enabled"):
530
+ return user_input
531
+
532
+ states: list[dict[str, Any]] = workflow.get("states", [])
533
+ if not states:
534
+ return user_input
535
+
536
+ for state in states:
537
+ if state.get("key") == user_input:
538
+ return user_input
539
+
540
+ user_lower: str = user_input.lower()
541
+ for state in states:
542
+ label: str = state.get("label", "")
543
+ if label.lower() == user_lower:
544
+ return state["key"]
545
+
546
+ for state in states:
547
+ key: str = state.get("key", "")
548
+ if key.lower() == user_lower:
549
+ return key
550
+
551
+ return user_input
552
+
553
+
513
554
  @form.command("transition")
514
555
  @click.argument("form_uuid")
515
556
  @click.argument("response_uuid")
@@ -527,6 +568,10 @@ def form_transition(
527
568
  ) -> None:
528
569
  """Advance a response to a new workflow state.
529
570
 
571
+ \b
572
+ Accepts either the state key (e.g. 'done') or its human label (e.g. 'Done')
573
+ — the CLI resolves labels to keys automatically (case-insensitive).
574
+
530
575
  \b
531
576
  The form's state_change_permission audience is the sole gate — there is no
532
577
  response-author short-circuit. If you're not in the audience the API will
@@ -535,15 +580,20 @@ def form_transition(
535
580
  \b
536
581
  Examples:
537
582
  dailybot form transition <form_uuid> <response_uuid> qa --note "QA assigned"
583
+ dailybot form transition <form_uuid> <response_uuid> Done
538
584
  dailybot form transition <form_uuid> <response_uuid> released --json
539
585
  """
540
586
  client = require_auth()
541
587
 
588
+ resolved_state: str = _resolve_workflow_state(client, form_uuid, to_state)
589
+
542
590
  summary_lines: list[str] = [
543
591
  f"Form UUID: {form_uuid}",
544
592
  f"Response UUID: {response_uuid}",
545
- f"To state: {to_state}",
593
+ f"To state: {resolved_state}",
546
594
  ]
595
+ if resolved_state != to_state:
596
+ summary_lines.append(f" (resolved from label '{to_state}')")
547
597
  if note:
548
598
  summary_lines.append(f"Note: {note}")
549
599
  confirm_write(summary_lines, assume_yes)
@@ -553,7 +603,7 @@ def form_transition(
553
603
  result: dict[str, Any] = client.transition_form_response(
554
604
  form_uuid=form_uuid,
555
605
  response_uuid=response_uuid,
556
- to_state=to_state,
606
+ to_state=resolved_state,
557
607
  note=note,
558
608
  )
559
609
  except APIError as exc:
@@ -563,7 +613,7 @@ def form_transition(
563
613
  emit_json(result)
564
614
  return
565
615
 
566
- print_success(f"Response {response_uuid} transitioned to '{to_state}'.")
616
+ print_success(f"Response {response_uuid} transitioned to '{resolved_state}'.")
567
617
  form_data: dict[str, Any] | None = _maybe_load_form(client, form_uuid)
568
618
  print_form_response_state(result, form_data)
569
619
 
@@ -83,7 +83,12 @@ def build_query_params(
83
83
  params: dict[str, Any] = {}
84
84
 
85
85
  if search is not None:
86
- params["search"] = search[:MAX_SEARCH_LEN]
86
+ normalized: str = " ".join(search.split())
87
+ if len(normalized) > MAX_SEARCH_LEN:
88
+ raise ValueError(
89
+ f"Search query is too long ({len(normalized)} chars, max {MAX_SEARCH_LEN})."
90
+ )
91
+ params["search"] = search
87
92
 
88
93
  start: str | None = None
89
94
  end: str | None = None
@@ -1,5 +1,6 @@
1
1
  """Status command for Dailybot CLI."""
2
2
 
3
+ import json as json_mod
3
4
  from typing import Any
4
5
 
5
6
  import click
@@ -58,12 +59,19 @@ def _check_auth() -> None:
58
59
 
59
60
  @click.command()
60
61
  @click.option("--auth", is_flag=True, default=False, help="Check authentication status.")
61
- def status(auth: bool) -> None:
62
+ @click.option(
63
+ "--json", "json_mode", is_flag=True, default=False, help="Emit machine-readable JSON to stdout."
64
+ )
65
+ def status(auth: bool, json_mode: bool) -> None:
62
66
  """Show pending check-ins for today.
63
67
 
64
68
  \b
65
69
  Use --auth to verify your credentials are valid:
66
70
  dailybot status --auth
71
+
72
+ \b
73
+ Use --json for machine-readable output:
74
+ dailybot status --json
67
75
  """
68
76
  if auth:
69
77
  _check_auth()
@@ -77,6 +85,9 @@ def status(auth: bool) -> None:
77
85
  try:
78
86
  with console.status("Fetching pending check-ins..."):
79
87
  data: dict[str, Any] = client.get_status()
88
+ if json_mode:
89
+ click.echo(json_mod.dumps(data))
90
+ return
80
91
  checkins: list[dict[str, Any]] = data.get("pending_checkins", [])
81
92
  print_pending_checkins(checkins)
82
93
  except APIError as e:
@@ -6,7 +6,9 @@ from pathlib import Path
6
6
  from typing import Any
7
7
 
8
8
  DEFAULT_API_URL: str = "https://api.dailybot.com"
9
+ DEFAULT_APP_URL: str = "https://app.dailybot.com"
9
10
  _api_url_override: str | None = None
11
+ _app_url_override: str | None = None
10
12
 
11
13
 
12
14
  def set_api_url_override(url: str) -> None:
@@ -15,6 +17,12 @@ def set_api_url_override(url: str) -> None:
15
17
  _api_url_override = url.rstrip("/")
16
18
 
17
19
 
20
+ def set_app_url_override(url: str) -> None:
21
+ """Set a CLI-level webapp URL override (from --app-url flag)."""
22
+ global _app_url_override
23
+ _app_url_override = url.rstrip("/")
24
+
25
+
18
26
  CONFIG_DIR: Path = Path.home() / ".config" / "dailybot"
19
27
  CREDENTIALS_FILE: Path = CONFIG_DIR / "credentials.json"
20
28
  CONFIG_FILE: Path = CONFIG_DIR / "config.json"
@@ -108,6 +116,16 @@ def get_api_url() -> str:
108
116
  return DEFAULT_API_URL
109
117
 
110
118
 
119
+ def get_app_url() -> str:
120
+ """Return the webapp URL (--app-url flag > env var > default)."""
121
+ if _app_url_override:
122
+ return _app_url_override
123
+ env_url: str | None = os.environ.get("DAILYBOT_APP_URL")
124
+ if env_url:
125
+ return env_url.rstrip("/")
126
+ return DEFAULT_APP_URL
127
+
128
+
111
129
  def get_token() -> str | None:
112
130
  """Return the stored auth token, or the DAILYBOT_CLI_TOKEN env var."""
113
131
  env_token: str | None = os.environ.get("DAILYBOT_CLI_TOKEN")
@@ -327,7 +327,7 @@ def print_webhook_result(data: dict[str, Any]) -> None:
327
327
  console.print(Panel(table, title="[bold]Webhook Registered[/bold]", border_style="green"))
328
328
 
329
329
 
330
- def print_agent_messages(messages: list[dict[str, Any]]) -> None:
330
+ def print_agent_messages(messages: list[Any]) -> None:
331
331
  """Display a list of agent messages."""
332
332
  if not messages:
333
333
  print_info("No messages found.")
@@ -339,6 +339,9 @@ def print_agent_messages(messages: list[dict[str, Any]]) -> None:
339
339
  table.add_column("Delivered")
340
340
  table.add_column("Created", style="dim")
341
341
  for msg in messages:
342
+ if not isinstance(msg, dict):
343
+ table.add_row("text", "", str(msg), "", "")
344
+ continue
342
345
  delivered: bool = msg.get("delivered", False)
343
346
  delivered_text: str = "[green]yes[/green]" if delivered else "[yellow]no[/yellow]"
344
347
  sender_type: str = msg.get("sender_type", "")
@@ -27,7 +27,7 @@ from dailybot_cli.commands.upgrade import upgrade
27
27
  from dailybot_cli.commands.user import user
28
28
  from dailybot_cli.commands.version import version
29
29
  from dailybot_cli.commands.workflow import workflow
30
- from dailybot_cli.config import set_api_url_override
30
+ from dailybot_cli.config import set_api_url_override, set_app_url_override
31
31
 
32
32
  # Format used by `dailybot --version`. Single line so it's friendly to scripts
33
33
  # parsing the output. The richer multi-line panel lives in `dailybot version`.
@@ -42,8 +42,14 @@ _VERSION_MESSAGE: str = f"dailybot {__version__} (Python {platform.python_versio
42
42
  envvar="DAILYBOT_API_URL",
43
43
  help="Override the API base URL (e.g. staging).",
44
44
  )
45
+ @click.option(
46
+ "--app-url",
47
+ default=None,
48
+ envvar="DAILYBOT_APP_URL",
49
+ help="Override the webapp/dashboard base URL (default: https://app.dailybot.com).",
50
+ )
45
51
  @click.pass_context
46
- def cli(ctx: click.Context, api_url: str | None) -> None:
52
+ def cli(ctx: click.Context, api_url: str | None, app_url: str | None) -> None:
47
53
  """Dailybot CLI - The command-line bridge between humans and agents.
48
54
 
49
55
  \b
@@ -68,6 +74,8 @@ def cli(ctx: click.Context, api_url: str | None) -> None:
68
74
  """
69
75
  if api_url:
70
76
  set_api_url_override(api_url)
77
+ if app_url:
78
+ set_app_url_override(app_url)
71
79
  if ctx.invoked_subcommand is None:
72
80
  run_interactive()
73
81
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dailybot-cli
3
- Version: 3.2.2
3
+ Version: 3.4.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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dailybot-cli"
7
- version = "3.2.2"
7
+ version = "3.4.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