dailybot-cli 3.0.2__tar.gz → 3.1.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.0.2 → dailybot_cli-3.1.0}/PKG-INFO +8 -8
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/README.md +7 -7
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/api_client.py +70 -23
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/commands/checkin.py +10 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/commands/form.py +37 -4
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/commands/interactive.py +3 -3
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/commands/kudos.py +54 -16
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/commands/public_api_helpers.py +32 -2
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/commands/query_options.py +44 -1
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/commands/user_scoped_actions.py +60 -9
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/commands/workflow.py +4 -2
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/display.py +37 -21
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/tui/app.py +17 -19
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli.egg-info/PKG-INFO +8 -8
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/pyproject.toml +1 -1
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/LICENSE +0 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/__init__.py +0 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/commands/__init__.py +0 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/commands/agent.py +0 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/commands/ask.py +0 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/commands/auth.py +0 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/commands/authoring_helpers.py +0 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/commands/channels.py +0 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/commands/chat.py +0 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/commands/config.py +0 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/commands/hook.py +0 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/commands/identity.py +0 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/commands/interactive_chat.py +0 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/commands/status.py +0 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/commands/team.py +0 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/commands/uninstall.py +0 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/commands/update.py +0 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/commands/upgrade.py +0 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/commands/user.py +0 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/commands/version.py +0 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/config.py +0 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/install_method.py +0 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/ledger.py +0 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/main.py +0 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/tui/__init__.py +0 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/tui/commands.py +0 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/tui/conversation.py +0 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli/tui/intents.py +0 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli.egg-info/SOURCES.txt +0 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli.egg-info/dependency_links.txt +0 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli.egg-info/entry_points.txt +0 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli.egg-info/requires.txt +0 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/dailybot_cli.egg-info/top_level.txt +0 -0
- {dailybot_cli-3.0.2 → dailybot_cli-3.1.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dailybot-cli
|
|
3
|
-
Version: 3.0
|
|
3
|
+
Version: 3.1.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
|
|
@@ -489,8 +489,8 @@ dailybot kudos list --filter KUDOS_RECEIVED
|
|
|
489
489
|
dailybot kudos list --filter KUDOS_GIVEN --since 2026-01-01 --until 2026-06-30
|
|
490
490
|
dailybot kudos list --search "release" --all --json
|
|
491
491
|
|
|
492
|
-
#
|
|
493
|
-
dailybot kudos org --
|
|
492
|
+
# Every kudos in the organization (admin-only)
|
|
493
|
+
dailybot kudos org --page-size 20 --since 2026-07-01
|
|
494
494
|
|
|
495
495
|
# Leaderboard of top kudos recipients
|
|
496
496
|
dailybot kudos wall-of-fame --limit 10
|
|
@@ -499,9 +499,9 @@ dailybot kudos wall-of-fame --limit 10
|
|
|
499
499
|
`dailybot kudos list` accepts the shared list flags (pagination, `--search`, and
|
|
500
500
|
date filters) — see [Listing, search, and pagination](#listing-search-and-pagination).
|
|
501
501
|
|
|
502
|
-
> `dailybot kudos org` is **
|
|
503
|
-
>
|
|
504
|
-
>
|
|
502
|
+
> `dailybot kudos org` is **admin-only** and shows the whole organization's kudos
|
|
503
|
+
> feed, where `dailybot kudos list` shows only the ones you gave or received. It
|
|
504
|
+
> accepts the same list flags.
|
|
505
505
|
|
|
506
506
|
---
|
|
507
507
|
|
|
@@ -564,7 +564,7 @@ envelope, and list output prints a `Showing X of N` count footer.
|
|
|
564
564
|
| Flag | Description |
|
|
565
565
|
|------|-------------|
|
|
566
566
|
| `--page N` | Fetch a specific 1-based page. |
|
|
567
|
-
| `--page-size N` | Results per page (max
|
|
567
|
+
| `--page-size N` | Results per page (max 100). |
|
|
568
568
|
| `--all` | Fetch every page and merge the results. |
|
|
569
569
|
| `--limit N` | Stop after N results. |
|
|
570
570
|
| `--search TEXT` / `--grep TEXT` | Filter by a search term (max 256 chars, truncated client-side). |
|
|
@@ -892,7 +892,7 @@ Replies to agent emails land as messages retrievable via `dailybot agent message
|
|
|
892
892
|
|---------|-------------|
|
|
893
893
|
| `dailybot kudos give` | Give kudos to a user (`--to`), a team (`--team`), or both |
|
|
894
894
|
| `dailybot kudos list` | List kudos you've received or given (`--filter`, search, date flags) |
|
|
895
|
-
| `dailybot kudos org` |
|
|
895
|
+
| `dailybot kudos org` | Every kudos in the organization (**admin-only**) |
|
|
896
896
|
| `dailybot kudos wall-of-fame` | Leaderboard of top kudos recipients (`--limit`) |
|
|
897
897
|
|
|
898
898
|
### Team
|
|
@@ -450,8 +450,8 @@ dailybot kudos list --filter KUDOS_RECEIVED
|
|
|
450
450
|
dailybot kudos list --filter KUDOS_GIVEN --since 2026-01-01 --until 2026-06-30
|
|
451
451
|
dailybot kudos list --search "release" --all --json
|
|
452
452
|
|
|
453
|
-
#
|
|
454
|
-
dailybot kudos org --
|
|
453
|
+
# Every kudos in the organization (admin-only)
|
|
454
|
+
dailybot kudos org --page-size 20 --since 2026-07-01
|
|
455
455
|
|
|
456
456
|
# Leaderboard of top kudos recipients
|
|
457
457
|
dailybot kudos wall-of-fame --limit 10
|
|
@@ -460,9 +460,9 @@ dailybot kudos wall-of-fame --limit 10
|
|
|
460
460
|
`dailybot kudos list` accepts the shared list flags (pagination, `--search`, and
|
|
461
461
|
date filters) — see [Listing, search, and pagination](#listing-search-and-pagination).
|
|
462
462
|
|
|
463
|
-
> `dailybot kudos org` is **
|
|
464
|
-
>
|
|
465
|
-
>
|
|
463
|
+
> `dailybot kudos org` is **admin-only** and shows the whole organization's kudos
|
|
464
|
+
> feed, where `dailybot kudos list` shows only the ones you gave or received. It
|
|
465
|
+
> accepts the same list flags.
|
|
466
466
|
|
|
467
467
|
---
|
|
468
468
|
|
|
@@ -525,7 +525,7 @@ envelope, and list output prints a `Showing X of N` count footer.
|
|
|
525
525
|
| Flag | Description |
|
|
526
526
|
|------|-------------|
|
|
527
527
|
| `--page N` | Fetch a specific 1-based page. |
|
|
528
|
-
| `--page-size N` | Results per page (max
|
|
528
|
+
| `--page-size N` | Results per page (max 100). |
|
|
529
529
|
| `--all` | Fetch every page and merge the results. |
|
|
530
530
|
| `--limit N` | Stop after N results. |
|
|
531
531
|
| `--search TEXT` / `--grep TEXT` | Filter by a search term (max 256 chars, truncated client-side). |
|
|
@@ -853,7 +853,7 @@ Replies to agent emails land as messages retrievable via `dailybot agent message
|
|
|
853
853
|
|---------|-------------|
|
|
854
854
|
| `dailybot kudos give` | Give kudos to a user (`--to`), a team (`--team`), or both |
|
|
855
855
|
| `dailybot kudos list` | List kudos you've received or given (`--filter`, search, date flags) |
|
|
856
|
-
| `dailybot kudos org` |
|
|
856
|
+
| `dailybot kudos org` | Every kudos in the organization (**admin-only**) |
|
|
857
857
|
| `dailybot kudos wall-of-fame` | Leaderboard of top kudos recipients (`--limit`) |
|
|
858
858
|
|
|
859
859
|
### Team
|
|
@@ -10,14 +10,41 @@ import httpx
|
|
|
10
10
|
from dailybot_cli.config import get_api_key, get_api_url, get_token
|
|
11
11
|
|
|
12
12
|
_MAX_LIST_PAGES: int = 50 # safety cap for paginated list endpoints
|
|
13
|
-
DEFAULT_PAGE_SIZE: int =
|
|
14
|
-
MAX_PAGE_SIZE: int =
|
|
13
|
+
DEFAULT_PAGE_SIZE: int = 25 # server default page size for paginated list endpoints
|
|
14
|
+
MAX_PAGE_SIZE: int = 100 # server clamps above this; the client clamps too
|
|
15
15
|
|
|
16
16
|
MAX_RATE_LIMIT_RETRIES: int = 3 # attempts to retry a generic 429 before raising
|
|
17
17
|
DEFAULT_RETRY_AFTER_SECS: float = 1.0 # backoff floor when a 429 omits Retry-After
|
|
18
18
|
# A free-plan daily throttle is NOT transient — retrying can never succeed today.
|
|
19
19
|
FREE_PLAN_DAILY_LIMIT_CODE: str = "free_plan_daily_limit_exceeded"
|
|
20
20
|
|
|
21
|
+
MAX_FALLBACK_DETAIL_CHARS: int = 160 # cap for a non-JSON error body echoed to the user
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _fallback_detail(response: httpx.Response) -> str:
|
|
25
|
+
"""Build an error detail when the body is not the expected JSON envelope.
|
|
26
|
+
|
|
27
|
+
A 5xx can return a rendered HTML page. Echoing that verbatim floods the
|
|
28
|
+
terminal and leaks server internals (tracebacks, settings, file paths), so
|
|
29
|
+
only short non-HTML bodies are surfaced.
|
|
30
|
+
"""
|
|
31
|
+
content_type: str = response.headers.get("content-type", "")
|
|
32
|
+
text: str = (response.text or "").strip()
|
|
33
|
+
if not text or "html" in content_type.lower() or text.startswith("<"):
|
|
34
|
+
return f"HTTP {response.status_code}"
|
|
35
|
+
return text[:MAX_FALLBACK_DETAIL_CHARS]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def resource_uuid(payload: dict[str, Any]) -> str:
|
|
39
|
+
"""Return a resource's canonical identifier from an API payload.
|
|
40
|
+
|
|
41
|
+
The API is mid-migration from ``id`` to ``uuid``: forms and their responses
|
|
42
|
+
now expose only ``uuid``, agent resources expose both with the same value,
|
|
43
|
+
and check-ins / kudos / workflows still expose only ``id``. Reading through
|
|
44
|
+
this helper keeps every caller correct under all three shapes.
|
|
45
|
+
"""
|
|
46
|
+
return str(payload.get("uuid") or payload.get("id") or "")
|
|
47
|
+
|
|
21
48
|
|
|
22
49
|
@dataclass
|
|
23
50
|
class PaginatedResult:
|
|
@@ -151,8 +178,11 @@ class DailyBotClient:
|
|
|
151
178
|
if isinstance(upgrade_url, str):
|
|
152
179
|
extra["upgrade_url"] = upgrade_url
|
|
153
180
|
except Exception:
|
|
154
|
-
detail = response
|
|
155
|
-
|
|
181
|
+
detail = _fallback_detail(response)
|
|
182
|
+
# Only a 401 means the session is unusable. A 403 is an authorization
|
|
183
|
+
# verdict (wrong role, wrong plan) whose server detail explains the
|
|
184
|
+
# actual cause — overwriting it sends the user into a re-login loop.
|
|
185
|
+
if response.status_code == 401 and self._agent_auth_mode == "bearer":
|
|
156
186
|
detail = "Session expired. Run 'dailybot login' to re-authenticate."
|
|
157
187
|
retry_after: float | None = None
|
|
158
188
|
if response.status_code == 429:
|
|
@@ -216,23 +246,19 @@ class DailyBotClient:
|
|
|
216
246
|
page_size: int | None = None,
|
|
217
247
|
fetch_all: bool = False,
|
|
218
248
|
limit: int | None = None,
|
|
219
|
-
force_paginated: bool = False,
|
|
220
249
|
) -> PaginatedResult:
|
|
221
250
|
"""GET a list endpoint, tolerating both the DRF envelope and a bare array.
|
|
222
251
|
|
|
223
252
|
- ``page`` / ``page_size`` are sent when provided; ``page_size`` is clamped
|
|
224
253
|
to ``[1, MAX_PAGE_SIZE]``. ``limit`` is a legacy alias that also caps the
|
|
225
254
|
total number of collected items.
|
|
226
|
-
- ``force_paginated`` adds ``paginated=true`` to the query — required for the
|
|
227
|
-
two forms endpoints (``/v1/forms/`` and ``/v1/forms/<uuid>/responses/``)
|
|
228
|
-
that return a bare array unless asked for the envelope.
|
|
229
255
|
- ``fetch_all`` follows ``next`` (bounded by ``_MAX_LIST_PAGES``); otherwise a
|
|
230
|
-
single page is returned.
|
|
231
|
-
|
|
256
|
+
single page is returned.
|
|
257
|
+
- Every ``/v1`` list endpoint now returns the envelope unconditionally. The
|
|
258
|
+
bare-array branch below is kept only so an older deployment degrades to a
|
|
259
|
+
single page instead of raising.
|
|
232
260
|
"""
|
|
233
261
|
query: dict[str, Any] = dict(params) if params else {}
|
|
234
|
-
if force_paginated:
|
|
235
|
-
query["paginated"] = "true"
|
|
236
262
|
if page is not None:
|
|
237
263
|
query["page"] = page
|
|
238
264
|
effective_page_size: int | None = page_size if page_size is not None else limit
|
|
@@ -780,8 +806,8 @@ class DailyBotClient:
|
|
|
780
806
|
) -> list[dict[str, Any]]:
|
|
781
807
|
"""GET /v1/forms/ — optionally expand questions, search, and page.
|
|
782
808
|
|
|
783
|
-
|
|
784
|
-
|
|
809
|
+
When ``meta`` is given it is populated with ``count`` / ``next`` for a
|
|
810
|
+
pagination footer.
|
|
785
811
|
"""
|
|
786
812
|
params: dict[str, Any] = {}
|
|
787
813
|
if include_questions:
|
|
@@ -796,7 +822,6 @@ class DailyBotClient:
|
|
|
796
822
|
page_size=page_size,
|
|
797
823
|
fetch_all=fetch_all,
|
|
798
824
|
limit=limit,
|
|
799
|
-
force_paginated=True,
|
|
800
825
|
)
|
|
801
826
|
_fill_meta(meta, result)
|
|
802
827
|
return result.results
|
|
@@ -868,7 +893,6 @@ class DailyBotClient:
|
|
|
868
893
|
page_size=page_size,
|
|
869
894
|
fetch_all=fetch_all,
|
|
870
895
|
limit=limit,
|
|
871
|
-
force_paginated=True,
|
|
872
896
|
)
|
|
873
897
|
_fill_meta(meta, result)
|
|
874
898
|
return result.results
|
|
@@ -1210,6 +1234,8 @@ class DailyBotClient:
|
|
|
1210
1234
|
self,
|
|
1211
1235
|
*,
|
|
1212
1236
|
search: str | None = None,
|
|
1237
|
+
start_date: str | None = None,
|
|
1238
|
+
end_date: str | None = None,
|
|
1213
1239
|
page: int | None = None,
|
|
1214
1240
|
page_size: int | None = None,
|
|
1215
1241
|
fetch_all: bool = True,
|
|
@@ -1218,7 +1244,7 @@ class DailyBotClient:
|
|
|
1218
1244
|
) -> list[dict[str, Any]]:
|
|
1219
1245
|
"""GET /v1/workflows/ — list workflows (read-only; plan-gated feature)."""
|
|
1220
1246
|
params: dict[str, Any] = {}
|
|
1221
|
-
_merge_list_query(params, search=search)
|
|
1247
|
+
_merge_list_query(params, search=search, start_date=start_date, end_date=end_date)
|
|
1222
1248
|
result: PaginatedResult = self._paginated_get(
|
|
1223
1249
|
f"{self.api_url}/v1/workflows/",
|
|
1224
1250
|
params=params,
|
|
@@ -1239,14 +1265,35 @@ class DailyBotClient:
|
|
|
1239
1265
|
)
|
|
1240
1266
|
return self._handle_response(response)
|
|
1241
1267
|
|
|
1242
|
-
def
|
|
1243
|
-
|
|
1244
|
-
|
|
1268
|
+
def list_kudos_organization(
|
|
1269
|
+
self,
|
|
1270
|
+
*,
|
|
1271
|
+
search: str | None = None,
|
|
1272
|
+
start_date: str | None = None,
|
|
1273
|
+
end_date: str | None = None,
|
|
1274
|
+
page: int | None = None,
|
|
1275
|
+
page_size: int | None = None,
|
|
1276
|
+
fetch_all: bool = True,
|
|
1277
|
+
limit: int | None = None,
|
|
1278
|
+
meta: dict[str, Any] | None = None,
|
|
1279
|
+
) -> list[dict[str, Any]]:
|
|
1280
|
+
"""GET /v1/kudos/organization/ — every kudos in the org (admin-only).
|
|
1281
|
+
|
|
1282
|
+
The org-wide counterpart of ``list_kudos``, which is scoped to the caller.
|
|
1283
|
+
Returns the same paginated envelope, not an aggregate statistics object.
|
|
1284
|
+
"""
|
|
1285
|
+
params: dict[str, Any] = {}
|
|
1286
|
+
_merge_list_query(params, search=search, start_date=start_date, end_date=end_date)
|
|
1287
|
+
result: PaginatedResult = self._paginated_get(
|
|
1245
1288
|
f"{self.api_url}/v1/kudos/organization/",
|
|
1246
|
-
|
|
1247
|
-
|
|
1289
|
+
params=params,
|
|
1290
|
+
page=page,
|
|
1291
|
+
page_size=page_size,
|
|
1292
|
+
fetch_all=fetch_all,
|
|
1293
|
+
limit=limit,
|
|
1248
1294
|
)
|
|
1249
|
-
|
|
1295
|
+
_fill_meta(meta, result)
|
|
1296
|
+
return result.results
|
|
1250
1297
|
|
|
1251
1298
|
def get_kudos_wall_of_fame(self, *, limit: int | None = None) -> dict[str, Any]:
|
|
1252
1299
|
"""GET /v1/kudos/wall-of-fame/ — leaderboard rankings."""
|
|
@@ -31,7 +31,9 @@ from dailybot_cli.commands.public_api_helpers import (
|
|
|
31
31
|
enforce_plan_access,
|
|
32
32
|
exit_for_api_error,
|
|
33
33
|
require_auth,
|
|
34
|
+
validate_user_filter,
|
|
34
35
|
)
|
|
36
|
+
from dailybot_cli.commands.query_options import paging_options
|
|
35
37
|
from dailybot_cli.commands.user_scoped_actions import (
|
|
36
38
|
execute_checkin_complete,
|
|
37
39
|
execute_checkin_edit,
|
|
@@ -307,6 +309,7 @@ def checkin_show(followup_uuid: str, json_mode: bool) -> None:
|
|
|
307
309
|
@click.option(
|
|
308
310
|
"--search", "-s", "search", default=None, help="Filter response content (max 256 chars)."
|
|
309
311
|
)
|
|
312
|
+
@paging_options
|
|
310
313
|
@click.option("--json", "json_mode", is_flag=True, help="Emit machine-readable JSON to stdout.")
|
|
311
314
|
def checkin_history(
|
|
312
315
|
followup_uuid: str,
|
|
@@ -315,6 +318,9 @@ def checkin_history(
|
|
|
315
318
|
date_to: str | None,
|
|
316
319
|
user: str | None,
|
|
317
320
|
search: str | None,
|
|
321
|
+
page: int | None,
|
|
322
|
+
page_size: int | None,
|
|
323
|
+
limit: int | None,
|
|
318
324
|
json_mode: bool,
|
|
319
325
|
) -> None:
|
|
320
326
|
"""Show a check-in's response history.
|
|
@@ -330,6 +336,7 @@ def checkin_history(
|
|
|
330
336
|
dailybot checkin history <followup_uuid> --from 2026-06-01 --to 2026-06-30 --json
|
|
331
337
|
dailybot checkin history <followup_uuid> --user <user_uuid> --days 30
|
|
332
338
|
"""
|
|
339
|
+
validate_user_filter(user)
|
|
333
340
|
client = require_auth()
|
|
334
341
|
execute_checkin_history(
|
|
335
342
|
client,
|
|
@@ -339,6 +346,9 @@ def checkin_history(
|
|
|
339
346
|
date_to=date_to,
|
|
340
347
|
user=user,
|
|
341
348
|
search=search,
|
|
349
|
+
page=page,
|
|
350
|
+
page_size=page_size,
|
|
351
|
+
limit=limit,
|
|
342
352
|
json_mode=json_mode,
|
|
343
353
|
)
|
|
344
354
|
|
|
@@ -26,8 +26,13 @@ from dailybot_cli.commands.public_api_helpers import (
|
|
|
26
26
|
enforce_plan_access,
|
|
27
27
|
exit_for_api_error,
|
|
28
28
|
require_auth,
|
|
29
|
+
validate_user_filter,
|
|
30
|
+
)
|
|
31
|
+
from dailybot_cli.commands.query_options import (
|
|
32
|
+
build_query_params,
|
|
33
|
+
paging_options,
|
|
34
|
+
query_options,
|
|
29
35
|
)
|
|
30
|
-
from dailybot_cli.commands.query_options import build_query_params, query_options
|
|
31
36
|
from dailybot_cli.commands.user_scoped_actions import (
|
|
32
37
|
execute_form_list,
|
|
33
38
|
execute_form_submit,
|
|
@@ -51,6 +56,16 @@ from dailybot_cli.display import (
|
|
|
51
56
|
print_success,
|
|
52
57
|
)
|
|
53
58
|
|
|
59
|
+
# The server reuses `invalid_workflow_state` for a malformed `--state
|
|
60
|
+
# "Label:#color"` during authoring. On this listing it means the form has no
|
|
61
|
+
# workflow at all, so the shared message would send the user down the wrong path.
|
|
62
|
+
_RESPONSES_ERROR_OVERRIDES: dict[str, str] = {
|
|
63
|
+
"invalid_workflow_state": (
|
|
64
|
+
"This form has no workflow, so --state doesn't apply. Drop the flag, or run "
|
|
65
|
+
"`dailybot form get <form_uuid>` to see the states a workflow form defines."
|
|
66
|
+
),
|
|
67
|
+
}
|
|
68
|
+
|
|
54
69
|
|
|
55
70
|
def _maybe_load_form(client: DailyBotClient, form_uuid: str) -> dict[str, Any] | None:
|
|
56
71
|
"""Return form metadata if accessible; swallow errors (UI-only enrichment)."""
|
|
@@ -293,7 +308,11 @@ def form_submit(
|
|
|
293
308
|
|
|
294
309
|
@form.command("responses")
|
|
295
310
|
@click.argument("form_uuid")
|
|
296
|
-
@click.option(
|
|
311
|
+
@click.option(
|
|
312
|
+
"--state",
|
|
313
|
+
default=None,
|
|
314
|
+
help="Filter by workflow state key (e.g. draft, not Draft). Workflow forms only.",
|
|
315
|
+
)
|
|
297
316
|
@click.option(
|
|
298
317
|
"--all",
|
|
299
318
|
"all_responses",
|
|
@@ -313,6 +332,7 @@ def form_submit(
|
|
|
313
332
|
is_flag=True,
|
|
314
333
|
help="Return only the most recent response (continue where you left off).",
|
|
315
334
|
)
|
|
335
|
+
@paging_options
|
|
316
336
|
@click.option("--json", "json_mode", is_flag=True, help="Emit machine-readable JSON to stdout.")
|
|
317
337
|
def form_responses(
|
|
318
338
|
form_uuid: str,
|
|
@@ -323,6 +343,9 @@ def form_responses(
|
|
|
323
343
|
date_to: str | None,
|
|
324
344
|
search: str | None,
|
|
325
345
|
latest: bool,
|
|
346
|
+
page: int | None,
|
|
347
|
+
page_size: int | None,
|
|
348
|
+
limit: int | None,
|
|
326
349
|
json_mode: bool,
|
|
327
350
|
) -> None:
|
|
328
351
|
"""List responses on a form.
|
|
@@ -339,6 +362,7 @@ def form_responses(
|
|
|
339
362
|
dailybot form responses <form_uuid> --all --from 2026-01-01 --to 2026-06-30
|
|
340
363
|
dailybot form responses <form_uuid> --user <user_uuid> --json
|
|
341
364
|
"""
|
|
365
|
+
validate_user_filter(user)
|
|
342
366
|
client = require_auth()
|
|
343
367
|
truncated_search: str | None = search[:256] if search is not None else None
|
|
344
368
|
meta: dict[str, Any] = {}
|
|
@@ -352,10 +376,14 @@ def form_responses(
|
|
|
352
376
|
date_from=date_from,
|
|
353
377
|
date_to=date_to,
|
|
354
378
|
search=truncated_search,
|
|
379
|
+
page=page,
|
|
380
|
+
page_size=page_size,
|
|
381
|
+
fetch_all=page is None and page_size is None and limit is None,
|
|
382
|
+
limit=limit,
|
|
355
383
|
meta=meta,
|
|
356
384
|
)
|
|
357
385
|
except APIError as exc:
|
|
358
|
-
exit_for_api_error(exc, json_mode)
|
|
386
|
+
exit_for_api_error(exc, json_mode, code_overrides=_RESPONSES_ERROR_OVERRIDES)
|
|
359
387
|
|
|
360
388
|
if latest:
|
|
361
389
|
responses = responses[:1] if responses else []
|
|
@@ -366,7 +394,12 @@ def form_responses(
|
|
|
366
394
|
|
|
367
395
|
form_data: dict[str, Any] | None = _maybe_load_form(client, form_uuid)
|
|
368
396
|
print_form_responses_table(form_uuid, responses, form_data)
|
|
369
|
-
print_pagination_footer(
|
|
397
|
+
print_pagination_footer(
|
|
398
|
+
len(responses),
|
|
399
|
+
meta.get("count"),
|
|
400
|
+
has_more=bool(meta.get("next")),
|
|
401
|
+
more_hint="omit --page/--page-size to fetch every page",
|
|
402
|
+
)
|
|
370
403
|
|
|
371
404
|
|
|
372
405
|
@form.group("response")
|
|
@@ -10,7 +10,7 @@ import questionary
|
|
|
10
10
|
from questionary import Choice, Separator
|
|
11
11
|
|
|
12
12
|
from dailybot_cli import __version__
|
|
13
|
-
from dailybot_cli.api_client import APIError, DailyBotClient
|
|
13
|
+
from dailybot_cli.api_client import APIError, DailyBotClient, resource_uuid
|
|
14
14
|
from dailybot_cli.commands.auth import _do_login
|
|
15
15
|
from dailybot_cli.commands.chat import ChatPayloadError, build_chat_payload
|
|
16
16
|
from dailybot_cli.commands.kudos import execute_kudos_give
|
|
@@ -94,7 +94,7 @@ def _checkin_label(checkin: dict[str, Any]) -> str:
|
|
|
94
94
|
|
|
95
95
|
|
|
96
96
|
def _form_label(form: dict[str, Any]) -> str:
|
|
97
|
-
name: str = str(form.get("name") or form
|
|
97
|
+
name: str = str(form.get("name") or resource_uuid(form) or "Form")
|
|
98
98
|
active: bool = bool(form.get("is_active"))
|
|
99
99
|
suffix: str = "" if active else " [inactive]"
|
|
100
100
|
return f"{name}{suffix}"
|
|
@@ -269,7 +269,7 @@ def _submit_form(client: DailyBotClient) -> None:
|
|
|
269
269
|
if selected is None:
|
|
270
270
|
raise InteractiveAbort()
|
|
271
271
|
|
|
272
|
-
form_uuid: str =
|
|
272
|
+
form_uuid: str = resource_uuid(selected)
|
|
273
273
|
form_name: str = str(selected.get("name") or form_uuid)
|
|
274
274
|
if not form_uuid:
|
|
275
275
|
print_error("Selected form has no UUID.")
|
|
@@ -17,7 +17,7 @@ from dailybot_cli.commands.public_api_helpers import (
|
|
|
17
17
|
resolve_team_by_name_or_uuid,
|
|
18
18
|
resolve_user_by_name_or_uuid,
|
|
19
19
|
)
|
|
20
|
-
from dailybot_cli.commands.query_options import build_query_params, query_options
|
|
20
|
+
from dailybot_cli.commands.query_options import build_query_params, query_options, resolve_fetch_all
|
|
21
21
|
from dailybot_cli.display import (
|
|
22
22
|
console,
|
|
23
23
|
print_detail_panel,
|
|
@@ -211,13 +211,6 @@ def kudos_give(
|
|
|
211
211
|
)
|
|
212
212
|
|
|
213
213
|
|
|
214
|
-
_KUDOS_ORG_FIELDS: list[tuple[str, str]] = [
|
|
215
|
-
("Total kudos", "total_kudos"),
|
|
216
|
-
("Total givers", "total_givers"),
|
|
217
|
-
("Total receivers", "total_receivers"),
|
|
218
|
-
]
|
|
219
|
-
|
|
220
|
-
|
|
221
214
|
@kudos.command("list")
|
|
222
215
|
@click.option(
|
|
223
216
|
"--filter", "kudos_filter", default=None, help="Filter (e.g. KUDOS_RECEIVED, KUDOS_GIVEN)."
|
|
@@ -264,7 +257,7 @@ def kudos_list(
|
|
|
264
257
|
print_error(str(exc))
|
|
265
258
|
raise SystemExit(EXIT_USAGE_ERROR)
|
|
266
259
|
client = require_auth()
|
|
267
|
-
resolved_fetch_all: bool =
|
|
260
|
+
resolved_fetch_all: bool = resolve_fetch_all(spec)
|
|
268
261
|
meta: dict[str, Any] = {}
|
|
269
262
|
try:
|
|
270
263
|
with console.status("Fetching kudos..."):
|
|
@@ -289,26 +282,71 @@ def kudos_list(
|
|
|
289
282
|
|
|
290
283
|
|
|
291
284
|
@kudos.command("org")
|
|
285
|
+
@query_options
|
|
292
286
|
@click.option("--json", "json_mode", is_flag=True, help="Emit machine-readable JSON to stdout.")
|
|
293
|
-
def kudos_org(
|
|
294
|
-
|
|
287
|
+
def kudos_org(
|
|
288
|
+
json_mode: bool,
|
|
289
|
+
page: int | None,
|
|
290
|
+
page_size: int | None,
|
|
291
|
+
fetch_all: bool,
|
|
292
|
+
limit: int | None,
|
|
293
|
+
search: str | None,
|
|
294
|
+
since: str | None,
|
|
295
|
+
until: str | None,
|
|
296
|
+
on_date: str | None,
|
|
297
|
+
last_week: bool,
|
|
298
|
+
today: bool,
|
|
299
|
+
) -> None:
|
|
300
|
+
"""Browse every kudos in the organization (admin only).
|
|
301
|
+
|
|
302
|
+
\b
|
|
303
|
+
The org-wide feed. `kudos list` shows only the ones you gave or received.
|
|
304
|
+
Non-admins receive a 403.
|
|
295
305
|
|
|
296
306
|
\b
|
|
297
307
|
Examples:
|
|
298
308
|
dailybot kudos org
|
|
299
|
-
dailybot kudos org --
|
|
309
|
+
dailybot kudos org --page-size 20 --since 2026-07-01
|
|
310
|
+
dailybot kudos org --search onboarding --json
|
|
300
311
|
"""
|
|
301
312
|
enforce_plan_access("kudos_org", json_mode=json_mode)
|
|
313
|
+
try:
|
|
314
|
+
spec = build_query_params(
|
|
315
|
+
page=page,
|
|
316
|
+
page_size=page_size,
|
|
317
|
+
fetch_all=fetch_all,
|
|
318
|
+
limit=limit,
|
|
319
|
+
search=search,
|
|
320
|
+
since=since,
|
|
321
|
+
until=until,
|
|
322
|
+
on_date=on_date,
|
|
323
|
+
last_week=last_week,
|
|
324
|
+
today=today,
|
|
325
|
+
)
|
|
326
|
+
except ValueError as exc:
|
|
327
|
+
print_error(str(exc))
|
|
328
|
+
raise SystemExit(EXIT_USAGE_ERROR)
|
|
302
329
|
client = require_auth()
|
|
330
|
+
meta: dict[str, Any] = {}
|
|
303
331
|
try:
|
|
304
|
-
with console.status("Fetching kudos
|
|
305
|
-
|
|
332
|
+
with console.status("Fetching organization kudos..."):
|
|
333
|
+
kudos_items: list[dict[str, Any]] = client.list_kudos_organization(
|
|
334
|
+
search=spec.params.get("search"),
|
|
335
|
+
start_date=spec.params.get("start_date"),
|
|
336
|
+
end_date=spec.params.get("end_date"),
|
|
337
|
+
page=spec.page,
|
|
338
|
+
page_size=spec.page_size,
|
|
339
|
+
fetch_all=resolve_fetch_all(spec),
|
|
340
|
+
limit=spec.limit,
|
|
341
|
+
meta=meta,
|
|
342
|
+
)
|
|
306
343
|
except APIError as exc:
|
|
307
344
|
exit_for_api_error(exc, json_mode)
|
|
308
345
|
if json_mode:
|
|
309
|
-
emit_json(
|
|
346
|
+
emit_json(kudos_items)
|
|
310
347
|
return
|
|
311
|
-
|
|
348
|
+
print_kudos_table(kudos_items)
|
|
349
|
+
print_pagination_footer(len(kudos_items), meta.get("count"), has_more=bool(meta.get("next")))
|
|
312
350
|
|
|
313
351
|
|
|
314
352
|
@kudos.command("wall-of-fame")
|
|
@@ -211,7 +211,8 @@ ERROR_CODE_MESSAGES: dict[str, str] = {
|
|
|
211
211
|
# `extra` (upgrade_url, required/current role) in exit_for_api_error.
|
|
212
212
|
"plan_upgrade_required": (
|
|
213
213
|
"This action requires a paid Dailybot plan. On the free plan the CLI can "
|
|
214
|
-
"only submit agent reports,
|
|
214
|
+
"only submit agent reports and emails, check agent messages and health, "
|
|
215
|
+
"read your own profile, and view today's pending check-ins."
|
|
215
216
|
),
|
|
216
217
|
"plan_free_api_keys_forbidden": (
|
|
217
218
|
"API keys aren't available on the free plan. Run `dailybot login` to use a "
|
|
@@ -236,6 +237,10 @@ ERROR_CODE_MESSAGES: dict[str, str] = {
|
|
|
236
237
|
"invalid_date_range": (
|
|
237
238
|
"Invalid date. Use YYYY-MM-DD, and make sure the start date is on or before the end date."
|
|
238
239
|
),
|
|
240
|
+
"invalid_user_identifier": (
|
|
241
|
+
"--user only accepts a user UUID, not an email or a name. "
|
|
242
|
+
"Run 'dailybot user list' to find it."
|
|
243
|
+
),
|
|
239
244
|
# Rate limiting (429)
|
|
240
245
|
"free_plan_daily_limit_exceeded": (
|
|
241
246
|
"You've reached today's free-plan limit for this action. Wait for it to reset "
|
|
@@ -263,6 +268,19 @@ UUID_PATTERN: re.Pattern[str] = re.compile(
|
|
|
263
268
|
)
|
|
264
269
|
|
|
265
270
|
|
|
271
|
+
def validate_user_filter(value: str | None) -> None:
|
|
272
|
+
"""Reject a non-UUID ``--user`` filter before it reaches the API.
|
|
273
|
+
|
|
274
|
+
The response-listing endpoints accept only a UUID here. Checking locally
|
|
275
|
+
turns a round-trip and a 400 into an immediate, actionable message.
|
|
276
|
+
"""
|
|
277
|
+
if value is None:
|
|
278
|
+
return
|
|
279
|
+
if not UUID_PATTERN.match(value):
|
|
280
|
+
print_error(ERROR_CODE_MESSAGES["invalid_user_identifier"])
|
|
281
|
+
raise SystemExit(1)
|
|
282
|
+
|
|
283
|
+
|
|
266
284
|
def require_auth() -> DailyBotClient:
|
|
267
285
|
"""Ensure a login session or API key exists and return a client.
|
|
268
286
|
|
|
@@ -305,18 +323,30 @@ def _augment_code_message(base: str, code: str, extra: dict[str, Any]) -> str:
|
|
|
305
323
|
return base
|
|
306
324
|
|
|
307
325
|
|
|
308
|
-
def exit_for_api_error(
|
|
326
|
+
def exit_for_api_error(
|
|
327
|
+
exc: APIError,
|
|
328
|
+
json_mode: bool,
|
|
329
|
+
*,
|
|
330
|
+
code_overrides: dict[str, str] | None = None,
|
|
331
|
+
) -> NoReturn:
|
|
309
332
|
"""Map API failures to user-facing messages and process exit codes.
|
|
310
333
|
|
|
311
334
|
Dispatches strictly on the machine-readable ``code`` (never on ``detail``
|
|
312
335
|
text); falls back to the status-code-based message when ``code`` is absent
|
|
313
336
|
(older backends).
|
|
337
|
+
|
|
338
|
+
``code_overrides`` lets a command replace the shared message for a code the
|
|
339
|
+
server reuses across surfaces. ``invalid_workflow_state`` means "malformed
|
|
340
|
+
--state definition" when authoring a form and "this form has no workflow"
|
|
341
|
+
when filtering its responses; only the calling command can tell them apart.
|
|
314
342
|
"""
|
|
315
343
|
code: str | None = getattr(exc, "code", None)
|
|
316
344
|
extra: dict[str, Any] = getattr(exc, "extra", {}) or {}
|
|
317
345
|
mapped_message: str | None = ERROR_CODE_MESSAGES.get(code) if code else None
|
|
318
346
|
if mapped_message and code:
|
|
319
347
|
mapped_message = _augment_code_message(mapped_message, code, extra)
|
|
348
|
+
if code and code_overrides and code in code_overrides:
|
|
349
|
+
mapped_message = code_overrides[code]
|
|
320
350
|
|
|
321
351
|
if exc.status_code == 401:
|
|
322
352
|
message: str = "Session expired. Run: dailybot login"
|