unique-sdk 2026.44.0.dev9__tar.gz → 2026.44.0.dev10__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 (92) hide show
  1. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/PKG-INFO +1 -1
  2. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/pyproject.toml +1 -1
  3. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/api_resources/_scheduled_task.py +5 -1
  4. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/cli.py +21 -3
  5. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/commands/scheduled_tasks.py +8 -0
  6. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/formatting.py +15 -2
  7. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/shell.py +15 -3
  8. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/skills/unique-cli-scheduled-tasks/SKILL.md +56 -45
  9. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/README.md +0 -0
  10. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/__init__.py +0 -0
  11. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/_api_requestor.py +0 -0
  12. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/_api_resource.py +0 -0
  13. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/_api_version.py +0 -0
  14. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/_error.py +0 -0
  15. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/_http_client.py +0 -0
  16. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/_list_object.py +0 -0
  17. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/_object_classes.py +0 -0
  18. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/_request_options.py +0 -0
  19. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/_unique_object.py +0 -0
  20. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/_unique_ql.py +0 -0
  21. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/_unique_response.py +0 -0
  22. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/_util.py +0 -0
  23. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/_version.py +0 -0
  24. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/_webhook.py +0 -0
  25. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/api_resources/__init__.py +0 -0
  26. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/api_resources/_acronyms.py +0 -0
  27. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/api_resources/_agentic_table.py +0 -0
  28. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/api_resources/_analytics_order.py +0 -0
  29. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/api_resources/_benchmarking.py +0 -0
  30. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/api_resources/_briefing.py +0 -0
  31. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/api_resources/_chat_completion.py +0 -0
  32. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/api_resources/_content.py +0 -0
  33. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/api_resources/_context_memory.py +0 -0
  34. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/api_resources/_elicitation.py +0 -0
  35. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/api_resources/_embedding.py +0 -0
  36. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/api_resources/_event.py +0 -0
  37. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/api_resources/_folder.py +0 -0
  38. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/api_resources/_group.py +0 -0
  39. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/api_resources/_integrated.py +0 -0
  40. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/api_resources/_llm_models.py +0 -0
  41. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/api_resources/_mcp.py +0 -0
  42. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/api_resources/_message.py +0 -0
  43. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/api_resources/_message_assessment.py +0 -0
  44. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/api_resources/_message_execution.py +0 -0
  45. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/api_resources/_message_log.py +0 -0
  46. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/api_resources/_message_tool.py +0 -0
  47. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/api_resources/_module.py +0 -0
  48. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/api_resources/_search.py +0 -0
  49. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/api_resources/_search_string.py +0 -0
  50. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/api_resources/_short_term_memory.py +0 -0
  51. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/api_resources/_space.py +0 -0
  52. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/api_resources/_user.py +0 -0
  53. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/api_resources/_web_search.py +0 -0
  54. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/__init__.py +0 -0
  55. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/__main__.py +0 -0
  56. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/commands/__init__.py +0 -0
  57. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/commands/_citation_manifest.py +0 -0
  58. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/commands/agentic_table.py +0 -0
  59. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/commands/agentic_table_write.py +0 -0
  60. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/commands/browser.py +0 -0
  61. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/commands/cite_file.py +0 -0
  62. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/commands/elicitation.py +0 -0
  63. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/commands/files.py +0 -0
  64. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/commands/folders.py +0 -0
  65. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/commands/mcp.py +0 -0
  66. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/commands/navigation.py +0 -0
  67. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/commands/read.py +0 -0
  68. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/commands/search.py +0 -0
  69. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/commands/subagent.py +0 -0
  70. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/commands/web_search.py +0 -0
  71. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/commands/web_search_config.py +0 -0
  72. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/config.py +0 -0
  73. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/identity.py +0 -0
  74. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/metadata_filter.py +0 -0
  75. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/paths.py +0 -0
  76. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/skills/unique-cli-agentic-table/SKILL.md +0 -0
  77. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/skills/unique-cli-elicitation/SKILL.md +0 -0
  78. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/skills/unique-cli-file-management/SKILL.md +0 -0
  79. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/skills/unique-cli-mcp/SKILL.md +0 -0
  80. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/skills/unique-cli-search/SKILL.md +0 -0
  81. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/skills/unique-cli-subagent/SKILL.md +0 -0
  82. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/skills/unique-cli-uploaded-search/SKILL.md +0 -0
  83. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/skills/unique-cli-web-search/SKILL.md +0 -0
  84. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/state.py +0 -0
  85. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/cli/workspace.py +0 -0
  86. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/utils/analytics_order_run.py +0 -0
  87. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/utils/benchmarking_run.py +0 -0
  88. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/utils/chat_history.py +0 -0
  89. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/utils/chat_in_space.py +0 -0
  90. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/utils/file_io.py +0 -0
  91. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/unique_sdk/utils/sources.py +0 -0
  92. {unique_sdk-2026.44.0.dev9 → unique_sdk-2026.44.0.dev10}/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.44.0.dev9
3
+ Version: 2026.44.0.dev10
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.44.0.dev9"
3
+ version = "2026.44.0.dev10"
4
4
  description = ""
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -13,7 +13,8 @@ class ScheduledTask(APIResource["ScheduledTask"]):
13
13
  """Cron-tab style scheduled task that triggers an assistant on a recurring schedule.
14
14
 
15
15
  Each task defines a cron expression, an assistant to execute, an optional
16
- chat to continue, and a prompt to send.
16
+ chat to continue, and a prompt to send. The cron is evaluated in
17
+ ``timezone`` when set, otherwise in UTC.
17
18
  """
18
19
 
19
20
  @classproperty
@@ -34,6 +35,7 @@ class ScheduledTask(APIResource["ScheduledTask"]):
34
35
  prompt: str
35
36
  name: NotRequired[str]
36
37
  chatId: NotRequired[str | None]
38
+ timezone: NotRequired[str]
37
39
  enabled: NotRequired[bool]
38
40
 
39
41
  class ModifyParams(RequestOptions):
@@ -42,6 +44,7 @@ class ScheduledTask(APIResource["ScheduledTask"]):
42
44
  prompt: NotRequired[str]
43
45
  name: NotRequired[str | None]
44
46
  chatId: NotRequired[str | None]
47
+ timezone: NotRequired[str | None]
45
48
  enabled: NotRequired[bool]
46
49
 
47
50
  class DeletedObject(TypedDict):
@@ -58,6 +61,7 @@ class ScheduledTask(APIResource["ScheduledTask"]):
58
61
  assistantId: str
59
62
  assistantName: str | None
60
63
  chatId: str | None
64
+ timezone: str | None
61
65
  prompt: str
62
66
  enabled: bool
63
67
  lastRunAt: str | None
@@ -1039,6 +1039,12 @@ def schedule_get(ctx: click.Context, task_id: str) -> None:
1039
1039
  required=True,
1040
1040
  help="Prompt text sent to the assistant on each trigger.",
1041
1041
  )
1042
+ @click.option(
1043
+ "--timezone",
1044
+ "-tz",
1045
+ required=True,
1046
+ help="IANA timezone the cron runs in, e.g. Europe/Zurich. Use UTC for UTC.",
1047
+ )
1042
1048
  @click.option(
1043
1049
  "--name",
1044
1050
  "-n",
@@ -1062,6 +1068,7 @@ def schedule_create(
1062
1068
  cron: str,
1063
1069
  assistant_id: str,
1064
1070
  prompt: str,
1071
+ timezone: str,
1065
1072
  name: str | None,
1066
1073
  chat_id: str | None,
1067
1074
  disabled: bool,
@@ -1071,7 +1078,7 @@ def schedule_create(
1071
1078
  \b
1072
1079
  Defines a cron schedule, an assistant, and a prompt. The task
1073
1080
  will execute the assistant with the given prompt on every cron
1074
- match.
1081
+ match. The cron is evaluated in --timezone.
1075
1082
 
1076
1083
  \b
1077
1084
  Cron expression format (5 fields):
@@ -1081,18 +1088,20 @@ def schedule_create(
1081
1088
  Examples:
1082
1089
  unique-cli schedule create \\
1083
1090
  --cron "0 9 * * 1-5" \\
1091
+ --timezone Europe/Zurich \\
1084
1092
  --assistant clx1abc2d0001abcdef123456 \\
1085
1093
  --prompt "Generate the daily sales report" \\
1086
1094
  --name "Daily sales report"
1087
1095
 
1088
1096
  unique-cli schedule create \\
1089
- -c "*/15 * * * *" -a clx1abc -p "Check inbox" --disabled
1097
+ -c "*/15 * * * *" -tz Europe/Zurich -a clx1abc -p "Check inbox" --disabled
1090
1098
  """
1091
1099
  output = cmd_schedule_create(
1092
1100
  LazyState.get(ctx),
1093
1101
  cron=cron,
1094
1102
  assistant_id=assistant_id,
1095
1103
  prompt=prompt,
1104
+ timezone=timezone,
1096
1105
  name=name,
1097
1106
  chat_id=chat_id,
1098
1107
  enabled=not disabled,
@@ -1111,6 +1120,12 @@ def schedule_create(
1111
1120
  @click.option("--prompt", "-p", default=None, help="Updated prompt text.")
1112
1121
  @click.option("--name", "-n", default=None, help="Updated label (use 'none' to clear).")
1113
1122
  @click.option("--chat-id", default=None, help="Updated chat ID (use 'none' to clear).")
1123
+ @click.option(
1124
+ "--timezone",
1125
+ "-tz",
1126
+ default=None,
1127
+ help="IANA timezone the cron runs in (required with --cron).",
1128
+ )
1114
1129
  @click.option("--enable", is_flag=True, default=False, help="Enable the task.")
1115
1130
  @click.option("--disable", is_flag=True, default=False, help="Disable the task.")
1116
1131
  @click.pass_context
@@ -1122,6 +1137,7 @@ def schedule_update(
1122
1137
  prompt: str | None,
1123
1138
  name: str | None,
1124
1139
  chat_id: str | None,
1140
+ timezone: str | None,
1125
1141
  enable: bool,
1126
1142
  disable: bool,
1127
1143
  ) -> None:
@@ -1130,10 +1146,11 @@ def schedule_update(
1130
1146
  \b
1131
1147
  TASK_ID is the scheduled task identifier. Only the fields you
1132
1148
  provide will be changed; everything else stays the same.
1149
+ --timezone is required whenever --cron is given.
1133
1150
 
1134
1151
  \b
1135
1152
  Examples:
1136
- unique-cli schedule update clx3ghi4f --cron "0 9 * * 1-5"
1153
+ unique-cli schedule update clx3ghi4f --cron "0 9 * * 1-5" --timezone Europe/Zurich
1137
1154
  unique-cli schedule update clx3ghi4f --disable
1138
1155
  unique-cli schedule update clx3ghi4f --enable --prompt "New prompt"
1139
1156
  unique-cli schedule update clx3ghi4f --chat-id none
@@ -1167,6 +1184,7 @@ def schedule_update(
1167
1184
  prompt=prompt,
1168
1185
  name=resolved_name,
1169
1186
  chat_id=resolved_chat_id,
1187
+ timezone=timezone,
1170
1188
  enabled=enabled,
1171
1189
  )
1172
1190
  click.echo(output)
@@ -46,6 +46,7 @@ def cmd_schedule_create(
46
46
  cron: str,
47
47
  assistant_id: str,
48
48
  prompt: str,
49
+ timezone: str,
49
50
  *,
50
51
  name: str | None = None,
51
52
  chat_id: str | None = None,
@@ -57,6 +58,7 @@ def cmd_schedule_create(
57
58
  "cronExpression": cron,
58
59
  "assistantId": assistant_id,
59
60
  "prompt": prompt,
61
+ "timezone": timezone,
60
62
  "enabled": enabled,
61
63
  }
62
64
  if name is not None:
@@ -84,9 +86,13 @@ def cmd_schedule_update(
84
86
  prompt: str | None = None,
85
87
  name: str | None = None,
86
88
  chat_id: str | None = None,
89
+ timezone: str | None = None,
87
90
  enabled: bool | None = None,
88
91
  ) -> str:
89
92
  """Update an existing scheduled task."""
93
+ if cron is not None and timezone is None:
94
+ return f"{SCHEDULE_ERROR_PREFIX} --timezone is required when changing --cron"
95
+
90
96
  try:
91
97
  params: dict[str, Any] = {}
92
98
  if cron is not None:
@@ -100,6 +106,8 @@ def cmd_schedule_update(
100
106
  params["name"] = name if name != "" else None
101
107
  if chat_id is not None:
102
108
  params["chatId"] = chat_id if chat_id != "" else None
109
+ if timezone is not None:
110
+ params["timezone"] = timezone
103
111
  if enabled is not None:
104
112
  params["enabled"] = enabled
105
113
 
@@ -162,6 +162,7 @@ def format_scheduled_task(task: ScheduledTask) -> str:
162
162
  ["ID:", getattr(task, "id", "?")],
163
163
  ["Name:", getattr(task, "name", None) or "(falls back to prompt)"],
164
164
  ["Cron:", getattr(task, "cronExpression", "?")],
165
+ ["Timezone:", getattr(task, "timezone", None) or "UTC"],
165
166
  [
166
167
  "Assistant:",
167
168
  getattr(task, "assistantName", None) or getattr(task, "assistantId", "?"),
@@ -186,6 +187,7 @@ def format_scheduled_tasks(tasks: list[ScheduledTask]) -> str:
186
187
  for t in tasks:
187
188
  task_id = getattr(t, "id", "?")
188
189
  cron = getattr(t, "cronExpression", "?")
190
+ timezone = getattr(t, "timezone", None) or "UTC"
189
191
  enabled = "on" if getattr(t, "enabled", False) else "off"
190
192
  assistant = getattr(t, "assistantName", None) or getattr(t, "assistantId", "?")
191
193
  prompt = getattr(t, "prompt", "")
@@ -195,9 +197,20 @@ def format_scheduled_tasks(tasks: list[ScheduledTask]) -> str:
195
197
  # Mirrors the web UI: an unnamed task is identified by its prompt snippet.
196
198
  label = getattr(t, "name", None) or snippet
197
199
  last_run = _format_date(getattr(t, "lastRunAt", None))
198
- rows.append([enabled, label, cron, assistant, snippet, task_id, last_run])
200
+ rows.append(
201
+ [enabled, label, cron, timezone, assistant, snippet, task_id, last_run]
202
+ )
199
203
 
200
- header = ["STATUS", "NAME", "CRON", "ASSISTANT", "PROMPT", "ID", "LAST RUN"]
204
+ header = [
205
+ "STATUS",
206
+ "NAME",
207
+ "CRON",
208
+ "TIMEZONE",
209
+ "ASSISTANT",
210
+ "PROMPT",
211
+ "ID",
212
+ "LAST RUN",
213
+ ]
201
214
  lines = [f"{len(tasks)} scheduled task(s):\n"]
202
215
  all_rows = [header] + rows
203
216
  lines.extend(_pad_columns(all_rows))
@@ -1113,12 +1113,14 @@ class UniqueShell(cmd.Cmd):
1113
1113
  --cron / -c <expr> Cron expression (required)
1114
1114
  --assistant / -a <id> Assistant ID (required)
1115
1115
  --prompt / -p <text> Prompt text (required)
1116
+ --timezone / -tz <tz> IANA timezone (required)
1116
1117
  --name / -n <text> Short label shown in the UI (optional)
1117
1118
  --chat-id <id> Chat to continue (optional)
1118
1119
  --disabled Create in disabled state
1119
1120
 
1120
1121
  Update options:
1121
1122
  --cron / -c <expr> Updated cron expression
1123
+ --timezone / -tz <tz> IANA timezone (required with --cron)
1122
1124
  --assistant / -a <id> Updated assistant ID
1123
1125
  --prompt / -p <text> Updated prompt
1124
1126
  --name / -n <text> Updated label ('none' to clear)
@@ -1129,7 +1131,7 @@ class UniqueShell(cmd.Cmd):
1129
1131
  Examples:
1130
1132
  /> schedule list
1131
1133
  /> schedule get clx3ghi4f0003mnopqr345678
1132
- /> schedule create -c "0 9 * * 1-5" -a clx1abc -p "Daily report" -n "Daily report"
1134
+ /> schedule create -c "0 9 * * 1-5" -tz Europe/Zurich -a clx1abc -p "Daily report" -n "Daily report"
1133
1135
  /> schedule update clx3ghi4f --disable
1134
1136
  /> schedule delete clx3ghi4f0003mnopqr345678
1135
1137
  """
@@ -1179,6 +1181,7 @@ class UniqueShell(cmd.Cmd):
1179
1181
  cron: str | None = None
1180
1182
  assistant_id: str | None = None
1181
1183
  prompt: str | None = None
1184
+ timezone: str | None = None
1182
1185
  name: str | None = None
1183
1186
  chat_id: str | None = None
1184
1187
  disabled = False
@@ -1194,6 +1197,9 @@ class UniqueShell(cmd.Cmd):
1194
1197
  elif parts[i] in ("--prompt", "-p") and i + 1 < len(parts):
1195
1198
  prompt = parts[i + 1]
1196
1199
  i += 2
1200
+ elif parts[i] in ("--timezone", "-tz") and i + 1 < len(parts):
1201
+ timezone = parts[i + 1]
1202
+ i += 2
1197
1203
  elif parts[i] in ("--name", "-n") and i + 1 < len(parts):
1198
1204
  name = parts[i + 1]
1199
1205
  i += 2
@@ -1207,10 +1213,10 @@ class UniqueShell(cmd.Cmd):
1207
1213
  self._print(f"Unknown option: {parts[i]}")
1208
1214
  return
1209
1215
 
1210
- if not cron or not assistant_id or not prompt:
1216
+ if not cron or not assistant_id or not prompt or not timezone:
1211
1217
  self._print(
1212
1218
  "Usage: schedule create --cron <expr> --assistant <id> --prompt <text> "
1213
- "[--name <text>] [--chat-id <id>] [--disabled]"
1219
+ "--timezone <tz> [--name <text>] [--chat-id <id>] [--disabled]"
1214
1220
  )
1215
1221
  return
1216
1222
 
@@ -1220,6 +1226,7 @@ class UniqueShell(cmd.Cmd):
1220
1226
  cron=cron,
1221
1227
  assistant_id=assistant_id,
1222
1228
  prompt=prompt,
1229
+ timezone=timezone,
1223
1230
  name=name,
1224
1231
  chat_id=chat_id,
1225
1232
  enabled=not disabled,
@@ -1233,6 +1240,7 @@ class UniqueShell(cmd.Cmd):
1233
1240
  prompt: str | None = None
1234
1241
  name: str | None = None
1235
1242
  chat_id: str | None = None
1243
+ timezone: str | None = None
1236
1244
  enable = False
1237
1245
  disable = False
1238
1246
 
@@ -1247,6 +1255,9 @@ class UniqueShell(cmd.Cmd):
1247
1255
  elif parts[i] in ("--prompt", "-p") and i + 1 < len(parts):
1248
1256
  prompt = parts[i + 1]
1249
1257
  i += 2
1258
+ elif parts[i] in ("--timezone", "-tz") and i + 1 < len(parts):
1259
+ timezone = parts[i + 1]
1260
+ i += 2
1250
1261
  elif parts[i] in ("--name", "-n") and i + 1 < len(parts):
1251
1262
  name = "" if parts[i + 1].lower() == "none" else parts[i + 1]
1252
1263
  i += 2
@@ -1282,6 +1293,7 @@ class UniqueShell(cmd.Cmd):
1282
1293
  prompt=prompt,
1283
1294
  name=name,
1284
1295
  chat_id=chat_id,
1296
+ timezone=timezone,
1285
1297
  enabled=enabled,
1286
1298
  )
1287
1299
  )
@@ -73,31 +73,35 @@ months, after you have told the user it would not.
73
73
 
74
74
  ## Timezone handling (critical)
75
75
 
76
- **The platform runs all cron schedules in UTC.** The user almost certainly thinks in their local timezone. You **must** account for this difference or tasks will fire at the wrong time.
76
+ **Write the cron in the user's local wall-clock time and always pass `--timezone`.** The
77
+ platform evaluates the cron in that IANA timezone and keeps the local run time across daylight
78
+ saving changes. Never convert local times to UTC. Never mention UTC equivalents or daylight
79
+ saving shifts to the user.
77
80
 
78
- **Before creating or updating a schedule:**
79
- 1. Check if you already know the user's timezone (from prior conversation, system info, or profile).
80
- 2. If you do **not** know it, **ask**: "What timezone are you in? (e.g. Europe/Zurich, US/Eastern, Asia/Tokyo)"
81
- 3. Convert the user's desired local time to UTC before building the cron expression.
82
- 4. Confirm back in the **user's local timezone**, and mention the UTC equivalent.
81
+ The CLI **rejects** `schedule create` without `--timezone`, and rejects `schedule update` that
82
+ changes `--cron` without `--timezone`.
83
83
 
84
- **Conversion examples (Europe/Zurich, CET = UTC+1 / CEST = UTC+2):**
85
-
86
- | User says (local) | Season | UTC offset | Cron (UTC) |
87
- |--------------------|--------|------------|------------|
88
- | "9:00 AM" | Winter (CET) | UTC+1 | `0 8 * * *` |
89
- | "9:00 AM" | Summer (CEST) | UTC+2 | `0 7 * * *` |
90
- | "5:30 PM" | Winter (CET) | UTC+1 | `30 16 * * *` |
91
- | "midnight" | Winter (CET) | UTC+1 | `0 23 * * *` (previous day in UTC) |
84
+ **Where the timezone comes from, in order:**
85
+ 1. The `User's timezone` line in the system prompt (browser-detected IANA name).
86
+ 2. A timezone already established earlier in the conversation.
87
+ 3. Otherwise, **ask**: "What timezone are you in? (e.g. Europe/Zurich, America/New_York, Asia/Tokyo)"
92
88
 
93
89
  **Rules:**
94
- - Always tell the user the schedule in **their local time** first, then add "(UTC: HH:MM)" in parentheses.
95
- - If the timezone has DST, warn the user: "Note: when daylight saving changes, the task will shift by one hour in your local time since the platform runs in UTC."
96
- - For interval-based schedules like "every 15 minutes" or "every 2 hours", timezone conversion is not needed — just mention that these run around the clock in UTC.
97
- - Store the user's timezone mentally for the rest of the conversation so you don't ask again.
90
+ - Use **IANA names only** (`Europe/Zurich`, `America/New_York`, `Asia/Tokyo`). Never use
91
+ abbreviations (`CET`, `EST`) or UTC offsets (`+02:00`, `UTC+2`). The platform rejects them.
92
+ - Pass `--timezone <IANA>` on every `create`, and on every `update` that changes `--cron`.
93
+ - Write the cron for the user's local time. "9:00 AM in Zurich" → `-c "0 9 * * *"` `-tz Europe/Zurich`.
94
+ - Confirm back in the user's local time only. Do **not** add "(UTC: …)" or warn about DST.
95
+ - For interval schedules like "every 15 minutes", still pass `--timezone`.
96
+ - Store the user's timezone for the rest of the conversation so you don't ask again.
97
+ - To schedule explicitly in UTC, pass `--timezone UTC`.
98
+
99
+ **Legacy tasks without a timezone:** If `schedule get` shows `Timezone: UTC` on a task the user
100
+ thinks of in local time, any update to its schedule **must** pass the new local cron and
101
+ `--timezone` together. Changing only the timezone reinterprets the old UTC cron as local time.
98
102
 
99
103
  **Example confirmation:**
100
- > "Done — scheduled to run **every weekday at 9:00 AM (your time, Europe/Zurich)**. That's 7:00 AM UTC in summer / 8:00 AM UTC in winter. Note: the exact local time will shift by 1 hour when daylight saving changes."
104
+ > "Done — scheduled to run **every weekday at 9:00 AM your time (Europe/Zurich)**."
101
105
 
102
106
  ## Agent workflow for creating a task
103
107
 
@@ -114,11 +118,12 @@ Before running `schedule create`, **resolve / ask the user**:
114
118
  1. **Assistant** — Use `$UNIQUE_ASSISTANT_ID` (the current assistant). Do not
115
119
  ask. Do not run `unique-cli schedule list` to "find" one. Do not start an
116
120
  elicitation. If the env var is missing, surface that as an error and stop.
117
- 2. **Timezone** — If not already known, ask for it first. You need this to convert times correctly.
121
+ 2. **Timezone** — Prefer the `User's timezone` line in the system prompt. If
122
+ missing and not already known, ask. You need an IANA name for `--timezone`.
118
123
  3. **New chat or existing chat?**
119
124
  - **New chat each run** (default) — omit `--chat-id`. Each execution creates a fresh chat.
120
125
  - **Continue an existing chat** — the user must provide a chat ID (starts with `chat_`). Pass it via `--chat-id`.
121
- 4. **When should it run?** — Ask in plain language (e.g. "every weekday at 9 AM"). Convert to a UTC cron expression.
126
+ 4. **When should it run?** — Ask in plain language (e.g. "every weekday at 9 AM"). Translate to a **local-time** cron expression; do not convert to UTC.
122
127
  5. **Prompt text** — what the assistant should do on each trigger.
123
128
  6. **Name** — **you write it, do not ask for it.** Derive a short, descriptive label from the
124
129
  prompt (a few words, title-style, e.g. "Daily sales report", "Weekly pipeline review").
@@ -145,7 +150,7 @@ Only pass `--chat-id` when the user explicitly wants to continue a specific chat
145
150
  unique-cli schedule list
146
151
  ```
147
152
 
148
- Output shows a table with status, name, cron expression, assistant, prompt snippet, task ID, and last run time. A task saved without a name shows its prompt snippet in the name column.
153
+ Output shows a table with status, name, cron expression, timezone, assistant, prompt snippet, task ID, and last run time. A task saved without a name shows its prompt snippet in the name column.
149
154
 
150
155
  ## Get Task Details
151
156
 
@@ -159,13 +164,14 @@ Shows full details of a single task including all fields.
159
164
 
160
165
  Always pass `--assistant "$UNIQUE_ASSISTANT_ID"` so the task runs on the
161
166
  current assistant. Do not substitute a literal `assistant_…` ID and do not
162
- ask the user to provide one.
167
+ ask the user to provide one. Always pass `--timezone` with an IANA name.
163
168
 
164
169
  ### New chat each run (default — no `--chat-id`)
165
170
 
166
171
  ```bash
167
172
  unique-cli schedule create \
168
173
  --cron "0 9 * * 1-5" \
174
+ --timezone Europe/Zurich \
169
175
  --assistant "$UNIQUE_ASSISTANT_ID" \
170
176
  --prompt "Generate the daily sales report and email it to the team" \
171
177
  --name "Daily sales report"
@@ -176,6 +182,7 @@ unique-cli schedule create \
176
182
  ```bash
177
183
  unique-cli schedule create \
178
184
  --cron "0 9 * * 1-5" \
185
+ --timezone Europe/Zurich \
179
186
  --assistant "$UNIQUE_ASSISTANT_ID" \
180
187
  --chat-id chat_b7ze6mpv0edy324yhjj1d92t \
181
188
  --prompt "Append today's numbers to the running report" \
@@ -186,7 +193,8 @@ unique-cli schedule create \
186
193
 
187
194
  | Option | Short | Required | Description |
188
195
  |--------|-------|----------|-------------|
189
- | `--cron` | `-c` | Yes | 5-field cron expression |
196
+ | `--cron` | `-c` | Yes | 5-field cron expression in the user's local time |
197
+ | `--timezone` | `-tz` | Yes | IANA timezone the cron runs in (e.g. `Europe/Zurich`). Use `UTC` for UTC. |
190
198
  | `--assistant` | `-a` | Yes | Assistant ID to execute (starts with `assistant_`). **Always pass `"$UNIQUE_ASSISTANT_ID"`** — never ask the user. |
191
199
  | `--prompt` | `-p` | Yes | Prompt text sent each run |
192
200
  | `--name` | `-n` | No | Short label shown in the UI (≤128 chars). **Always pass one** — you generate it; without it the UI falls back to the prompt text |
@@ -195,7 +203,7 @@ unique-cli schedule create \
195
203
 
196
204
  ### Translating user requests to cron
197
205
 
198
- The user will describe schedules in plain language. Translate silently:
206
+ The user will describe schedules in plain language. Translate silently into **local-time** cron:
199
207
 
200
208
  | User says | Cron expression |
201
209
  |-----------|-----------------|
@@ -230,10 +238,11 @@ Day-of-week mapping: 0=Sun, 1=Mon, 2=Tue, 3=Wed, 4=Thu, 5=Fri, 6=Sat, 7=Sun
230
238
  ## Update a Scheduled Task
231
239
 
232
240
  Only the fields you provide are changed; everything else stays the same.
241
+ When changing `--cron`, you **must** also pass `--timezone`.
233
242
 
234
243
  ```bash
235
- # Change the schedule
236
- unique-cli schedule update <task_id> --cron "0 9 * * 1-5"
244
+ # Change the schedule (timezone required with --cron)
245
+ unique-cli schedule update <task_id> --cron "0 9 * * 1-5" --timezone Europe/Zurich
237
246
 
238
247
  # Disable a task
239
248
  unique-cli schedule update <task_id> --disable
@@ -249,7 +258,8 @@ unique-cli schedule update <task_id> --chat-id none
249
258
 
250
259
  | Option | Short | Description |
251
260
  |--------|-------|-------------|
252
- | `--cron` | `-c` | Updated cron expression |
261
+ | `--cron` | `-c` | Updated cron expression (requires `--timezone`) |
262
+ | `--timezone` | `-tz` | IANA timezone the cron runs in (required with `--cron`) |
253
263
  | `--assistant` | `-a` | Updated assistant ID (starts with `assistant_`) |
254
264
  | `--prompt` | `-p` | Updated prompt text |
255
265
  | `--name` | `-n` | Updated label (`none` to clear it and fall back to the prompt) |
@@ -271,40 +281,38 @@ This action cannot be undone.
271
281
 
272
282
  User: "Schedule a daily sales report every weekday at 9 AM"
273
283
 
274
- Agent (if timezone unknown): "What timezone are you in?"
275
-
276
- User: "Zurich"
277
-
278
- Agent thinks: Europe/Zurich, currently CEST (UTC+2), so 9:00 AM local = 7:00 AM UTC → cron `0 7 * * 1-5`. The assistant is the current one (`$UNIQUE_ASSISTANT_ID`); no need to ask.
284
+ Agent thinks: System prompt has `User's timezone: Europe/Zurich`. Local cron is `0 9 * * 1-5`. The assistant is the current one (`$UNIQUE_ASSISTANT_ID`); no need to ask.
279
285
 
280
286
  Run:
281
287
  ```bash
282
288
  unique-cli schedule create \
283
- -c "0 7 * * 1-5" \
289
+ -c "0 9 * * 1-5" \
290
+ -tz Europe/Zurich \
284
291
  -a "$UNIQUE_ASSISTANT_ID" \
285
292
  -p "Generate the daily sales report and email it to the team" \
286
293
  -n "Daily sales report"
287
294
  ```
288
295
 
289
- Respond: "Done — I created **Daily sales report**, running **every weekday (Mon–Fri) at 9:00 AM your time (Europe/Zurich)**. That's 7:00 AM UTC. A new chat will be created for each run. Note: when clocks change for daylight saving, the task will shift by one hour in your local time."
296
+ Respond: "Done — I created **Daily sales report**, running **every weekday (Mon–Fri) at 9:00 AM your time (Europe/Zurich)**. A new chat will be created for each run."
290
297
 
291
298
  ### Set up a recurring task that continues the same chat
292
299
 
293
300
  User: "I want to add daily numbers to an existing report chat at 9 AM"
294
301
 
295
- Agent thinks: Already know timezone is Europe/Zurich (CEST), 9 AM local = 7 AM UTC. Assistant is the current one (`$UNIQUE_ASSISTANT_ID`).
302
+ Agent thinks: Already know timezone is Europe/Zurich. Local cron is `0 9 * * 1-5`. Assistant is the current one (`$UNIQUE_ASSISTANT_ID`).
296
303
 
297
304
  Run:
298
305
  ```bash
299
306
  unique-cli schedule create \
300
- -c "0 7 * * 1-5" \
307
+ -c "0 9 * * 1-5" \
308
+ -tz Europe/Zurich \
301
309
  -a "$UNIQUE_ASSISTANT_ID" \
302
310
  --chat-id chat_b7ze6mpv0edy324yhjj1d92t \
303
311
  -p "Append today's numbers to the running report" \
304
312
  -n "Running report top-up"
305
313
  ```
306
314
 
307
- Respond: "Done — I created **Running report top-up**, running **every weekday (Mon–Fri) at 9:00 AM your time** (7:00 AM UTC), continuing chat `chat_b7ze6mpv0edy324yhjj1d92t`."
315
+ Respond: "Done — I created **Running report top-up**, running **every weekday (Mon–Fri) at 9:00 AM your time**, continuing chat `chat_b7ze6mpv0edy324yhjj1d92t`."
308
316
 
309
317
  ### Pause and resume a task
310
318
 
@@ -322,25 +330,25 @@ Respond: "The task is **active** again and will run on its next scheduled time."
322
330
 
323
331
  User: "Change it to run every hour instead"
324
332
 
325
- Agent thinks: "every hour" is interval-based, no timezone conversion needed.
333
+ Agent thinks: Interval schedule; still pass the known timezone.
326
334
 
327
335
  Run:
328
336
  ```bash
329
- unique-cli schedule update <task_id> --cron "0 * * * *"
337
+ unique-cli schedule update <task_id> --cron "0 * * * *" --timezone Europe/Zurich
330
338
  ```
331
339
 
332
- Respond: "Updated — the task now runs **every hour**, on the hour (UTC). Since this is interval-based, it runs at the same wall-clock intervals regardless of timezone."
340
+ Respond: "Updated — the task now runs **every hour**, on the hour."
333
341
 
334
342
  User: "Actually, make it run at 6:30 PM my time on Fridays"
335
343
 
336
- Agent thinks: Europe/Zurich CEST (UTC+2), 6:30 PM = 16:30 UTC → cron `30 16 * * 5`
344
+ Agent thinks: Europe/Zurich, local 6:30 PM → cron `30 18 * * 5`.
337
345
 
338
346
  Run:
339
347
  ```bash
340
- unique-cli schedule update <task_id> --cron "30 16 * * 5"
348
+ unique-cli schedule update <task_id> --cron "30 18 * * 5" --timezone Europe/Zurich
341
349
  ```
342
350
 
343
- Respond: "Updated — the task now runs **every Friday at 6:30 PM your time** (4:30 PM UTC)."
351
+ Respond: "Updated — the task now runs **every Friday at 6:30 PM your time**."
344
352
 
345
353
  ## SDK Usage (Python)
346
354
 
@@ -357,6 +365,7 @@ task = unique_sdk.ScheduledTask.create(
357
365
  user_id="user_123",
358
366
  company_id="company_456",
359
367
  cronExpression="0 9 * * 1-5",
368
+ timezone="Europe/Zurich",
360
369
  assistantId="assistant_cvj3fd7x8hpt1hfp0akqu1rq",
361
370
  prompt="Generate daily report",
362
371
  name="Daily report",
@@ -367,6 +376,7 @@ task = unique_sdk.ScheduledTask.create(
367
376
  user_id="user_123",
368
377
  company_id="company_456",
369
378
  cronExpression="0 9 * * 1-5",
379
+ timezone="Europe/Zurich",
370
380
  assistantId="assistant_cvj3fd7x8hpt1hfp0akqu1rq",
371
381
  chatId="chat_b7ze6mpv0edy324yhjj1d92t",
372
382
  prompt="Append today's data",
@@ -401,7 +411,8 @@ unique_sdk.ScheduledTask.delete(
401
411
  )
402
412
  ```
403
413
 
404
- All methods have `*_async` variants for async usage.
414
+ All methods have `*_async` variants for async usage. Omitting `timezone` on create keeps
415
+ UTC behaviour for backward compatibility; the CLI always requires it.
405
416
 
406
417
  ## Prerequisites
407
418