dailybot-cli 3.5.2__tar.gz → 3.6.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.5.2 → dailybot_cli-3.6.0}/PKG-INFO +1 -1
  2. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/api_client.py +36 -3
  3. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/commands/form.py +151 -7
  4. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/commands/public_api_helpers.py +3 -1
  5. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/commands/user_scoped_actions.py +2 -0
  6. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli.egg-info/PKG-INFO +1 -1
  7. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/pyproject.toml +1 -1
  8. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/LICENSE +0 -0
  9. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/README.md +0 -0
  10. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/__init__.py +0 -0
  11. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/commands/__init__.py +0 -0
  12. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/commands/agent.py +0 -0
  13. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/commands/ask.py +0 -0
  14. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/commands/auth.py +0 -0
  15. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/commands/authoring_helpers.py +0 -0
  16. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/commands/channels.py +0 -0
  17. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/commands/chat.py +0 -0
  18. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/commands/checkin.py +0 -0
  19. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/commands/config.py +0 -0
  20. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/commands/conversation.py +0 -0
  21. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/commands/hook.py +0 -0
  22. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/commands/identity.py +0 -0
  23. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/commands/interactive.py +0 -0
  24. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/commands/interactive_chat.py +0 -0
  25. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/commands/kudos.py +0 -0
  26. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/commands/query_options.py +0 -0
  27. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/commands/status.py +0 -0
  28. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/commands/team.py +0 -0
  29. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/commands/uninstall.py +0 -0
  30. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/commands/update.py +0 -0
  31. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/commands/upgrade.py +0 -0
  32. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/commands/user.py +0 -0
  33. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/commands/version.py +0 -0
  34. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/commands/workflow.py +0 -0
  35. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/config.py +0 -0
  36. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/display.py +0 -0
  37. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/install_method.py +0 -0
  38. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/ledger.py +0 -0
  39. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/main.py +0 -0
  40. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/tui/__init__.py +0 -0
  41. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/tui/app.py +0 -0
  42. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/tui/commands.py +0 -0
  43. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/tui/conversation.py +0 -0
  44. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli/tui/intents.py +0 -0
  45. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli.egg-info/SOURCES.txt +0 -0
  46. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli.egg-info/dependency_links.txt +0 -0
  47. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli.egg-info/entry_points.txt +0 -0
  48. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli.egg-info/requires.txt +0 -0
  49. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/dailybot_cli.egg-info/top_level.txt +0 -0
  50. {dailybot_cli-3.5.2 → dailybot_cli-3.6.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dailybot-cli
3
- Version: 3.5.2
3
+ Version: 3.6.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
@@ -20,6 +20,7 @@ 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
22
  MAX_SEARCH_QUERY_LENGTH: int = 256 # server rejects search queries longer than this
23
+ MAX_OWNER_USER_IDS: int = 50 # server rejects owner_user_ids lists longer than this
23
24
 
24
25
 
25
26
  def _fallback_detail(response: httpx.Response) -> str:
@@ -865,6 +866,7 @@ class DailyBotClient:
865
866
  include_questions: bool = False,
866
867
  include_archived: bool = False,
867
868
  owner: str | None = None,
869
+ owner_user_ids: list[str] | None = None,
868
870
  filter_scope: str | None = None,
869
871
  order: str | None = None,
870
872
  is_ascend: bool = False,
@@ -879,9 +881,10 @@ class DailyBotClient:
879
881
  ) -> list[dict[str, Any]]:
880
882
  """GET /v1/forms/ — optionally expand questions, search, and page.
881
883
 
882
- The default response is org-scoped (every form the caller can see on the
883
- webapp list view). Pass ``owner="me"`` to narrow to the caller's own forms,
884
- or ``filter_scope`` to apply a server-side scope filter.
884
+ The default response is org-wide (every form in the caller's org).
885
+ Capabilities are governed by each form's permissions. Pass
886
+ ``owner_user_ids`` to filter by specific owners, or
887
+ ``filter_scope`` to apply a server-side scope filter.
885
888
 
886
889
  When ``meta`` is given it is populated with ``count`` / ``next`` for a
887
890
  pagination footer.
@@ -893,6 +896,8 @@ class DailyBotClient:
893
896
  params["include_archived"] = "true"
894
897
  if owner:
895
898
  params["owner"] = owner
899
+ if owner_user_ids:
900
+ params["owner_user_ids"] = ",".join(owner_user_ids)
896
901
  if filter_scope:
897
902
  params["filter"] = filter_scope
898
903
  if order:
@@ -911,6 +916,34 @@ class DailyBotClient:
911
916
  _fill_meta(meta, result)
912
917
  return result.results
913
918
 
919
+ def list_form_owners(
920
+ self,
921
+ *,
922
+ search: str | None = None,
923
+ offset: int | None = None,
924
+ limit: int | None = None,
925
+ ) -> dict[str, Any]:
926
+ """GET /v1/forms/form-owners/ — paginated picker of org members who own forms.
927
+
928
+ Returns the raw paginated envelope ``{count, next, previous, results}``.
929
+ Each result has ``uuid``, ``full_name``, ``image``, ``role``, and
930
+ optionally ``email`` (only visible to admins/managers).
931
+ """
932
+ params: dict[str, Any] = {}
933
+ if search:
934
+ params["search"] = search
935
+ if offset is not None:
936
+ params["offset"] = offset
937
+ if limit is not None:
938
+ params["limit"] = limit
939
+ response: httpx.Response = httpx.get(
940
+ f"{self.api_url}/v1/forms/form-owners/",
941
+ params=params,
942
+ headers=self._headers(),
943
+ timeout=self.timeout,
944
+ )
945
+ return self._handle_response(response)
946
+
914
947
  def get_form(self, form_uuid: str) -> dict[str, Any]:
915
948
  """GET /v1/forms/<form_uuid>/ — form metadata and question definitions."""
916
949
  response: httpx.Response = httpx.get(
@@ -6,7 +6,11 @@ from typing import Any
6
6
 
7
7
  import click
8
8
 
9
- from dailybot_cli.api_client import APIError, DailyBotClient
9
+ from dailybot_cli.api_client import (
10
+ MAX_OWNER_USER_IDS,
11
+ APIError,
12
+ DailyBotClient,
13
+ )
10
14
  from dailybot_cli.commands.authoring_helpers import (
11
15
  build_question,
12
16
  build_question_edit_fields,
@@ -26,7 +30,9 @@ from dailybot_cli.commands.public_api_helpers import (
26
30
  emit_json,
27
31
  enforce_plan_access,
28
32
  exit_for_api_error,
33
+ get_current_user_uuid,
29
34
  require_auth,
35
+ resolve_user_by_name_or_uuid,
30
36
  validate_user_filter,
31
37
  )
32
38
  from dailybot_cli.commands.query_options import (
@@ -64,6 +70,8 @@ MAX_SUBMISSION_SOURCE_LENGTH: int = 512
64
70
  _EMAIL_RE: re.Pattern[str] = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")
65
71
 
66
72
  FORM_LIST_FILTERS: tuple[str, ...] = ("all", "me", "public", "approval", "workflow", "archived")
73
+ # "me" is deprecated server-side — kept here for back-compat but --mine now
74
+ # uses owner_user_ids with the caller's UUID instead.
67
75
  FORM_LIST_ORDERS: tuple[str, ...] = ("alphabetical", "recent", "total")
68
76
  RESPONSE_ORDERS: tuple[str, ...] = ("recent", "oldest")
69
77
  RESPONSE_SOURCES: tuple[str, ...] = ("member", "anonymous", "automation", "public")
@@ -202,14 +210,26 @@ def form() -> None:
202
210
  @click.option(
203
211
  "--mine",
204
212
  is_flag=True,
205
- help="Only forms you own (default lists every form you can see in the org).",
213
+ help="Only forms you own (default lists every form in the org).",
214
+ )
215
+ @click.option(
216
+ "--owner",
217
+ "owners",
218
+ multiple=True,
219
+ help=(
220
+ "Filter by form owner (UUID, email, or name). Repeatable. "
221
+ "Names/emails are resolved via the org directory."
222
+ ),
206
223
  )
207
224
  @click.option(
208
225
  "--filter",
209
226
  "filter_scope",
210
227
  type=click.Choice(FORM_LIST_FILTERS, case_sensitive=False),
211
228
  default=None,
212
- help="Scope filter: all, me, public, approval, workflow, archived.",
229
+ help=(
230
+ "Scope filter: all, public, approval, workflow, archived. "
231
+ '("me" still works but is deprecated — use --mine or --owner instead.)'
232
+ ),
213
233
  )
214
234
  @click.option(
215
235
  "--order",
@@ -236,6 +256,7 @@ def form() -> None:
236
256
  def form_list(
237
257
  include_archived: bool,
238
258
  mine: bool,
259
+ owners: tuple[str, ...],
239
260
  filter_scope: str | None,
240
261
  order: str | None,
241
262
  is_ascend: bool,
@@ -255,15 +276,17 @@ def form_list(
255
276
  """List forms visible to you.
256
277
 
257
278
  \b
258
- Acts as you. You can only see and act on what you could in the webapp:
259
- by default this is every form in your org you have list-view access to.
260
- Pass --mine to narrow to only the forms you own, or --filter to scope.
279
+ Lists all forms in your organization. Capabilities (editing, response
280
+ visibility, state changes) are governed by each form's permissions.
281
+ Pass --mine or --owner to filter by form owner, or --filter to scope.
261
282
  Archived forms are hidden unless you pass --include-archived or --filter archived.
262
283
 
263
284
  \b
264
285
  Examples:
265
286
  dailybot form list
266
287
  dailybot form list --mine
288
+ dailybot form list --owner "Jane Doe"
289
+ dailybot form list --owner <uuid> --owner <uuid>
267
290
  dailybot form list --filter workflow --order alphabetical --asc
268
291
  dailybot form list --filter approval
269
292
  dailybot form list --filter public --order total
@@ -289,12 +312,34 @@ def form_list(
289
312
  print_error(str(exc))
290
313
  raise SystemExit(1)
291
314
  client = require_auth()
315
+
316
+ owner_user_ids: list[str] | None = None
317
+ if mine or owners:
318
+ owner_user_ids = []
319
+ if mine:
320
+ my_uuid: str | None = get_current_user_uuid(client)
321
+ if my_uuid:
322
+ owner_user_ids.append(my_uuid)
323
+ else:
324
+ print_error(
325
+ "Could not resolve your user UUID for --mine. "
326
+ "Try passing your UUID with --owner instead."
327
+ )
328
+ raise SystemExit(1)
329
+ if owners:
330
+ owner_user_ids.extend(_resolve_owner_uuids(client, owners))
331
+ if len(owner_user_ids) > MAX_OWNER_USER_IDS:
332
+ print_error(
333
+ f"Too many owner UUIDs ({len(owner_user_ids)}). Maximum is {MAX_OWNER_USER_IDS}."
334
+ )
335
+ raise SystemExit(1)
336
+
292
337
  execute_form_list(
293
338
  client,
294
339
  json_mode=json_mode,
295
340
  include_archived=include_archived,
296
341
  include_questions=include_questions,
297
- owner="me" if mine else None,
342
+ owner_user_ids=owner_user_ids,
298
343
  filter_scope=filter_scope,
299
344
  order=order,
300
345
  is_ascend=is_ascend,
@@ -302,6 +347,105 @@ def form_list(
302
347
  )
303
348
 
304
349
 
350
+ def _resolve_owner_uuids(
351
+ client: DailyBotClient,
352
+ identifiers: tuple[str, ...],
353
+ ) -> list[str]:
354
+ """Resolve owner identifiers (UUID, email, or name) to UUIDs.
355
+
356
+ Loads the org directory only when non-UUID identifiers are present.
357
+ """
358
+ from dailybot_cli.commands.public_api_helpers import UUID_PATTERN
359
+
360
+ uuids: list[str] = []
361
+ to_resolve: list[str] = []
362
+ for ident in identifiers:
363
+ if UUID_PATTERN.match(ident):
364
+ uuids.append(ident)
365
+ else:
366
+ to_resolve.append(ident)
367
+ if to_resolve:
368
+ with console.status("Resolving owner names..."):
369
+ users: list[dict[str, Any]] = client.list_users(include_email=True)
370
+ for ident in to_resolve:
371
+ try:
372
+ uid, _name = resolve_user_by_name_or_uuid(users, ident)
373
+ uuids.append(uid)
374
+ except ValueError as exc:
375
+ print_error(str(exc))
376
+ raise SystemExit(1)
377
+ return uuids
378
+
379
+
380
+ @form.command("owners")
381
+ @click.option("--search", "-s", default=None, help="Search owners by name or email.")
382
+ @click.option("--json", "json_mode", is_flag=True, help="Emit machine-readable JSON to stdout.")
383
+ @click.option("--limit", "-l", type=int, default=None, help="Max results (default 20, max 50).")
384
+ def form_owners(
385
+ search: str | None,
386
+ json_mode: bool,
387
+ limit: int | None,
388
+ ) -> None:
389
+ """List org members who own at least one form.
390
+
391
+ \b
392
+ A lightweight picker for the --owner filter on `form list`. Returns only
393
+ members who own at least one non-archived form. Email is visible only to
394
+ admins/managers.
395
+
396
+ \b
397
+ Examples:
398
+ dailybot form owners
399
+ dailybot form owners --search jane
400
+ dailybot form owners --json
401
+ """
402
+ client = require_auth()
403
+ try:
404
+ with console.status("Fetching form owners..."):
405
+ data: dict[str, Any] = client.list_form_owners(
406
+ search=search,
407
+ limit=limit,
408
+ )
409
+ except APIError as exc:
410
+ exit_for_api_error(exc, json_mode)
411
+
412
+ results: list[dict[str, Any]] = data.get("results", [])
413
+ if json_mode:
414
+ emit_json(results)
415
+ return
416
+
417
+ if not results:
418
+ from dailybot_cli.display import print_info
419
+
420
+ print_info("No form owners found.")
421
+ return
422
+
423
+ from rich.table import Table
424
+
425
+ table = Table(title="Form owners", border_style="cyan")
426
+ table.add_column("Name", style="bold")
427
+ table.add_column("UUID", style="dim")
428
+ table.add_column("Role")
429
+ has_email: bool = any(r.get("email") for r in results)
430
+ if has_email:
431
+ table.add_column("Email")
432
+ for row in results:
433
+ cols: list[str] = [
434
+ str(row.get("full_name", "")),
435
+ str(row.get("uuid", "")),
436
+ str(row.get("role", "")),
437
+ ]
438
+ if has_email:
439
+ cols.append(str(row.get("email", "")))
440
+ table.add_row(*cols)
441
+ console.print(table)
442
+ total: int | None = data.get("count")
443
+ if total is not None and total > len(results):
444
+ from dailybot_cli.display import print_info
445
+
446
+ print_info(f"Showing {len(results)} of {total} owners. Use --search to narrow.")
447
+
448
+
305
449
  @form.command("get")
306
450
  @click.argument("form_uuid")
307
451
  @click.option("--json", "json_mode", is_flag=True, help="Emit machine-readable JSON to stdout.")
@@ -238,7 +238,9 @@ ERROR_CODE_MESSAGES: dict[str, str] = {
238
238
  "This form requires guest submitter info. "
239
239
  "Add --guest-name and --guest-email to your command."
240
240
  ),
241
- "invalid_filter": "Unknown --filter value. Use: all, me, public, approval, workflow, archived.",
241
+ "invalid_filter": "Unknown --filter value. Use: all, public, approval, workflow, archived.",
242
+ "invalid_owner_user_id": "Invalid --owner UUID format.",
243
+ "too_many_owner_user_ids": "Too many --owner UUIDs (max 50). Narrow your filter.",
242
244
  "invalid_order": "Unknown --order value. Use: alphabetical, recent, total (forms) or recent, oldest (responses).",
243
245
  "invalid_submission_sources": (
244
246
  "Unknown --source value. Use: member, anonymous, automation, public (comma-separated)."
@@ -502,6 +502,7 @@ def execute_form_list(
502
502
  include_archived: bool = False,
503
503
  include_questions: bool = False,
504
504
  owner: str | None = None,
505
+ owner_user_ids: list[str] | None = None,
505
506
  filter_scope: str | None = None,
506
507
  order: str | None = None,
507
508
  is_ascend: bool = False,
@@ -518,6 +519,7 @@ def execute_form_list(
518
519
  include_questions=always_include_questions,
519
520
  include_archived=include_archived,
520
521
  owner=owner,
522
+ owner_user_ids=owner_user_ids,
521
523
  filter_scope=filter_scope,
522
524
  order=order,
523
525
  is_ascend=is_ascend,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dailybot-cli
3
- Version: 3.5.2
3
+ Version: 3.6.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.5.2"
7
+ version = "3.6.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