1use super::types::Message;
22use std::fmt::{Display, Formatter, Result};
23
24impl Display for Message {
25 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
41 let text = match self {
42 Message::AutostartEnabled => "Autostart has been enabled. Kasl will start automatically on system boot.".to_string(),
44 Message::AutostartEnabledUser => "Autostart has been enabled for current user. Kasl will start when you log in.".to_string(),
45 Message::AutostartDisabled => "Autostart has been disabled.".to_string(),
46 Message::AutostartAlreadyDisabled => "Autostart was already disabled.".to_string(),
47 Message::AutostartEnableFailed(error) => format!("Failed to enable autostart: {}", error),
48 Message::AutostartDisableFailed(error) => format!("Failed to disable autostart: {}", error),
49 Message::AutostartStatus(status) => format!("Autostart is currently: {}", status),
50 Message::AutostartRequiresAdmin => "Administrator privileges required for system-level autostart. Trying user-level alternative...".to_string(),
51 Message::AutostartCheckingAlternative => "Checking alternative autostart method...".to_string(),
52
53 Message::TaskCreated => "Task created successfully".to_string(),
55 Message::TaskUpdated => "Task updated successfully".to_string(),
56 Message::TaskNotFound => "Task not found".to_string(),
57 Message::TaskUpdateFailed => "Failed to update task".to_string(),
58 Message::TasksDeletedCount(count) => format!("Deleted {} task(s) successfully.", count),
59 Message::TasksNotFoundForDate(date) => format!("Tasks not found for {}, report not sent.", date),
60 Message::TasksNotFoundSad => "No tasks found.".to_string(),
61 Message::TasksHeader => "Tasks:".to_string(),
62 Message::TasksDiscoverySummary { incomplete, jira, gitlab } => {
63 let mut parts = Vec::new();
64 if *incomplete > 0 {
65 parts.push(format!("{} incomplete", incomplete));
66 }
67 if *jira > 0 {
68 parts.push(format!("{} jira", jira));
69 }
70 if *gitlab > 0 {
71 parts.push(format!("{} gitlab", gitlab));
72 }
73 format!("Found: {}", parts.join(", "))
74 }
75 Message::TasksDiscoverySeparator => "──────── other ────────".to_string(),
76 Message::TasksDiscoverySearchingIncomplete => "Looking for incomplete tasks...".to_string(),
77 Message::TasksDiscoveryFetchingExternal => "Fetching GitLab commits and Jira issues...".to_string(),
78 Message::NoTaskIdsProvided => "No task IDs provided for deletion.".to_string(),
79 Message::TasksNotFoundForIds(ids) => format!("No tasks found with IDs: {:?}", ids),
80 Message::TasksToBeDeleted => "The following tasks will be deleted:".to_string(),
81 Message::ConfirmDeleteTask => "Are you sure you want to delete this task?".to_string(),
82 Message::ConfirmDeleteTasks(count) => format!("Are you sure you want to delete {} tasks?", count),
83 Message::ConfirmDeleteAllTodayTasks(count) => format!("Are you sure you want to delete ALL {} tasks for today?", count),
84 Message::ConfirmDeleteAllTodayTasksFinal => "This action cannot be undone. Are you REALLY sure?".to_string(),
85 Message::NoTasksForToday => "No tasks found for today.".to_string(),
86 Message::TaskNotFoundWithId(id) => format!("Task with ID {} not found.", id),
87 Message::CurrentTaskState => "Current task:".to_string(),
88 Message::TaskEditPreview => "Task after changes:".to_string(),
89 Message::ConfirmTaskUpdate => "Save changes?".to_string(),
90 Message::NoChangesDetected => "No changes detected.".to_string(),
91 Message::NoTasksSelected => "No tasks selected for editing.".to_string(),
92 Message::SelectTasksToEdit => "Select tasks to edit (space to select, enter to confirm)".to_string(),
93 Message::EditingTask(name) => format!("Editing task: {}", name),
94 Message::TaskUpdatedWithName(name) => format!("Task '{}' updated successfully.", name),
95 Message::TaskSkippedNoChanges(name) => format!("Task '{}' - no changes, skipped.", name),
96 Message::TaskEditingCompleted => "Task editing completed.".to_string(),
97 Message::PromptTaskNameEdit => "Task name".to_string(),
98 Message::PromptTaskCommentEdit => "Comment (optional)".to_string(),
99 Message::PromptTaskCompletenessEdit => "Completeness (0-100)".to_string(),
100 Message::TaskCompletenessRange => "Completeness must be between 0 and 100".to_string(),
101
102 Message::WorkdayEnded => "Workday ended for today.".to_string(),
104 Message::WorkdayNeverStarted(date) => {
105 format!(
106 "No workday was started on {}, so there is nothing to end. `kasl watch` opens the day, and `kasl report` shows what is recorded.",
107 date
108 )
109 }
110 Message::WorkdayNotFoundForDate(date) => format!("No workday record found for {}", date),
111 Message::WorkdayCreateFailed => "Failed to create workday".to_string(),
112 Message::WorkdayStarting(date) => format!("Starting workday for {}", date),
113 Message::WorkdayCouldNotFindAfterFinalizing(date) => {
114 format!("Could not find workday for {} after finalizing.", date)
115 }
116
117 Message::ConfigSaved => "Configuration saved successfully".to_string(),
119 Message::ConfigDeleted => "Configuration deleted successfully".to_string(),
120 Message::ConfigParseError => "Failed to parse configuration".to_string(),
121 Message::ConfigSaveError => "Failed to save configuration".to_string(),
122 Message::ConfigModuleGitLab => "GitLab settings".to_string(),
123 Message::ConfigModuleJira => "Jira settings".to_string(),
124 Message::ConfigModuleSiServer => "SiServer settings".to_string(),
125 Message::ConfigModuleMonitor => "Monitor settings".to_string(),
126 Message::ConfigModuleServer => "Server settings".to_string(),
127 Message::ConfigModuleProductivity => "Productivity settings".to_string(),
128 Message::ConfigModuleTaskDiscovery => "Task discovery settings".to_string(),
129 Message::ConfigModuleJiraInbox => "Jira inbox settings".to_string(),
130 Message::TaskDiscoveryIgnoreListHeader => "Current ignore list:".to_string(),
131 Message::TaskDiscoveryIgnoreListEmpty => "Ignore list is empty.".to_string(),
132 Message::TaskDiscoveryIgnoreNameAdded(name) => format!("Added '{}' to ignore list.", name),
133 Message::TaskDiscoveryIgnoreNameExists(name) => format!("'{}' is already in the ignore list.", name),
134 Message::TaskDiscoveryIgnoreNamesAdded(count) => {
135 format!("Added {} name(s) to the discovery ignore list.", count)
136 }
137
138 Message::DailyReportSent(date) => {
140 format!(
141 "Your report dated {} has been successfully submitted\nWait for a message to your email address",
142 date
143 )
144 }
145 Message::MonthlyReportSent(date) => {
146 format!(
147 "Your monthly report dated {} has been successfully submitted\nWait for a message to your email address",
148 date
149 )
150 }
151 Message::MonthlyReportTriggered => "It's the last working day of the month. Submitting the monthly report as well...".to_string(),
152 Message::ReportSendFailed(status) => format!("Failed to send report. Status: {}", status),
153 Message::MonthlyReportSendFailed(status) => format!("Failed to send monthly report. Status: {}", status),
154 Message::ReportHeader(date) => format!("Report for {}", date),
155 Message::WorkingHoursForMonth(month_year) => format!("Working hours for {}", month_year),
156 Message::ReportPayloadHeading { url, date } => {
157 format!("This is what {} would send to {}, as a multipart form:", date, url)
158 }
159
160 Message::ExportingData(data, format) => format!("Exporting {} in {} format...", data, format),
162 Message::ExportCompleted(path) => format!("Export completed successfully: {}", path),
163 Message::ExportingAllData => "Exporting all data...".to_string(),
164
165 Message::TemplateCreated(name) => format!("Template '{}' created successfully.", name),
167 Message::TemplateUpdated(name) => format!("Template '{}' updated successfully.", name),
168 Message::TemplateDeleted(name) => format!("Template '{}' deleted successfully.", name),
169 Message::TemplateNotFound(name) => format!("Template '{}' not found.", name),
170 Message::TemplateAlreadyExists(name) => format!("Template '{}' already exists.", name),
171 Message::TemplateCreateFailed => "Failed to create template.".to_string(),
172 Message::NoTemplatesFound => "No templates found.".to_string(),
173 Message::TemplateListHeader => "Task Templates:".to_string(),
174 Message::SelectTemplateToEdit => "Select template to edit".to_string(),
175 Message::SelectTemplateToDelete => "Select template to delete".to_string(),
176 Message::ConfirmDeleteTemplate(name) => format!("Delete template '{}'?", name),
177 Message::EditingTemplate(name) => format!("Editing template: {}", name),
178 Message::NoTemplatesMatchingQuery(query) => format!("No templates matching '{}'", query),
179 Message::TemplateSearchResults(query) => format!("Templates matching '{}':", query),
180 Message::SelectTemplateAction => "What would you like to do?".to_string(),
181 Message::PromptTemplateName => "Template name (unique identifier)".to_string(),
182 Message::PromptTemplateTaskName => "Task name".to_string(),
183 Message::PromptTemplateComment => "Comment (optional)".to_string(),
184 Message::PromptTemplateCompleteness => "Default completeness (0-100)".to_string(),
185 Message::CreatingTaskFromTemplate(name) => format!("Creating task from template '{}'", name),
186 Message::SelectTemplate => "Select a template".to_string(),
187 Message::CreateTemplateFirst => "Create templates with 'kasl template add'".to_string(),
188
189 Message::TagCreated(name) => format!("Tag '{}' created successfully.", name),
191 Message::TagUpdated(name) => format!("Tag '{}' updated successfully.", name),
192 Message::TagDeleted(name) => format!("Tag '{}' deleted successfully.", name),
193 Message::TagNotFound(name) => format!("Tag '{}' not found.", name),
194 Message::TagAlreadyExists(name) => format!("Tag '{}' already exists.", name),
195 Message::NoTagsFound => "No tags found.".to_string(),
196 Message::TagListHeader => "Tags:".to_string(),
197 Message::EditingTag(name) => format!("Editing tag: {}", name),
198 Message::SelectTagAction => "What would you like to do?".to_string(),
199 Message::SelectTagToEdit => "Select tag to edit".to_string(),
200 Message::SelectTagToDelete => "Select tag to delete".to_string(),
201 Message::ConfirmDeleteTag(name) => format!("Delete tag '{}'?", name),
202 Message::ConfirmDeleteTagWithTasks(name, count) => format!("Tag '{}' is used by {} task(s). Delete anyway?", name, count),
203 Message::PromptTagName => "Tag name".to_string(),
204 Message::PromptTagColor => "Tag color (e.g., blue, green, red)".to_string(),
205 Message::NoTasksWithTag(tag) => format!("No tasks found with tag '{}'.", tag),
206 Message::TasksWithTag(tag) => format!("Tasks with tag '{}':", tag),
207 Message::TagsAddedToTask(tags) => format!("Tags added: {}", tags),
208
209 Message::ShortIntervalsDetected(count, duration) => format!("Found {} short work intervals (total: {})", count, duration),
211 Message::PromptMinWorkInterval => "Minimum work interval (minutes)".to_string(),
212
213 Message::WorkdayUpdateFailed => "Failed to update workday.".to_string(),
215
216 Message::PausesTitle(date) => format!("Pauses for {}", date),
218
219 Message::MonitorStarted {
221 pause_threshold,
222 poll_interval,
223 activity_threshold,
224 } => {
225 format!(
226 "Monitor is running with pause threshold {}s, poll interval {}ms, activity threshold {}s",
227 pause_threshold, poll_interval, activity_threshold
228 )
229 }
230 Message::MonitorExitedNormally => "Monitor exited normally".to_string(),
231 Message::MonitorShuttingDown => "Shutting down monitor...".to_string(),
232 Message::MonitorError(error) => format!("Monitor error: {}", error),
233 Message::MonitorTaskPanicked(error) => format!("Monitor task panicked: {}", error),
234 Message::PauseStarted => "Pause Start".to_string(),
235 Message::PauseEnded => "Pause End".to_string(),
236
237 Message::WatcherStarted(pid) => format!("Watcher started in the background (PID: {}).", pid),
239 Message::WatcherStopped(pid) => format!("Watcher process (PID: {}) stopped successfully.", pid),
240 Message::WatcherNotRunning => "Watcher is not running.".to_string(),
241 Message::WatcherNotRunningPidNotFound => "Watcher does not appear to be running (PID file not found).".to_string(),
242 Message::WatcherStartingForeground => "Starting watcher in foreground... Press Ctrl+C to exit.".to_string(),
243 Message::WatcherAlreadyRunningPid(pid) => format!("A watcher is already running (PID: {}) - left it as it is.", pid),
244 Message::WatcherAlreadyRunning => "A watcher is already running for this user - stop it with `kasl watch --stop`, then run this again.".to_string(),
245 Message::WatcherStoppingExisting(pid) => format!("Stopping existing watcher (PID: {})...", pid),
246 Message::WatcherFailedToStopExisting(error) => format!("Warning: Failed to stop existing daemon: {}", error),
247 Message::WatcherReceivedSigterm => "Received SIGTERM, shutting down gracefully...".to_string(),
248 Message::WatcherReceivedSigint => "Received SIGINT, shutting down gracefully...".to_string(),
249 Message::WatcherReceivedCtrlC => "Received Ctrl+C, shutting down gracefully...".to_string(),
250 Message::WatcherCtrlCListenFailed(error) => format!("Failed to listen for Ctrl+C: {}", error),
251 Message::WatcherSignalHandlingNotSupported => "Warning: Signal handling not supported on this platform".to_string(),
252 Message::DaemonModeNotSupported => "Daemon mode is not supported on this platform.".to_string(),
253 Message::FailedToGetCurrentExecutable => "Failed to get the path of the current executable".to_string(),
254 Message::FailedToCreateSigtermHandler => "Failed to create SIGTERM handler".to_string(),
255 Message::FailedToCreateSigintHandler => "Failed to create SIGINT handler".to_string(),
256
257 Message::UpdateAvailable { app_name, latest } => {
259 format!(
260 "A new version of {} is available: v{}\nUpgrade now by running: {} self-update",
261 app_name, latest, app_name
262 )
263 }
264 Message::UpdateCompleted { app_name, version } => {
265 format!("The {} application has been successfully updated to version {}!", app_name, version)
266 }
267 Message::NoUpdateRequired => "No update required. You are using the latest version!".to_string(),
268 Message::UpdateDownloadUrlNotSet => "Download URL not set".to_string(),
269 Message::UpdateLatestTagNotFound(url) => format!("Could not resolve the latest release tag from {}", url),
270 Message::WatcherStoppingForUpdate => "Stopping watcher for update...".to_string(),
271 Message::WatcherRestartingAfterUpdate => "Restarting watcher after update...".to_string(),
272 Message::WatcherStoppingForConfig => "Stopping watcher to apply new configuration...".to_string(),
273 Message::WatcherRestartingAfterConfig => "Restarting watcher with updated configuration...".to_string(),
274 Message::WatcherRestarted => "Watcher successfully restarted with new configuration".to_string(),
275 Message::WatcherRestartFailed { error } => format!("Warning: Failed to restart watcher: {}", error),
276 Message::UpdateBinaryNotFoundInArchive => "Binary not found in the release archive.".to_string(),
277
278 Message::WrongPassword(count) => format!("You entered the wrong password {} times!", count),
280
281 Message::GitlabFetchFailed(error) => format!("[kasl] Failed to get GitLab events: {}", error),
283 Message::GitlabUserIdFailed(error) => format!("[kasl] Failed to get GitLab user ID: {}", error),
284 Message::JiraFetchFailed(error) => format!("[kasl] Failed to get Jira issues: {}", error),
285 Message::SiServerConfigNotFound => "SiServer configuration not found in config file.".to_string(),
286 Message::SiServerSessionFailed(error) => format!("[kasl] Failed to get SiServer session for rest dates: {}", error),
287 Message::SiServerRestDatesFailed(error) => format!("[kasl] Failed to request rest dates: {}", error),
288 Message::SiServerRestDatesParsingFailed(error) => format!("[kasl] Failed to parse rest dates response: {}", error),
289
290 Message::KaslServerReached { url, version } => format!("{} is kasl-server {}", url, version),
292 Message::KaslServerConnected { user_name, agent_name } => {
293 format!("Connected as {} (agent '{}')", user_name, agent_name)
294 }
295 Message::KaslServerConfigured(url) => format!("Configured server: {}", url),
296 Message::KaslServerDatabaseUnhealthy(state) => {
297 format!(
298 "The server answered, but reports its database as '{}' - uploads will fail until that clears.",
299 state
300 )
301 }
302 Message::KaslServerUnreachable(error) => format!("Cannot reach the server: {}", error),
303 Message::KaslServerTokenRejected(error) => format!("The stored token no longer works: {}", error),
304 Message::KaslServerUrlNeedsScheme(url) => {
305 format!(
306 "'{}' has no scheme - write http:// or https:// so it is clear whether the token crosses the network in the clear.",
307 url
308 )
309 }
310 Message::KaslServerTokenEmpty => "No token entered; nothing was changed.".to_string(),
311 Message::KaslServerTokenMissing => "A server is configured but no token is stored - run `kasl server connect` again.".to_string(),
312 Message::KaslServerTokenNotRemoved(error) => {
313 format!("The address was forgotten, but the stored token could not be removed: {}", error)
314 }
315 Message::KaslServerNotConnected => "This machine is not connected to a kasl-server.".to_string(),
316 Message::KaslServerDisconnected => "Disconnected; the stored token has been removed.".to_string(),
317 Message::KaslServerNoDayToPush(date) => format!("No workday recorded for {} - nothing to send.", date),
318 Message::KaslServerDayPushed { date, pauses, tasks } => {
319 format!("Sent {} to the server: {} pauses, {} tasks", date, pauses, tasks)
320 }
321 Message::KaslServerTasksDeleted(count) => {
322 format!("{} task(s) removed on the server - deleted here since the last upload", count)
323 }
324 Message::KaslServerPushRejected(error) => {
325 format!(
326 "{}
327The server will not accept this day as sent; fix it here and push again.",
328 error
329 )
330 }
331 Message::KaslServerPushTokenRejected(error) => {
332 format!(
333 "{}
334Run `kasl server connect` to connect again with a token your administrator issues.",
335 error
336 )
337 }
338 Message::KaslServerDayQueued(date) => {
339 format!("{} is queued and will be sent with the next successful push.", date)
340 }
341 Message::KaslServerQueueEmpty => "Nothing is waiting to be sent.".to_string(),
342 Message::KaslServerQueueOwed(count) => match count {
343 1 => "1 day is waiting to be sent:".to_string(),
344 _ => format!("{} days are waiting to be sent:", count),
345 },
346 Message::KaslServerQueueEntry { date, attempts, last_error } => {
347 let tried = match attempts {
350 1 => "1 attempt".to_string(),
351 _ => format!("{} attempts", attempts),
352 };
353 match last_error {
354 Some(error) => format!(" {} - {}, last: {}", date, tried, error),
355 None => format!(" {} - {}", date, tried),
356 }
357 }
358 Message::KaslServerQueueSending(count) => match count {
359 1 => "Sending 1 day...".to_string(),
360 _ => format!("Sending {} days...", count),
361 },
362 Message::KaslServerDayRefused { date, reason } => {
363 format!("{} was refused and has been dropped from the queue: {}", date, reason)
364 }
365 Message::KaslServerDayDeferred { date, reason } => {
366 format!("{} is still waiting: {}", date, reason)
367 }
368 Message::KaslServerFlushSummary { accepted, refused, deferred } => {
369 format!("{} sent, {} refused, {} still waiting", accepted, refused, deferred)
370 }
371 Message::KaslServerBackfillRange { from, to, days } => match days {
372 1 => format!("1 recorded day between {} and {}", from, to),
373 _ => format!("{} recorded days between {} and {}", days, from, to),
374 },
375 Message::KaslServerBackfillNoDays { from, to } => {
376 format!("No workdays recorded between {} and {} - nothing to send.", from, to)
377 }
378 Message::KaslServerBackfillOrderReversed => "The start of the range is after its end; swap --from and --to.".to_string(),
379 Message::KaslServerBackfillWholeHistory { from, to, days } => match days {
380 1 => format!("1 recorded day, the whole history from {} to {}", from, to),
381 _ => format!("{} recorded days, the whole history from {} to {}", days, from, to),
382 },
383 Message::KaslServerBackfillNothingRecorded => "No workday has ever been recorded on this machine - nothing to send.".to_string(),
384 Message::KaslServerCompatibility { server_version, api_version } => {
385 format!("server {} · api {} · ok", server_version, api_version)
386 }
387 Message::KaslServerPrivacyHeading(level) => format!("Privacy level on this server: {}", level),
388 Message::KaslServerPrivacySummary(summary) => summary.clone(),
389 Message::KaslServerPrivacyStoredHeading => "What it stores:".to_string(),
390 Message::KaslServerPrivacyStored { what, detail } => format!(" {} - {}", what, detail),
391 Message::KaslServerPrivacyNeverHeading => "What it never collects:".to_string(),
392 Message::KaslServerPrivacyBullet(line) => format!(" {}", line),
393 Message::KaslServerPrivacyVisibleHeading => "Who can see it:".to_string(),
394 Message::KaslServerPrivacyRetention(text) => format!("Retention: {}", text),
395 Message::KaslServerPrivacyOnChange(text) => format!("If the level changes: {}", text),
396 Message::KaslServerPrivacyUpdatedAt(when) => format!("This level was last set {}.", when),
397 Message::KaslServerPrivacySetByAdmin => {
398 "The level is the installation's, set by an administrator on the server. kasl shows it; it cannot widen or narrow it from here.".to_string()
399 }
400 Message::PulseOff => "Pulse: off - the server is not told whether you are working right now. `kasl server pulse enable` turns it on.".to_string(),
402 Message::PulseNothingYet => {
403 "Pulse: on - nothing has been sent since it was turned on. A running watcher sends one within seconds; `kasl watch` starts one.".to_string()
404 }
405 Message::PulseLastSent { ago, state } => match state {
406 Some(state) => format!("Pulse: on - the last one went {} ago ({}).", ago, state),
407 None => format!("Pulse: on - the last one went {} ago.", ago),
408 },
409 Message::PulseFailing { ago, error } => format!("Pulse: on, but the last one ({} ago) did not arrive: {}", ago, error),
410 Message::PulseLastArrived(ago) => format!("The last one that arrived went {} ago.", ago),
411 Message::PulseSilent(ago) => format!(
412 "The watcher was due to send another {} ago and has not - it is not running, or has stopped. `kasl watch` starts it.",
413 ago
414 ),
415 Message::PulseShownOffline(stale_after) => {
416 format!("The server stops believing a pulse after {}, so it now shows you as offline.", stale_after)
417 }
418 Message::PulseClockSkew(seconds) => format!(
419 "This machine's clock is {} s {} the server's - every time it records is off by as much, and the server refuses a pulse stamped more than a minute ahead. Set the clock right.",
420 seconds.abs(),
421 if *seconds > 0 { "ahead of" } else { "behind" }
422 ),
423 Message::PulseEnabled(url) => format!(
424 "Pulse on: the watcher tells {} once a minute whether you are working, on a break, or not in a day - the state only, never the task or the reason for a break.",
425 url
426 ),
427 Message::PulseNoWatcher => "No background watcher is running, so nothing goes out until one starts - `kasl watch`.".to_string(),
428 Message::PulseFirstArrived(state) => format!("The first pulse arrived: the server shows you as {}.", state),
429 Message::PulseFirstFailed(error) => format!(
430 "The first pulse did not arrive: {}
431The watcher keeps trying; `kasl server status` shows how it goes.",
432 error
433 ),
434 Message::PulseFirstPending => "The watcher has not sent one yet; `kasl server status` shows when it does.".to_string(),
435 Message::PulseDisabled(stale_after) => match stale_after {
436 Some(stale_after) => format!(
437 "Pulse off. The server keeps the last state it received, and shows you as offline once that is {} old.",
438 stale_after
439 ),
440 None => "Pulse off. The server keeps the last state it received, and shows you as offline once it stops believing it.".to_string(),
441 },
442 Message::PulseAlreadyOff => "The pulse is already off.".to_string(),
443 Message::KaslServerPushRetryable(error) => {
444 format!(
445 "{}
446The day is unchanged here and stays queued - `kasl server flush` sends it when the server is back.",
447 error
448 )
449 }
450
451 Message::DatabaseOperationFailed { operation, error } => {
453 format!("Database operation '{}' failed (continuing monitoring): {}", operation, error)
454 }
455 Message::NoIdSet => "No ID set".to_string(),
456
457 Message::FileNotFound => "File not found".to_string(),
459 Message::InvalidPidFileContent => "Invalid PID file content".to_string(),
460
461 Message::PathConfigured => "PATH successfully configured for system-wide access".to_string(),
463 Message::PathConfigWarning { error } => format!(
464 "Warning: Could not configure PATH automatically. {}\nYou may need to run as administrator or manually add kasl to your PATH.",
465 error
466 ),
467 Message::PathRegistryQueryError { status } => format!("Registry query failed (exit code: {})", status),
468 Message::PathRegistryUpdateError { status, stderr } => {
469 if stderr.trim().is_empty() {
470 format!("Registry update failed (exit code: {})", status)
471 } else {
472 format!("Registry update failed (exit code: {}): {}", status, stderr.trim())
473 }
474 }
475 Message::FailedToJoinPaths => "Failed to join paths".to_string(),
476 Message::FailedToExecuteRegQuery => "Failed to execute reg query".to_string(),
477 Message::FailedToParseRegOutput => "Failed to parse reg query output".to_string(),
478 Message::FailedToGetPathFromReg => "Failed to get PATH value from reg query".to_string(),
479 Message::FailedToExecuteRegSet => "Failed to execute reg set".to_string(),
480 Message::FailedToOpenProcess(code) => format!("Failed to open process: error code {}", code),
481 Message::FailedToTerminateProcess(code) => format!("Failed to terminate process: error code {}", code),
482 Message::ProcessTerminationNotSupported => "Process termination not supported on this platform".to_string(),
483
484 Message::MonthlyProductivity(percentage) => format!("Monthly work productivity: {:.1}%", percentage),
486 Message::LowProductivityWarning { current, threshold } => {
487 format!(
488 "🔴 LOW PRODUCTIVITY: {:.1}% (minimum: {:.1}%)\n If an absence is missing from the day, record it: kasl pauses add --start HH:MM --minutes N",
489 current, threshold
490 )
491 }
492 Message::ProductivityTooLowToSend { current, threshold } => {
493 format!(
494 "Cannot send report: productivity {:.1}% is below minimum {:.1}%\nIf an absence is missing from the day, record it: kasl pauses add --start HH:MM --minutes N",
495 current, threshold
496 )
497 }
498 Message::ManualPauseCreated {
499 start_time,
500 end_time,
501 duration_minutes,
502 } => {
503 format!("Pause recorded: {} - {} ({} minutes)", start_time, end_time, duration_minutes)
504 }
505 Message::ManualPauseOverlaps { start_time, end_time } => {
506 format!("Overlaps an existing pause ({} - {})", start_time, end_time)
507 }
508 Message::ManualPauseRemoved(id) => format!("Pause {} removed", id),
509 Message::ManualPauseNotFound(id) => format!("no pause with id '{}' - see `kasl pauses list`", id),
510
511 Message::PromptTaskName => "Enter task name (multi-line paste OK)".to_string(),
515 Message::TaskNameMergedFromPaste => "Merged pasted lines into the task name.".to_string(),
516 Message::PromptTaskComment => "Enter comment".to_string(),
517 Message::PromptTaskCompleteness => "Enter completeness".to_string(),
518 Message::PromptMinPauseDuration => "Enter minimum pause duration (minutes)".to_string(),
519 Message::PromptPauseThreshold => "Enter pause threshold (seconds)".to_string(),
520 Message::PromptPollInterval => "Enter poll interval (milliseconds)".to_string(),
521 Message::PromptActivityThreshold => "Enter activity threshold (seconds)".to_string(),
522 Message::PromptMinProductivityThreshold => "Enter minimum productivity threshold (%)".to_string(),
523 Message::PromptWorkdayHours => "Enter expected workday duration (hours)".to_string(),
524 Message::PromptMinWorkdayFraction => "Enter minimum workday fraction before suggesting breaks (0.0-1.0)".to_string(),
525 Message::PromptServerApiUrl => "Enter server API URL".to_string(),
526 Message::PromptServerAuthToken => "Enter server auth token".to_string(),
527 Message::PromptKaslServerUrl => "kasl-server URL".to_string(),
528 Message::PromptKaslServerToken => "Agent token (issued by your administrator)".to_string(),
529 Message::PromptSelectModules => "Select nodes to configure".to_string(),
530 Message::PromptSelectTasksToImport => "Select tasks to import".to_string(),
531 Message::PromptSelectTasksToIgnore => "Select tasks to ignore (optional)".to_string(),
532 Message::PromptSelectIgnoreNamesToRemove => "Select ignore names to remove (optional)".to_string(),
533 Message::PromptAddIgnoreName => "Add ignore name (empty to finish)".to_string(),
534
535 Message::OperationCompleted => "Operation completed successfully".to_string(),
537 Message::OperationCancelled => "Operation cancelled".to_string(),
538 Message::InvalidInput => "Invalid input provided".to_string(),
539 Message::PermissionDenied => "Permission denied".to_string(),
540 Message::DeprecatedCommand(old, new) => {
541 format!("`kasl {}` is now `kasl {}`. The old name still works but will be removed in 2.0.", old, new)
542 }
543
544 Message::ErrorSendingEvents(error) => format!("[kasl] Error sending events: {}", error),
546 Message::ErrorSendingMonthlyReport(error) => format!("[kasl] Error sending monthly report: {}", error),
547 Message::ErrorInRdevListener(error) => format!("Error in rdev listener: {:?}", error),
548 Message::ErrorRequestingRestDates(error) => format!("Error requesting rest dates: {}", error),
549
550 Message::SelectingTask(name) => format!("Selected task: {}", name),
552
553 Message::JiraInboxRequiresJiraConfig => "Jira inbox requires Jira to be configured. Run `kasl setup` and select Jira.".to_string(),
555 Message::JiraInboxEmpty => "Jira inbox is empty.".to_string(),
556 Message::JiraInboxListHeader => "Jira inbox:".to_string(),
557 Message::JiraInboxListSliced { shown, total, what } => {
558 if what.is_empty() {
559 format!("Jira inbox: {} of {} issues:", shown, total)
560 } else {
561 format!("Jira inbox: {} of {} issues ({}):", shown, total, what)
562 }
563 }
564 Message::JiraInboxNoMatch { total, what } => format!("No issue matches ({}); {} in the inbox.", what, total),
565 Message::JiraInboxSynced {
566 fetched,
567 new_count,
568 changed,
569 gone,
570 } => {
571 format!("Jira inbox synced: {} fetched, {} new, {} changed, {} gone.", fetched, new_count, changed, gone)
572 }
573 Message::JiraInboxNewIssues(count) => format!("Jira inbox: {} new issue(s).", count),
574 Message::JiraInboxNotFound(key) => format!("Issue '{}' not found in inbox.", key),
575 Message::JiraInboxPinned(key) => format!("Pinned {}.", key),
576 Message::JiraInboxUnpinned(key) => format!("Unpinned {}.", key),
577 Message::JiraInboxDismissed(key) => format!("Dismissed {}.", key),
578 Message::JiraInboxUndismissed(key) => format!("{} is back in the inbox.", key),
579 Message::JiraInboxSnoozed(key, until) => format!("Snoozed {} until {}.", key, until),
580 Message::JiraInboxUnsnoozed(key) => format!("Woke {}.", key),
581 Message::JiraInboxAllSnoozed(count) => {
582 format!("Jira inbox is clear; {} issue(s) are snoozed. `kasl inbox --snoozed` shows them.", count)
583 }
584 Message::JiraInboxWoke(count) => format!("Jira inbox: {} snoozed issue(s) came back.", count),
585 Message::JiraInboxTriaged {
586 taken,
587 snoozed,
588 dismissed,
589 skipped,
590 left,
591 } => {
592 let mut parts = Vec::new();
595 if *taken > 0 {
596 parts.push(format!("{} taken", taken));
597 }
598 if *snoozed > 0 {
599 parts.push(format!("{} snoozed", snoozed));
600 }
601 if *dismissed > 0 {
602 parts.push(format!("{} dismissed", dismissed));
603 }
604 if *skipped > 0 {
605 parts.push(format!("{} skipped", skipped));
606 }
607 let what = if parts.is_empty() { "nothing decided".to_string() } else { parts.join(", ") };
608 if *left > 0 {
609 format!("Triaged: {}; {} left untouched.", what, left)
610 } else {
611 format!("Triaged: {}.", what)
612 }
613 }
614 Message::JiraInboxOpened(key) => format!("Opened {} in browser.", key),
615 Message::JiraInboxTaken(key) => format!("Imported {} into tasks.", key),
616 Message::ToastActionUnknown(name) => format!("Unknown toast action '{}'; expected take, snooze, dismiss or done.", name),
617 Message::ToastActionApplied(what) => format!("{}.", what),
618 Message::ToastActionFailed(key, why) => format!("Could not act on {} from the toast: {}", key, why),
619 Message::JiraMoved(sentence) => format!("{}.", sentence),
620 Message::JiraCommented(key) => format!("Commented on {} in Jira.", key),
621 Message::JiraWriteFailed(key, why) => format!("Jira was not updated for {}: {}", key, why),
622 Message::JiraResolvedTaskOpen(key, resolution) => format!(
623 "{} was resolved in Jira{}; its task is still open - `kasl task find` offers to finish it.",
624 key, resolution
625 ),
626 Message::PromptJiraAfterDone(key) => format!("{} is done here. In Jira", key),
627 Message::PromptJiraCloseComment => "Comment to leave on the issue (empty for none)".to_string(),
628 Message::PromptJiraComment => "Comment".to_string(),
629 Message::PromptMarkResolvedDone => "Resolved in Jira - mark these tasks done (space toggles, enter confirms)".to_string(),
630 Message::JiraInboxAlreadyTaken(key, name) => format!("{} is already taken as '{}'.", key, name),
631 Message::JiraInboxSummary { total, fresh, taken } => {
632 let mut parts = Vec::new();
635 if *fresh > 0 {
636 parts.push(format!("{} new", fresh));
637 }
638 if *taken > 0 {
639 parts.push(format!("{} taken", taken));
640 }
641 let detail = if parts.is_empty() {
642 String::new()
643 } else {
644 format!(" ({})", parts.join(", "))
645 };
646 format!("{} in the inbox{}", total, detail)
647 }
648 Message::JiraInboxOpenFailed(err) => format!("Failed to open browser: {}", err),
649 Message::InboxRulesSkipped(count) => format!("{} inbox rule(s) skipped as unreadable - `kasl inbox rules` says why.", count),
650 Message::InboxRuleSkipped(number, why) => format!("Inbox rule {} is skipped: {}.", number, why),
651 Message::InboxRuleActed(what) => format!("{}.", what),
652 Message::InboxRulesNone => "No inbox rules yet. They go under `jira_inbox.rules` in the config; the Jira inbox guide shows how.".to_string(),
653 Message::InboxRulesApplied(0) => "The rules have nothing to change.".to_string(),
654 Message::InboxRulesApplied(count) => format!("The rules changed {} issue(s).", count),
655 Message::InboxRulesNotApplied(count) => format!(
656 "{} issue(s) match a rule that has not acted on them: rules act when an issue arrives or changes. `kasl inbox rules --apply` acts on them now.",
657 count
658 ),
659 Message::PromptJiraInboxEnabled => "Enable Jira inbox polling?".to_string(),
660 Message::PromptJiraInboxPollInterval => "Jira inbox poll interval (seconds)".to_string(),
661 Message::PromptJiraInboxNotify => "Show toast notifications for new issues?".to_string(),
662 Message::PromptJiraInboxSortFieldId => "Sort field id for ranking (e.g. customfield_12345 for Scoring; empty to skip)".to_string(),
663 Message::PromptJiraInboxSortFieldLabel => "Label for sort field (default Scoring)".to_string(),
664 Message::PromptJiraInboxExtraFieldId => "Additional custom field id to fetch (empty to finish)".to_string(),
665 Message::PromptJiraInboxExtraFieldLabel => "Label for this custom field".to_string(),
666
667 Message::MigrationsFound(count) => format!("Found {} pending database migrations", count),
669 Message::RunningMigration(version, name) => format!("Running migration v{}: {}", version, name),
670 Message::MigrationCompleted(version) => format!("✓ Migration v{} completed", version),
671 Message::MigrationFailed(version, error) => format!("✗ Migration v{} failed: {}", version, error),
672 Message::AllMigrationsCompleted => "All database migrations completed successfully".to_string(),
673 Message::DatabaseVersion(version) => format!("Current database version: {}", version),
674 Message::DatabaseUpToDate => "Database schema is up to date".to_string(),
675 Message::DatabaseNeedsUpdate => "Database schema needs to be updated".to_string(),
676 Message::MigrationHistory => "Migration history:".to_string(),
677 Message::NothingToRollback => "Nothing to rollback".to_string(),
678 Message::RollingBack(from, to) => format!("Rolling back from v{} to v{}", from, to),
679 Message::RollbackCompleted(version) => format!("Rollback to v{} completed", version),
680 };
681
682 write!(f, "{}", text)
683 }
684}