unique-sdk 2026.32.0.dev5__tar.gz → 2026.32.0.dev7__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 (91) hide show
  1. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/PKG-INFO +1 -1
  2. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/pyproject.toml +1 -1
  3. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/_api_requestor.py +8 -8
  4. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/_util.py +98 -0
  5. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/cli.py +2 -1
  6. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/shell.py +4 -7
  7. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/skills/unique-cli-elicitation/SKILL.md +13 -14
  8. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/README.md +0 -0
  9. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/__init__.py +0 -0
  10. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/_api_resource.py +0 -0
  11. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/_api_version.py +0 -0
  12. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/_error.py +0 -0
  13. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/_http_client.py +0 -0
  14. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/_list_object.py +0 -0
  15. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/_object_classes.py +0 -0
  16. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/_request_options.py +0 -0
  17. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/_unique_object.py +0 -0
  18. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/_unique_ql.py +0 -0
  19. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/_unique_response.py +0 -0
  20. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/_version.py +0 -0
  21. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/_webhook.py +0 -0
  22. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/api_resources/__init__.py +0 -0
  23. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/api_resources/_acronyms.py +0 -0
  24. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/api_resources/_agentic_table.py +0 -0
  25. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/api_resources/_analytics_order.py +0 -0
  26. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/api_resources/_benchmarking.py +0 -0
  27. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/api_resources/_briefing.py +0 -0
  28. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/api_resources/_chat_completion.py +0 -0
  29. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/api_resources/_content.py +0 -0
  30. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/api_resources/_dynamic_frontend.py +0 -0
  31. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/api_resources/_elicitation.py +0 -0
  32. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/api_resources/_embedding.py +0 -0
  33. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/api_resources/_event.py +0 -0
  34. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/api_resources/_folder.py +0 -0
  35. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/api_resources/_group.py +0 -0
  36. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/api_resources/_integrated.py +0 -0
  37. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/api_resources/_llm_models.py +0 -0
  38. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/api_resources/_mcp.py +0 -0
  39. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/api_resources/_message.py +0 -0
  40. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/api_resources/_message_assessment.py +0 -0
  41. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/api_resources/_message_execution.py +0 -0
  42. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/api_resources/_message_log.py +0 -0
  43. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/api_resources/_message_tool.py +0 -0
  44. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/api_resources/_module.py +0 -0
  45. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/api_resources/_scheduled_task.py +0 -0
  46. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/api_resources/_search.py +0 -0
  47. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/api_resources/_search_string.py +0 -0
  48. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/api_resources/_short_term_memory.py +0 -0
  49. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/api_resources/_space.py +0 -0
  50. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/api_resources/_user.py +0 -0
  51. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/api_resources/_web_search.py +0 -0
  52. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/__init__.py +0 -0
  53. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/__main__.py +0 -0
  54. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/commands/__init__.py +0 -0
  55. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/commands/_citation_manifest.py +0 -0
  56. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/commands/agentic_table.py +0 -0
  57. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/commands/browser.py +0 -0
  58. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/commands/cite_file.py +0 -0
  59. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/commands/dynamic_frontend.py +0 -0
  60. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/commands/elicitation.py +0 -0
  61. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/commands/files.py +0 -0
  62. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/commands/folders.py +0 -0
  63. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/commands/mcp.py +0 -0
  64. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/commands/navigation.py +0 -0
  65. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/commands/read.py +0 -0
  66. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/commands/scheduled_tasks.py +0 -0
  67. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/commands/search.py +0 -0
  68. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/commands/subagent.py +0 -0
  69. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/commands/web_search.py +0 -0
  70. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/commands/web_search_config.py +0 -0
  71. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/config.py +0 -0
  72. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/formatting.py +0 -0
  73. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/identity.py +0 -0
  74. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/metadata_filter.py +0 -0
  75. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/skills/unique-cli-agentic-table/SKILL.md +0 -0
  76. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/skills/unique-cli-dynamic-frontend/SKILL.md +0 -0
  77. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/skills/unique-cli-file-management/SKILL.md +0 -0
  78. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/skills/unique-cli-mcp/SKILL.md +0 -0
  79. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/skills/unique-cli-scheduled-tasks/SKILL.md +0 -0
  80. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/skills/unique-cli-search/SKILL.md +0 -0
  81. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/skills/unique-cli-subagent/SKILL.md +0 -0
  82. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/skills/unique-cli-uploaded-search/SKILL.md +0 -0
  83. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/skills/unique-cli-web-search/SKILL.md +0 -0
  84. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/cli/state.py +0 -0
  85. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/utils/analytics_order_run.py +0 -0
  86. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/utils/benchmarking_run.py +0 -0
  87. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/utils/chat_history.py +0 -0
  88. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/utils/chat_in_space.py +0 -0
  89. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/utils/file_io.py +0 -0
  90. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/utils/sources.py +0 -0
  91. {unique_sdk-2026.32.0.dev5 → unique_sdk-2026.32.0.dev7}/unique_sdk/utils/token.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: unique-sdk
3
- Version: 2026.32.0.dev5
3
+ Version: 2026.32.0.dev7
4
4
  Summary:
5
5
  Author: Martin Fadler, Konstantin Krauss, Andreas Hauri
6
6
  Author-email: Martin Fadler <martin.fadler@unique.ch>, Konstantin Krauss <konstantin@unique.ch>, Andreas Hauri <andreas@unique.ch>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "unique_sdk"
3
- version = "2026.32.0.dev5"
3
+ version = "2026.32.0.dev7"
4
4
  description = ""
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -194,8 +194,7 @@ class APIRequestor(object):
194
194
  )
195
195
 
196
196
  _util.log_info("Request to Unique", method=method, path=abs_url)
197
- _util.log_debug(
198
- "Request details",
197
+ _util.log_request_details(
199
198
  data=post_data,
200
199
  headers=headers,
201
200
  api_version=self.api_version,
@@ -206,7 +205,7 @@ class APIRequestor(object):
206
205
  )
207
206
 
208
207
  _util.log_info("Unique response", path=abs_url, status=rcode)
209
- _util.log_debug("Unique response body", body=rcontent)
208
+ _util.log_response_body(body=rcontent)
210
209
 
211
210
  if "Request-Id" in rheaders:
212
211
  request_id = rheaders["Request-Id"]
@@ -230,11 +229,11 @@ class APIRequestor(object):
230
229
  )
231
230
 
232
231
  _util.log_info("Async request to Unique", method=method, path=abs_url)
233
- _util.log_debug(
234
- "Async request details",
232
+ _util.log_request_details(
235
233
  data=post_data,
236
234
  headers=headers,
237
235
  api_version=self.api_version,
236
+ message="Async request details",
238
237
  )
239
238
 
240
239
  rcontent, rcode, rheaders = await self._client.request_async(
@@ -242,7 +241,7 @@ class APIRequestor(object):
242
241
  )
243
242
 
244
243
  _util.log_info("Unique response", path=abs_url, status=rcode)
245
- _util.log_debug("Unique response body", body=rcontent)
244
+ _util.log_response_body(body=rcontent)
246
245
 
247
246
  if "Request-Id" in rheaders:
248
247
  request_id = rheaders["Request-Id"]
@@ -364,7 +363,8 @@ class APIRequestor(object):
364
363
  except Exception as e:
365
364
  raise _error.APIError(
366
365
  "Invalid response body from API: %s "
367
- "(HTTP response code was %d)" % (rbody, rcode),
366
+ "(HTTP response code was %d)"
367
+ % (_util.redacted_body_for_error_message(rbody), rcode),
368
368
  cast(bytes, rbody),
369
369
  rcode,
370
370
  rheaders,
@@ -399,7 +399,7 @@ class APIRequestor(object):
399
399
  error_code=status,
400
400
  error_type=error_data.get("type"),
401
401
  error_message=error_data.get("message"),
402
- error_params=error_data.get("params"),
402
+ error_params=_util.redacted_error_params(error_data.get("params")),
403
403
  )
404
404
 
405
405
  error = cause.get("error", {}) if cause else {}
@@ -9,6 +9,7 @@ import random
9
9
  import re
10
10
  import sys
11
11
  import time
12
+ from collections.abc import Mapping
12
13
  from functools import wraps
13
14
  from typing import (
14
15
  Any,
@@ -33,6 +34,8 @@ UNIQUE_LOG = os.environ.get("UNIQUE_LOG")
33
34
 
34
35
  logger: logging.Logger = logging.getLogger("unique")
35
36
 
37
+ _REDACTED = "<redacted>"
38
+
36
39
 
37
40
  def _console_log_level():
38
41
  if unique_sdk.log in ["debug", "info"]:
@@ -42,6 +45,42 @@ def _console_log_level():
42
45
  return None
43
46
 
44
47
 
48
+ def _insecure_log_payloads_enabled() -> bool:
49
+ return os.environ.get("INSECURE_UNIQUE_SDK_LOG_PAYLOADS") == "true"
50
+
51
+
52
+ def _payload_byte_size(value: Any) -> int:
53
+ if value is None:
54
+ return 0
55
+ if isinstance(value, (bytes, bytearray)):
56
+ return len(value)
57
+ if isinstance(value, str):
58
+ return len(value.encode("utf-8"))
59
+ return len(str(value).encode("utf-8"))
60
+
61
+
62
+ def _headers_for_log(headers: Mapping[str, str] | None) -> dict[str, str] | str | None:
63
+ """Return headers for debug logs; Authorization is always redacted."""
64
+ if not _insecure_log_payloads_enabled():
65
+ return _REDACTED
66
+ if headers is None:
67
+ return None
68
+ try:
69
+ sanitized = dict(headers)
70
+ except (TypeError, ValueError):
71
+ return _REDACTED
72
+ for key in list(sanitized):
73
+ if str(key).lower() == "authorization":
74
+ sanitized[key] = _REDACTED
75
+ return sanitized
76
+
77
+
78
+ def _body_for_log(body: Any) -> Any:
79
+ if _insecure_log_payloads_enabled():
80
+ return body
81
+ return _REDACTED
82
+
83
+
45
84
  def log_debug(message, **params):
46
85
  msg = logfmt(dict(message=message, **params))
47
86
  if _console_log_level() == "debug":
@@ -56,6 +95,65 @@ def log_info(message, **params):
56
95
  logger.info(msg)
57
96
 
58
97
 
98
+ def log_request_details(
99
+ *,
100
+ data: Any,
101
+ headers: Mapping[str, str] | None,
102
+ api_version: str | None,
103
+ message: str = "Request details",
104
+ ) -> None:
105
+ if _insecure_log_payloads_enabled():
106
+ log_debug(
107
+ message,
108
+ data=data,
109
+ headers=_headers_for_log(headers),
110
+ api_version=api_version,
111
+ )
112
+ return
113
+ log_debug(
114
+ message,
115
+ data=_REDACTED,
116
+ headers=_REDACTED,
117
+ api_version=api_version,
118
+ payload_bytes=_payload_byte_size(data),
119
+ )
120
+
121
+
122
+ def log_response_body(*, body: Any) -> None:
123
+ if _insecure_log_payloads_enabled():
124
+ log_debug("Unique response body", body=body)
125
+ return
126
+ log_debug(
127
+ "Unique response body",
128
+ body=_body_for_log(body),
129
+ payload_bytes=_payload_byte_size(body),
130
+ )
131
+
132
+
133
+ def redacted_body_for_error_message(body: Any) -> Any:
134
+ """Body representation safe to embed in exception *messages*.
135
+
136
+ Exception messages surface at ERROR (retry logs, tracebacks) regardless
137
+ of LOG_LEVEL, so the raw body must not be embedded unless the insecure
138
+ opt-in is set. The full body stays available programmatically on the
139
+ exception's ``http_body`` attribute.
140
+ """
141
+ if _insecure_log_payloads_enabled():
142
+ return body
143
+ return f"<redacted {_payload_byte_size(body)} bytes>"
144
+
145
+
146
+ def redacted_error_params(params: Any) -> Any:
147
+ """Error ``params`` for the INFO-level 'Unique error received' log.
148
+
149
+ API validation errors commonly echo submitted values in ``params``, so
150
+ they are redacted unless the insecure opt-in is set.
151
+ """
152
+ if params is None or _insecure_log_payloads_enabled():
153
+ return params
154
+ return _REDACTED
155
+
156
+
59
157
  def logfmt(props):
60
158
  def fmt(key, val):
61
159
  # Handle case where val is a bytes or bytesarray
@@ -1373,7 +1373,8 @@ def elicit_ask(
1373
1373
  @click.option(
1374
1374
  "--mode",
1375
1375
  type=click.Choice(["FORM", "URL"], case_sensitive=False),
1376
- required=True,
1376
+ default="FORM",
1377
+ show_default=True,
1377
1378
  help="Elicitation display mode.",
1378
1379
  )
1379
1380
  @click.option(
@@ -97,7 +97,7 @@ OVERVIEW_HELP = textwrap.dedent("""\
97
97
  --placeholder-text <text> Text on the placeholder thinking step
98
98
  --cleanup collapse|delete How to tear down the placeholder
99
99
  elicit create <message> [opts] Fire-and-forget create
100
- --mode FORM|URL Display mode (required)
100
+ --mode FORM|URL Display mode (default: FORM)
101
101
  --tool-name / -t <name> Tool label (required)
102
102
  --schema <json> JSON schema (FORM mode)
103
103
  --url <url> External URL (URL mode)
@@ -811,7 +811,7 @@ class UniqueShell(cmd.Cmd):
811
811
  --tool-name / -t <name> Tool label shown in the UI
812
812
  --schema <json> JSON schema for form fields
813
813
  --url <url> External URL (create with --mode URL)
814
- --mode FORM|URL Display mode (create only, required)
814
+ --mode FORM|URL Display mode (create only, default: FORM)
815
815
  --chat-id / -c <id> Associated chat ID
816
816
  --message-id / -m <id> Associated message ID
817
817
  --expires-in <seconds> Auto-expire the request (create only)
@@ -1033,18 +1033,15 @@ class UniqueShell(cmd.Cmd):
1033
1033
  message = opts["message"]
1034
1034
  if not message:
1035
1035
  self._print(
1036
- "Usage: elicit create <message> --mode FORM|URL --tool-name <name> [options]"
1036
+ "Usage: elicit create <message> [--mode FORM|URL] --tool-name <name> [options]"
1037
1037
  )
1038
1038
  return
1039
- if not opts["mode"]:
1040
- self._print("Error: --mode is required (FORM or URL).")
1041
- return
1042
1039
  if not opts["tool_name"]:
1043
1040
  self._print("Error: --tool-name / -t is required.")
1044
1041
  return
1045
1042
 
1046
1043
  create_kwargs: dict[str, Any] = {
1047
- "mode": opts["mode"],
1044
+ "mode": opts["mode"] or "FORM",
1048
1045
  "message": message,
1049
1046
  "tool_name": opts["tool_name"],
1050
1047
  "schema": opts["schema"],
@@ -161,22 +161,19 @@ unique-cli elicit ask "Which report format do you want?" \
161
161
 
162
162
  Always use this before `rm`, `rmdir -r`, mass uploads, or anything irreversible.
163
163
 
164
+ Use an **empty-properties schema** — the UI's Confirm/Cancel buttons ARE the
165
+ consent. Do **not** add a boolean `confirm` field: the button and the checkbox
166
+ are two separate signals, and a user who presses Confirm with the box unchecked
167
+ would show as **Accepted** in the UI while you would read `confirm: false` and
168
+ wrongly treat it as declined.
169
+
164
170
  ```bash
165
- unique-cli elicit ask "Confirm deleting /Archive/2024 and everything inside it" \
171
+ unique-cli elicit ask "Permanently delete /Archive/2024 and everything inside it? Confirming deletes it immediately — this cannot be undone." \
166
172
  --chat-id "$UNIQUE_CHAT_ID" \
167
- --schema '{
168
- "type": "object",
169
- "properties": {
170
- "confirm": {
171
- "type": "boolean",
172
- "description": "Tick to permanently delete"
173
- }
174
- },
175
- "required": ["confirm"]
176
- }'
173
+ --schema '{"type": "object", "properties": {}}'
177
174
  ```
178
175
 
179
- Proceed **only** if the response contains `"confirm": true`. Treat `DECLINED`, `CANCELLED`, `EXPIRED`, or `confirm: false` all as "do not proceed" -- tell the user you stopped and return control.
176
+ Proceed **only** if the `Status:` is `ACCEPTED`. Treat `DECLINED`, `CANCELLED`, or `EXPIRED` all as "do not proceed" -- tell the user you stopped and return control. Put everything the user needs to decide into the message text, since the form has no fields.
180
177
 
181
178
  ### Structured form (multiple fields)
182
179
 
@@ -201,7 +198,9 @@ The table below documents `elicit ask`'s flags. `elicit create` takes the
201
198
  same `--chat-id`, `--message-id`, `--tool-name`, `--schema`, `--metadata`,
202
199
  and `--assistant-id` flags, but uses `--expires-in <seconds>` instead of
203
200
  `--timeout`/`--poll-interval` (those two apply only to `elicit wait`, which
204
- you call separately in the polling pattern).
201
+ you call separately in the polling pattern). `elicit create` also takes
202
+ `--mode FORM|URL` (default: `FORM` — you only need to pass it for URL
203
+ elicitations).
205
204
 
206
205
  | Option | Short | Default | Description |
207
206
  |--------|-------|---------|-------------|
@@ -296,7 +295,7 @@ esac
296
295
 
297
296
  - Always set `"required"` for fields you actually need -- this guarantees the user cannot submit an empty form.
298
297
  - Use `enum` for closed choices so the UI can render a selector.
299
- - Use `"type": "boolean"` for confirmations; treat `true` as "go ahead", everything else as "stop".
298
+ - For pure yes/no confirmations use an **empty-properties schema** (`{"type": "object", "properties": {}}`) and gate on `Status: ACCEPTED` — never add a boolean `confirm` field (the Confirm button and the checkbox are two separate signals that can disagree). Reserve `"type": "boolean"` for genuine data fields where `false` is a valid answer the user can still submit with Confirm (e.g. `include_appendix`).
300
299
  - Add short `description` strings -- they are shown as help text next to each field.
301
300
  - Keep schemas small. Ask at most 5 questions in a single elicitation; if you need more, split the flow so the user is not confused by an oversized form.
302
301