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.
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/PKG-INFO +1 -1
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/api_client.py +41 -2
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/form.py +192 -7
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/public_api_helpers.py +12 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/user_scoped_actions.py +29 -2
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/config.py +18 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/main.py +10 -2
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli.egg-info/PKG-INFO +1 -1
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/pyproject.toml +1 -1
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/LICENSE +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/README.md +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/__init__.py +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/__init__.py +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/agent.py +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/ask.py +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/auth.py +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/authoring_helpers.py +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/channels.py +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/chat.py +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/checkin.py +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/config.py +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/conversation.py +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/hook.py +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/identity.py +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/interactive.py +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/interactive_chat.py +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/kudos.py +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/query_options.py +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/status.py +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/team.py +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/uninstall.py +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/update.py +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/upgrade.py +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/user.py +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/version.py +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/commands/workflow.py +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/display.py +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/install_method.py +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/ledger.py +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/tui/__init__.py +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/tui/app.py +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/tui/commands.py +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/tui/conversation.py +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli/tui/intents.py +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli.egg-info/SOURCES.txt +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli.egg-info/dependency_links.txt +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli.egg-info/entry_points.txt +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli.egg-info/requires.txt +0 -0
- {dailybot_cli-3.3.0 → dailybot_cli-3.5.0}/dailybot_cli.egg-info/top_level.txt +0 -0
- {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
|
+
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=
|
|
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 (
|
|
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="
|
|
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
|
|
371
|
-
(server-enforced — a member receives 403).
|
|
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=
|
|
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
|
+
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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|