dailybot-cli 3.3.0__tar.gz → 3.5.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.3.0 → dailybot_cli-3.5.0}/PKG-INFO +1 -1
  2. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/api_client.py +41 -2
  3. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/form.py +192 -7
  4. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/public_api_helpers.py +12 -0
  5. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/user_scoped_actions.py +29 -2
  6. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/config.py +18 -0
  7. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/main.py +10 -2
  8. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli.egg-info/PKG-INFO +1 -1
  9. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/pyproject.toml +1 -1
  10. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/LICENSE +0 -0
  11. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/README.md +0 -0
  12. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/__init__.py +0 -0
  13. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/__init__.py +0 -0
  14. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/agent.py +0 -0
  15. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/ask.py +0 -0
  16. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/auth.py +0 -0
  17. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/authoring_helpers.py +0 -0
  18. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/channels.py +0 -0
  19. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/chat.py +0 -0
  20. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/checkin.py +0 -0
  21. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/config.py +0 -0
  22. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/conversation.py +0 -0
  23. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/hook.py +0 -0
  24. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/identity.py +0 -0
  25. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/interactive.py +0 -0
  26. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/interactive_chat.py +0 -0
  27. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/kudos.py +0 -0
  28. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/query_options.py +0 -0
  29. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/status.py +0 -0
  30. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/team.py +0 -0
  31. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/uninstall.py +0 -0
  32. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/update.py +0 -0
  33. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/upgrade.py +0 -0
  34. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/user.py +0 -0
  35. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/version.py +0 -0
  36. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/workflow.py +0 -0
  37. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/display.py +0 -0
  38. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/install_method.py +0 -0
  39. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/ledger.py +0 -0
  40. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/tui/__init__.py +0 -0
  41. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/tui/app.py +0 -0
  42. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/tui/commands.py +0 -0
  43. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/tui/conversation.py +0 -0
  44. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/tui/intents.py +0 -0
  45. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli.egg-info/SOURCES.txt +0 -0
  46. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli.egg-info/dependency_links.txt +0 -0
  47. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli.egg-info/entry_points.txt +0 -0
  48. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli.egg-info/requires.txt +0 -0
  49. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli.egg-info/top_level.txt +0 -0
  50. {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dailybot-cli
3
- Version: 3.3.0
3
+ Version: 3.5.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
@@ -809,6 +809,9 @@ class DailyBotClient:
809
809
  include_questions: bool = False,
810
810
  include_archived: bool = False,
811
811
  owner: str | None = None,
812
+ filter_scope: str | None = None,
813
+ order: str | None = None,
814
+ is_ascend: bool = False,
812
815
  search: str | None = None,
813
816
  start_date: str | None = None,
814
817
  end_date: str | None = None,
@@ -821,7 +824,8 @@ class DailyBotClient:
821
824
  """GET /v1/forms/ — optionally expand questions, search, and page.
822
825
 
823
826
  The default response is org-scoped (every form the caller can see on the
824
- webapp list view). Pass ``owner="me"`` to narrow to the caller's own forms.
827
+ webapp list view). Pass ``owner="me"`` to narrow to the caller's own forms,
828
+ or ``filter_scope`` to apply a server-side scope filter.
825
829
 
826
830
  When ``meta`` is given it is populated with ``count`` / ``next`` for a
827
831
  pagination footer.
@@ -833,6 +837,12 @@ class DailyBotClient:
833
837
  params["include_archived"] = "true"
834
838
  if owner:
835
839
  params["owner"] = owner
840
+ if filter_scope:
841
+ params["filter"] = filter_scope
842
+ if order:
843
+ params["order"] = order
844
+ if is_ascend:
845
+ params["is_ascend"] = "true"
836
846
  _merge_list_query(params, search=search, start_date=start_date, end_date=end_date)
837
847
  result: PaginatedResult = self._paginated_get(
838
848
  f"{self.api_url}/v1/forms/",
@@ -858,11 +868,25 @@ class DailyBotClient:
858
868
  self,
859
869
  form_uuid: str,
860
870
  content: dict[str, Any],
871
+ *,
872
+ automation: bool = False,
873
+ anonymous: bool = False,
874
+ guest_user: dict[str, str] | None = None,
875
+ submission_source: str | None = None,
861
876
  ) -> dict[str, Any]:
862
877
  """POST /v1/forms/<form_uuid>/responses/"""
878
+ payload: dict[str, Any] = {"content": content}
879
+ if automation:
880
+ payload["automation"] = True
881
+ if anonymous:
882
+ payload["anonymous"] = True
883
+ if guest_user:
884
+ payload["guest_user"] = guest_user
885
+ if submission_source:
886
+ payload["submission_source"] = submission_source
863
887
  response: httpx.Response = httpx.post(
864
888
  f"{self.api_url}/v1/forms/{form_uuid}/responses/",
865
- json={"content": content},
889
+ json=payload,
866
890
  headers=self._headers(),
867
891
  timeout=self.timeout,
868
892
  )
@@ -875,6 +899,11 @@ class DailyBotClient:
875
899
  state: str | None = None,
876
900
  all_responses: bool = False,
877
901
  user: str | None = None,
902
+ submission_sources: str | None = None,
903
+ submitter_user_ids: str | None = None,
904
+ flow_status: str | None = None,
905
+ order: str | None = None,
906
+ is_ascend: bool = False,
878
907
  date_from: str | None = None,
879
908
  date_to: str | None = None,
880
909
  search: str | None = None,
@@ -900,6 +929,16 @@ class DailyBotClient:
900
929
  params["all"] = "true"
901
930
  if user:
902
931
  params["user"] = user
932
+ if submission_sources:
933
+ params["submission_sources"] = submission_sources
934
+ if submitter_user_ids:
935
+ params["submitter_user_ids"] = submitter_user_ids
936
+ if flow_status:
937
+ params["flow_status"] = flow_status
938
+ if order:
939
+ params["order"] = order
940
+ if is_ascend:
941
+ params["is_ascend"] = "true"
903
942
  if date_from:
904
943
  params["date_from"] = date_from
905
944
  if date_to:
@@ -1,5 +1,6 @@
1
1
  """Form commands for the user-scoped public API."""
2
2
 
3
+ import re
3
4
  from collections.abc import Callable
4
5
  from typing import Any
5
6
 
@@ -59,6 +60,16 @@ from dailybot_cli.display import (
59
60
  # The server reuses `invalid_workflow_state` for a malformed `--state
60
61
  # "Label:#color"` during authoring. On this listing it means the form has no
61
62
  # workflow at all, so the shared message would send the user down the wrong path.
63
+ MAX_SUBMISSION_SOURCE_LENGTH: int = 512
64
+ _EMAIL_RE: re.Pattern[str] = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")
65
+
66
+ FORM_LIST_FILTERS: tuple[str, ...] = ("all", "me", "public", "approval", "workflow", "archived")
67
+ FORM_LIST_ORDERS: tuple[str, ...] = ("alphabetical", "recent", "total")
68
+ RESPONSE_ORDERS: tuple[str, ...] = ("recent", "oldest")
69
+ RESPONSE_SOURCES: tuple[str, ...] = ("member", "anonymous", "automation", "public")
70
+ RESPONSE_FLOW_STATUSES: tuple[str, ...] = ("pending", "approved", "denied")
71
+ MAX_SUBMITTER_IDS: int = 50
72
+
62
73
  _RESPONSES_ERROR_OVERRIDES: dict[str, str] = {
63
74
  "invalid_workflow_state": (
64
75
  "This form has no workflow, so --state doesn't apply. Drop the flag, or run "
@@ -193,11 +204,42 @@ def form() -> None:
193
204
  is_flag=True,
194
205
  help="Only forms you own (default lists every form you can see in the org).",
195
206
  )
207
+ @click.option(
208
+ "--filter",
209
+ "filter_scope",
210
+ type=click.Choice(FORM_LIST_FILTERS, case_sensitive=False),
211
+ default=None,
212
+ help="Scope filter: all, me, public, approval, workflow, archived.",
213
+ )
214
+ @click.option(
215
+ "--order",
216
+ type=click.Choice(FORM_LIST_ORDERS, case_sensitive=False),
217
+ default=None,
218
+ help="Sort order: alphabetical, recent, or total responses.",
219
+ )
220
+ @click.option(
221
+ "--ascending",
222
+ "--asc",
223
+ "is_ascend",
224
+ is_flag=True,
225
+ default=False,
226
+ help="Sort ascending (default: descending).",
227
+ )
228
+ @click.option(
229
+ "--include-questions",
230
+ is_flag=True,
231
+ default=False,
232
+ help="Include question definitions in each form.",
233
+ )
196
234
  @query_options
197
235
  @click.option("--json", "json_mode", is_flag=True, help="Emit machine-readable JSON to stdout.")
198
236
  def form_list(
199
237
  include_archived: bool,
200
238
  mine: bool,
239
+ filter_scope: str | None,
240
+ order: str | None,
241
+ is_ascend: bool,
242
+ include_questions: bool,
201
243
  json_mode: bool,
202
244
  page: int | None,
203
245
  page_size: int | None,
@@ -215,16 +257,19 @@ def form_list(
215
257
  \b
216
258
  Acts as you. You can only see and act on what you could in the webapp:
217
259
  by default this is every form in your org you have list-view access to.
218
- Pass --mine to narrow to only the forms you own.
219
- Archived forms are hidden unless you pass --include-archived.
260
+ Pass --mine to narrow to only the forms you own, or --filter to scope.
261
+ Archived forms are hidden unless you pass --include-archived or --filter archived.
220
262
 
221
263
  \b
222
264
  Examples:
223
265
  dailybot form list
224
266
  dailybot form list --mine
267
+ dailybot form list --filter workflow --order alphabetical --asc
268
+ dailybot form list --filter approval
269
+ dailybot form list --filter public --order total
225
270
  dailybot form list --search retro --since 2026-07-01
271
+ dailybot form list --include-questions --json
226
272
  dailybot form list --all
227
- dailybot form list --json
228
273
  """
229
274
  enforce_plan_access("form_list", json_mode=json_mode)
230
275
  try:
@@ -248,7 +293,11 @@ def form_list(
248
293
  client,
249
294
  json_mode=json_mode,
250
295
  include_archived=include_archived,
296
+ include_questions=include_questions,
251
297
  owner="me" if mine else None,
298
+ filter_scope=filter_scope,
299
+ order=order,
300
+ is_ascend=is_ascend,
252
301
  spec=spec,
253
302
  )
254
303
 
@@ -290,11 +339,50 @@ def form_get(form_uuid: str, json_mode: bool) -> None:
290
339
  )
291
340
  @click.option("--yes", "-y", "assume_yes", is_flag=True, help="Skip the confirmation prompt.")
292
341
  @click.option("--json", "json_mode", is_flag=True, help="Emit machine-readable JSON to stdout.")
342
+ @click.option(
343
+ "--automation",
344
+ is_flag=True,
345
+ default=False,
346
+ help=(
347
+ "Submit as an automation. The response will appear in channel "
348
+ "notifications without any submitter name — useful when "
349
+ "forwarding third-party form submissions via integrations."
350
+ ),
351
+ )
352
+ @click.option(
353
+ "--anonymous",
354
+ is_flag=True,
355
+ default=False,
356
+ help=(
357
+ "Submit anonymously. The response will appear with a random "
358
+ "generated name instead of your real name."
359
+ ),
360
+ )
361
+ @click.option(
362
+ "--guest-name",
363
+ default=None,
364
+ help="Guest submitter name (used with --automation for third-party submissions).",
365
+ )
366
+ @click.option(
367
+ "--guest-email",
368
+ default=None,
369
+ help="Guest submitter email (used with --automation for third-party submissions).",
370
+ )
371
+ @click.option(
372
+ "--source",
373
+ default=None,
374
+ help="Provenance label for this submission (max 512 chars, e.g. 'workflow:release-pipeline').",
375
+ )
293
376
  def form_submit(
294
377
  form_uuid: str,
295
378
  content: str | None,
296
379
  assume_yes: bool,
297
380
  json_mode: bool,
381
+ automation: bool,
382
+ anonymous: bool,
383
+ guest_name: str | None,
384
+ guest_email: str | None,
385
+ source: str | None,
298
386
  ) -> None:
299
387
  """Submit a form response.
300
388
 
@@ -309,7 +397,29 @@ def form_submit(
309
397
  dailybot form submit <form_uuid>
310
398
  dailybot form submit <form_uuid> --content '{"<question_uuid>":"Yes"}'
311
399
  dailybot form submit <form_uuid> --content '{"<uuid>":"Answer"}' --yes
400
+ dailybot form submit <form_uuid> --content '{"<uuid>":"A"}' --automation
401
+ dailybot form submit <form_uuid> --content '{"<uuid>":"A"}' --anonymous
402
+ dailybot form submit <form_uuid> --content '{"<uuid>":"A"}' --automation \\
403
+ --guest-name "Release Bot" --guest-email "bot@example.com" \\
404
+ --source "workflow:production-deploy"
312
405
  """
406
+ if guest_email and not _EMAIL_RE.match(guest_email):
407
+ print_error(f"Invalid email format: {guest_email}")
408
+ raise SystemExit(1)
409
+ if source and len(source) > MAX_SUBMISSION_SOURCE_LENGTH:
410
+ print_error(
411
+ f"--source is too long ({len(source)} chars, max {MAX_SUBMISSION_SOURCE_LENGTH})."
412
+ )
413
+ raise SystemExit(1)
414
+
415
+ guest_user: dict[str, str] | None = None
416
+ if guest_name or guest_email:
417
+ guest_user = {}
418
+ if guest_name:
419
+ guest_user["full_name"] = guest_name
420
+ if guest_email:
421
+ guest_user["email"] = guest_email
422
+
313
423
  client = require_auth()
314
424
  content_map = resolve_form_content(client, form_uuid, content)
315
425
  execute_form_submit(
@@ -318,6 +428,10 @@ def form_submit(
318
428
  content_map,
319
429
  assume_yes=assume_yes,
320
430
  json_mode=json_mode,
431
+ automation=automation,
432
+ anonymous=anonymous,
433
+ guest_user=guest_user,
434
+ submission_source=source,
321
435
  )
322
436
 
323
437
 
@@ -332,15 +446,48 @@ def form_submit(
332
446
  "--all",
333
447
  "all_responses",
334
448
  is_flag=True,
335
- help="List everyone's responses (admin/owner only; a member gets 403).",
449
+ help="List everyone's responses (requires VIEW_REPORTS permission).",
336
450
  )
337
451
  @click.option("--user", default=None, help="Filter to one user's responses (admin/owner only).")
452
+ @click.option(
453
+ "--source",
454
+ "submission_sources",
455
+ default=None,
456
+ help="Filter by origin (CSV: member, anonymous, automation, public).",
457
+ )
458
+ @click.option(
459
+ "--submitter",
460
+ "submitter_ids",
461
+ default=None,
462
+ help="Filter by submitter UUIDs (CSV, max 50).",
463
+ )
464
+ @click.option(
465
+ "--flow-status",
466
+ "flow_status",
467
+ type=click.Choice(RESPONSE_FLOW_STATUSES, case_sensitive=False),
468
+ default=None,
469
+ help="Approval flow status: pending, approved, denied.",
470
+ )
471
+ @click.option(
472
+ "--order",
473
+ type=click.Choice(RESPONSE_ORDERS, case_sensitive=False),
474
+ default=None,
475
+ help="Sort order: recent or oldest.",
476
+ )
477
+ @click.option(
478
+ "--ascending",
479
+ "--asc",
480
+ "is_ascend",
481
+ is_flag=True,
482
+ default=False,
483
+ help="Sort ascending (alias for --order oldest).",
484
+ )
338
485
  @click.option(
339
486
  "--from", "date_from", default=None, help="Responses on/after this date (YYYY-MM-DD)."
340
487
  )
341
488
  @click.option("--to", "date_to", default=None, help="Responses on/before this date (YYYY-MM-DD).")
342
489
  @click.option(
343
- "--search", "-s", "search", default=None, help="Filter response content (max 256 chars)."
490
+ "--search", "-s", "search", default=None, help="Search content and submitter (max 256 chars)."
344
491
  )
345
492
  @click.option(
346
493
  "--latest",
@@ -354,6 +501,11 @@ def form_responses(
354
501
  state: str | None,
355
502
  all_responses: bool,
356
503
  user: str | None,
504
+ submission_sources: str | None,
505
+ submitter_ids: str | None,
506
+ flow_status: str | None,
507
+ order: str | None,
508
+ is_ascend: bool,
357
509
  date_from: str | None,
358
510
  date_to: str | None,
359
511
  search: str | None,
@@ -367,17 +519,45 @@ def form_responses(
367
519
 
368
520
  \b
369
521
  Acts as you. By default the server returns only responses you authored.
370
- --all / --user surface others' responses and are admin/owner-only
371
- (server-enforced — a member receives 403). --from / --to narrow the window.
522
+ --all / --user surface others' responses and require VIEW_REPORTS
523
+ permission (server-enforced — a member receives 403).
524
+
525
+ \b
526
+ --source filters by origin: member, anonymous, automation, public.
527
+ Multiple values are comma-separated (OR semantics).
528
+ --submitter filters by specific user UUIDs (CSV, max 50, OR semantics).
529
+ --flow-status filters approval status: pending, approved, denied.
372
530
 
373
531
  \b
374
532
  Examples:
375
533
  dailybot form responses <form_uuid>
376
534
  dailybot form responses <form_uuid> --state qa --json
377
535
  dailybot form responses <form_uuid> --all --from 2026-01-01 --to 2026-06-30
536
+ dailybot form responses <form_uuid> --all --source automation
537
+ dailybot form responses <form_uuid> --all --source "member,automation"
538
+ dailybot form responses <form_uuid> --all --flow-status pending
539
+ dailybot form responses <form_uuid> --all --search "deploy" --order oldest
378
540
  dailybot form responses <form_uuid> --user <user_uuid> --json
379
541
  """
380
542
  validate_user_filter(user)
543
+ if submission_sources:
544
+ parts: list[str] = [s.strip() for s in submission_sources.split(",")]
545
+ invalid: list[str] = [s for s in parts if s not in RESPONSE_SOURCES]
546
+ if invalid:
547
+ print_error(
548
+ f"Invalid source(s): {', '.join(invalid)}. Allowed: {', '.join(RESPONSE_SOURCES)}."
549
+ )
550
+ raise SystemExit(1)
551
+ if submitter_ids:
552
+ ids: list[str] = [s.strip() for s in submitter_ids.split(",")]
553
+ if len(ids) > MAX_SUBMITTER_IDS:
554
+ print_error(f"Too many submitter UUIDs ({len(ids)}, max {MAX_SUBMITTER_IDS}).")
555
+ raise SystemExit(1)
556
+
557
+ effective_order: str | None = order
558
+ if is_ascend and not order:
559
+ effective_order = "oldest"
560
+
381
561
  client = require_auth()
382
562
  truncated_search: str | None = search[:256] if search is not None else None
383
563
  meta: dict[str, Any] = {}
@@ -388,6 +568,11 @@ def form_responses(
388
568
  state=state,
389
569
  all_responses=all_responses,
390
570
  user=user,
571
+ submission_sources=submission_sources,
572
+ submitter_user_ids=submitter_ids,
573
+ flow_status=flow_status,
574
+ order=effective_order,
575
+ is_ascend=is_ascend,
391
576
  date_from=date_from,
392
577
  date_to=date_to,
393
578
  search=truncated_search,
@@ -234,6 +234,18 @@ ERROR_CODE_MESSAGES: dict[str, str] = {
234
234
  "search_query_too_long": (
235
235
  "Search term is too long (maximum 256 characters). Shorten it and try again."
236
236
  ),
237
+ "guest_user_required": (
238
+ "This form requires guest submitter info. "
239
+ "Add --guest-name and --guest-email to your command."
240
+ ),
241
+ "invalid_filter": "Unknown --filter value. Use: all, me, public, approval, workflow, archived.",
242
+ "invalid_order": "Unknown --order value. Use: alphabetical, recent, total (forms) or recent, oldest (responses).",
243
+ "invalid_submission_sources": (
244
+ "Unknown --source value. Use: member, anonymous, automation, public (comma-separated)."
245
+ ),
246
+ "invalid_submitter_user_id": "Invalid --submitter UUID format.",
247
+ "too_many_submitter_user_ids": "Too many --submitter UUIDs (max 50).",
248
+ "invalid_flow_status": "Unknown --flow-status value. Use: pending, approved, denied.",
237
249
  "invalid_date_range": (
238
250
  "Invalid date. Use YYYY-MM-DD, and make sure the start date is on or before the end date."
239
251
  ),
@@ -500,19 +500,27 @@ def execute_form_list(
500
500
  *,
501
501
  json_mode: bool = False,
502
502
  include_archived: bool = False,
503
+ include_questions: bool = False,
503
504
  owner: str | None = None,
505
+ filter_scope: str | None = None,
506
+ order: str | None = None,
507
+ is_ascend: bool = False,
504
508
  spec: QuerySpec | None = None,
505
509
  ) -> list[dict[str, Any]] | None:
506
510
  """Fetch and display forms visible to the user (with question counts)."""
507
511
  query: QuerySpec = spec or QuerySpec()
508
512
  fetch_all: bool = resolve_fetch_all(query)
509
513
  meta: dict[str, Any] = {}
514
+ always_include_questions: bool = include_questions or not json_mode
510
515
  try:
511
516
  with console.status("Fetching forms..."):
512
517
  forms: list[dict[str, Any]] = client.list_forms(
513
- include_questions=True,
518
+ include_questions=always_include_questions,
514
519
  include_archived=include_archived,
515
520
  owner=owner,
521
+ filter_scope=filter_scope,
522
+ order=order,
523
+ is_ascend=is_ascend,
516
524
  search=query.params.get("search"),
517
525
  start_date=query.params.get("start_date"),
518
526
  end_date=query.params.get("end_date"),
@@ -542,6 +550,10 @@ def execute_form_submit(
542
550
  form_name: str | None = None,
543
551
  assume_yes: bool = False,
544
552
  json_mode: bool = False,
553
+ automation: bool = False,
554
+ anonymous: bool = False,
555
+ guest_user: dict[str, str] | None = None,
556
+ submission_source: str | None = None,
545
557
  ) -> None:
546
558
  """Submit a form response."""
547
559
  resolved_name: str = form_name or form_uuid
@@ -556,8 +568,19 @@ def execute_form_submit(
556
568
  summary_lines: list[str] = [
557
569
  f"Form: {resolved_name}",
558
570
  f"Form UUID: {form_uuid}",
559
- "Answers:",
560
571
  ]
572
+ if automation:
573
+ summary_lines.append("Mode: automation (no submitter shown in channel)")
574
+ if anonymous:
575
+ summary_lines.append("Mode: anonymous (random name shown in channel)")
576
+ if guest_user:
577
+ guest_desc: str = guest_user.get("full_name", "")
578
+ if guest_user.get("email"):
579
+ guest_desc += f" <{guest_user['email']}>" if guest_desc else guest_user["email"]
580
+ summary_lines.append(f"Guest: {guest_desc}")
581
+ if submission_source:
582
+ summary_lines.append(f"Source: {submission_source}")
583
+ summary_lines.append("Answers:")
561
584
  for question_uuid, answer in content_map.items():
562
585
  summary_lines.append(f" {question_uuid}: {answer}")
563
586
 
@@ -568,6 +591,10 @@ def execute_form_submit(
568
591
  result: dict[str, Any] = client.submit_form_response(
569
592
  form_uuid=form_uuid,
570
593
  content=content_map,
594
+ automation=automation,
595
+ anonymous=anonymous,
596
+ guest_user=guest_user,
597
+ submission_source=submission_source,
571
598
  )
572
599
  except APIError as exc:
573
600
  exit_for_api_error(exc, json_mode)
@@ -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")
@@ -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.3.0
3
+ Version: 3.5.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.3.0"
7
+ version = "3.5.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