| 1 | //! The first-run notice copy. |
| 2 | //! |
| 3 | //! One string, owned by the crate that owns what is collected, so the TUI and |
| 4 | //! the CLI cannot drift into describing two different products. Every claim |
| 5 | //! below is checked against [`crate::event`] by a test: if the schema grows a |
| 6 | //! field this text does not cover, that test fails. |
| 7 | //! |
| 8 | //! Two properties of the wording are deliberate and load-bearing: |
| 9 | //! |
| 10 | //! 1. **Usage defaults on with durable opt-out.** Disclosure presentation |
| 11 | //! records only that it was shown, never fictional human acceptance. |
| 12 | //! 2. **The red lines are stated as "not collected", not as "anonymized".** |
| 13 | //! Sampling and hashing are not the same promise, and a notice that implies |
| 14 | //! them when neither is true is worse than no notice. |
| 15 | |
| 16 | /// Headline shown above [`NOTICE_BODY`]. |
| 17 | pub const NOTICE_HEADLINE: &str = "Codewhale usage reporting"; |
| 18 | |
| 19 | /// The notice itself. |
| 20 | /// |
| 21 | /// Wrapped at 72 columns so it renders unchanged in the native responsive |
| 22 | /// modal and remains readable in an 80-column terminal. |
| 23 | pub const NOTICE_BODY: &str = "\ |
| 24 | Codewhale counts: which version you run, OS and CPU family, session |
| 25 | duration and outcome, and aggregate feature and error counters. |
| 26 | |
| 27 | It never collects your conversations, code, prompts, files, repo or |
| 28 | branch names, model content, or credentials — and it never sends a |
| 29 | per-turn or per-tool timeline of agent activity. |
| 30 | |
| 31 | You are identified only by a random ID stored on this machine, replaced |
| 32 | every 90 days. Change your mind any time: |
| 33 | codewhale config set telemetry false |
| 34 | |
| 35 | Full schema, field by field: docs/TELEMETRY.md |
| 36 | |
| 37 | Usage reporting is on by default. Codewhale and PostHog process these |
| 38 | counts when delivery is configured. No IP is collected."; |
| 39 | |
| 40 | /// Concise disclosure for every armed runtime surface, including headless CLI. |
| 41 | pub const STARTUP_DISCLOSURE: &str = "Usage reporting is on by default: Codewhale and PostHog process aggregate version/platform, session, feature and error counts when delivery is configured. No content or IP. Turn off: codewhale config set telemetry false. Details: codewhale config telemetry"; |
| 42 | |
| 43 | /// Present the policy once per revision without recording human acceptance. |
| 44 | /// Failure to save the display marker only causes a later repeat disclosure. |
| 45 | /// The interactive TUI draws its own localized notice and records its own |
| 46 | /// presentation, so it never gets a stray stderr line before the first frame. |
| 47 | pub(crate) fn show_startup_disclosure(surface: crate::event::Surface) { |
| 48 | if surface == crate::event::Surface::Tui { |
| 49 | return; |
| 50 | } |
| 51 | let Ok(path) = codewhale_config::SetupState::path() else { |
| 52 | return; |
| 53 | }; |
| 54 | let Some(state) = crate::load_setup_state_for_decision_at(&path) else { |
| 55 | return; |
| 56 | }; |
| 57 | if state.telemetry_opted_out() |
| 58 | || !state.needs_telemetry_notice(codewhale_config::TELEMETRY_NOTICE_VERSION) |
| 59 | { |
| 60 | return; |
| 61 | } |
| 62 | use std::io::Write; |
| 63 | if writeln!(std::io::stderr().lock(), "{STARTUP_DISCLOSURE}").is_ok() { |
| 64 | let _ = codewhale_config::SetupState::update_telemetry_at(&path, |latest| { |
| 65 | latest.record_telemetry_notice_shown(codewhale_config::TELEMETRY_NOTICE_VERSION); |
| 66 | }); |
| 67 | } |
| 68 | } |
| 69 |