diff --git a/OBSERVABILITY.md b/OBSERVABILITY.md index fc1d2e4671..7fa2b749aa 100644 --- a/OBSERVABILITY.md +++ b/OBSERVABILITY.md @@ -19,7 +19,7 @@ This repo has multiple binaries and runtime surfaces, but the same conventions a - `apps/api` is one OTEL service - `apps/desktop` is one Sentry/desktop service - internal route groups or modules are not separate OTEL services -- internal logical breakdowns use `hyprnote.subsystem` +- internal logical breakdowns use `char.subsystem` Current canonical subsystem values include: @@ -51,7 +51,7 @@ We use three separate concepts: Every process should set: -- `service.namespace = "hyprnote"` +- `service.namespace = "char"` - `service.name = ` - `service.version` - `deployment.environment` @@ -78,13 +78,13 @@ For example, `edge`, `llm`, `stt`, and `subscription` inside `apps/api` are not Use: -- `hyprnote.subsystem` +- `char.subsystem` Examples: -- API ingress span: `hyprnote.subsystem = "edge"` -- LLM handler span: `hyprnote.subsystem = "llm"` -- STT websocket/session spans: `hyprnote.subsystem = "stt"` +- API ingress span: `char.subsystem = "edge"` +- LLM handler span: `char.subsystem = "llm"` +- STT websocket/session spans: `char.subsystem = "stt"` Do not use a bare `service` span field for this. @@ -161,7 +161,7 @@ It is not: - generate it once at ingress if missing - forward it unchanged when useful -- record it as `hyprnote.request.id` +- record it as `char.request.id` - keep it semantically separate from OTEL trace context Never do this: @@ -208,11 +208,11 @@ Examples: - `gen_ai.usage.input_tokens` - `gen_ai.usage.output_tokens` -### Rule 2: Custom fields must use `hyprnote.*` +### Rule 2: Custom fields must use `char.*` If OTEL does not define a field, use: -- `hyprnote.*` +- `char.*` Do not use: @@ -246,11 +246,11 @@ Use: ### Request and duration -- `hyprnote.request.id` -- `hyprnote.duration_ms` -- `hyprnote.retry.delay_ms` -- `hyprnote.timeout_s` -- `hyprnote.timeout.elapsed` +- `char.request.id` +- `char.duration_ms` +- `char.retry.delay_ms` +- `char.timeout_s` +- `char.timeout.elapsed` ### HTTP and routing @@ -276,45 +276,45 @@ Use OTEL GenAI fields where available: - `gen_ai.usage.input_tokens` - `gen_ai.usage.output_tokens` -Use `hyprnote.*` for Hyprnote-specific request metadata: +Use `char.*` for Char-specific request metadata: -- `hyprnote.gen_ai.request.streaming` -- `hyprnote.gen_ai.request.message_count` -- `hyprnote.gen_ai.request.model_candidate_count` -- `hyprnote.gen_ai.request.tool_calling` -- `hyprnote.task.name` +- `char.gen_ai.request.streaming` +- `char.gen_ai.request.message_count` +- `char.gen_ai.request.model_candidate_count` +- `char.gen_ai.request.tool_calling` +- `char.task.name` ### STT and audio Use: -- `hyprnote.stt.provider.name` -- `hyprnote.stt.routing_strategy` -- `hyprnote.stt.model` -- `hyprnote.stt.language_codes` -- `hyprnote.stt.language_code` -- `hyprnote.stt.session.id` -- `hyprnote.stt.job.id` -- `hyprnote.stt.provider_session.id` -- `hyprnote.stt.provider_session.duration_s` -- `hyprnote.stt.provider_session.expires_at` -- `hyprnote.stt.provider.error_code` -- `hyprnote.audio.sample_rate_hz` -- `hyprnote.audio.channel_count` -- `hyprnote.audio.channel_index` -- `hyprnote.audio.size_bytes` -- `hyprnote.audio.duration_s` -- `hyprnote.audio.device` +- `char.stt.provider.name` +- `char.stt.routing_strategy` +- `char.stt.model` +- `char.stt.language_codes` +- `char.stt.language_code` +- `char.stt.session.id` +- `char.stt.job.id` +- `char.stt.provider_session.id` +- `char.stt.provider_session.duration_s` +- `char.stt.provider_session.expires_at` +- `char.stt.provider.error_code` +- `char.audio.sample_rate_hz` +- `char.audio.channel_count` +- `char.audio.channel_index` +- `char.audio.size_bytes` +- `char.audio.duration_s` +- `char.audio.device` ### Vendor-specific fields Keep vendor-specific fields namespaced: -- `hyprnote.supabase.*` -- `hyprnote.stripe.*` -- `hyprnote.connection.*` -- `hyprnote.integration.*` -- `hyprnote.bot.*` +- `char.supabase.*` +- `char.stripe.*` +- `char.connection.*` +- `char.integration.*` +- `char.bot.*` Always prefer `service.peer.name` for the downstream system name. @@ -322,9 +322,9 @@ Always prefer `service.peer.name` for the downstream system name. If raw payload capture is necessary for debug logs, use: -- `hyprnote.payload.raw` -- `hyprnote.http.response.body` -- `hyprnote.http.body_preview` +- `char.payload.raw` +- `char.http.response.body` +- `char.http.body_preview` Do not put large raw payloads on high-volume spans by default. @@ -339,7 +339,7 @@ Honeycomb service views come from OTEL resource attributes, especially: Because of that: - `apps/api` must stay one Honeycomb service: `api` -- internal analysis should use `hyprnote.subsystem` +- internal analysis should use `char.subsystem` ### High cardinality @@ -347,11 +347,11 @@ Honeycomb handles high-cardinality fields well. IDs are allowed when they help d Good high-cardinality examples: -- `hyprnote.request.id` +- `char.request.id` - `enduser.id` - `enduser.pseudo.id` - `gen_ai.response.id` -- `hyprnote.stt.job.id` +- `char.stt.job.id` - provider session IDs Do not avoid useful IDs just because they are high cardinality. @@ -402,11 +402,11 @@ Canonical Sentry tags include: - `error.type` - `gen_ai.provider.name` - `gen_ai.request.model` -- `hyprnote.gen_ai.request.streaming` -- `hyprnote.stt.provider.name` -- `hyprnote.stt.routing_strategy` -- `hyprnote.stt.model` -- `hyprnote.stt.language_codes` +- `char.gen_ai.request.streaming` +- `char.stt.provider.name` +- `char.stt.routing_strategy` +- `char.stt.model` +- `char.stt.language_codes` ### Context naming @@ -416,9 +416,9 @@ Canonical context names include: - `gen_ai.request` - `gen_ai.response` -- `hyprnote.stt.request` -- `hyprnote.enduser.claims` -- `hyprnote.session` +- `char.stt.request` +- `char.enduser.claims` +- `char.session` ### Sentry user @@ -492,7 +492,7 @@ Meaning: 1. Decide whether the concept already has an OTEL semantic convention. 2. If yes, use the OTEL field name. -3. If no, add a `hyprnote.*` field. +3. If no, add a `char.*` field. 4. If the field will be recorded later on a span, declare it at span creation. 5. If the code crosses a network boundary, extract or inject W3C trace context. 6. If request correlation is needed, keep `x-request-id` separate from trace propagation. diff --git a/Taskfile.yaml b/Taskfile.yaml index 68d975ff8d..26425dd1a6 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -25,7 +25,7 @@ tasks: - task: cli:ui - cp apps/cli-ui/.build/apple/Products/Release/char-cli-ui target/debug/ - cargo run -p cli --features standalone -- {{.CLI_ARGS}} - stat: btop -f hyprnote -u 500 --preset 1 + stat: btop -f char -u 500 --preset 1 clean-plugins: cmds: @@ -66,7 +66,7 @@ tasks: db: env: - DB: /Users/yujonglee/Library/Application Support/hyprnote/db.sqlite + DB: /Users/yujonglee/Library/Application Support/char/db.sqlite cmds: - | sqlite3 -json "$DB" 'SELECT store FROM main LIMIT 1;' | diff --git a/apps/api/fly.toml b/apps/api/fly.toml index ab4530fc40..bf01aa769b 100644 --- a/apps/api/fly.toml +++ b/apps/api/fly.toml @@ -1,4 +1,4 @@ -app = 'hyprnote-ai' +app = 'char-ai' primary_region = 'sjc' kill_signal = 'SIGTERM' kill_timeout = 30 diff --git a/apps/api/openapi.gen.json b/apps/api/openapi.gen.json index df163ee14c..ccb72925cf 100644 --- a/apps/api/openapi.gen.json +++ b/apps/api/openapi.gen.json @@ -922,7 +922,7 @@ { "name": "provider", "in": "query", - "description": "STT provider. Use 'hyprnote' for automatic routing (default), or specify:\ndeepgram, soniox, assemblyai, gladia, elevenlabs, fireworks, openai, dashscope, mistral", + "description": "STT provider. Use 'char' for automatic routing (default), or specify:\ndeepgram, soniox, assemblyai, gladia, elevenlabs, fireworks, openai, dashscope, mistral", "required": false, "schema": { "type": "string" @@ -1008,7 +1008,7 @@ { "name": "provider", "in": "query", - "description": "STT provider. Use 'hyprnote' for automatic routing (default), or specify:\ndeepgram, soniox, assemblyai, gladia, elevenlabs, fireworks, openai, dashscope, mistral", + "description": "STT provider. Use 'char' for automatic routing (default), or specify:\ndeepgram, soniox, assemblyai, gladia, elevenlabs, fireworks, openai, dashscope, mistral", "required": false, "schema": { "type": "string" diff --git a/apps/api/src/auth.rs b/apps/api/src/auth.rs index fc39cdb664..11ca62259e 100644 --- a/apps/api/src/auth.rs +++ b/apps/api/src/auth.rs @@ -30,7 +30,7 @@ pub async fn sentry_and_analytics(mut request: Request, next: Next) -> Response let mut ctx = BTreeMap::new(); ctx.insert( - "hyprnote.enduser.entitlements".into(), + "char.enduser.entitlements".into(), sentry::protocol::Value::Array( auth.claims .entitlements @@ -40,7 +40,7 @@ pub async fn sentry_and_analytics(mut request: Request, next: Next) -> Response ), ); scope.set_context( - "hyprnote.enduser.claims", + "char.enduser.claims", sentry::protocol::Context::Other(ctx), ); }); diff --git a/apps/api/src/main.rs b/apps/api/src/main.rs index 360487f4ef..4a756bbaba 100644 --- a/apps/api/src/main.rs +++ b/apps/api/src/main.rs @@ -82,7 +82,7 @@ async fn app() -> Router { let llm_config = hypr_llm_proxy::LlmProxyConfig::new(&env.llm).with_analytics(analytics.clone()); let stt_config = hypr_transcribe_proxy::SttProxyConfig::new(&env.stt, &env.supabase) - .with_hyprnote_routing(hypr_transcribe_proxy::HyprnoteRoutingConfig::default()) + .with_char_routing(hypr_transcribe_proxy::CharRoutingConfig::default()) .with_analytics(analytics.clone()); let stt_rate_limit = rate_limit::RateLimitState::builder() @@ -111,7 +111,7 @@ async fn app() -> Router { .build(); let auth_state_pro = AuthState::new(&env.supabase.supabase_url) - .with_required_entitlements(vec!["hyprnote_pro".into(), "hyprnote_lite".into()]); + .with_required_entitlements(vec!["char_pro".into(), "char_lite".into()]); let auth_state_basic = AuthState::new(&env.supabase.supabase_url); let auth_state_support = AuthState::new(&env.supabase.supabase_url); @@ -162,7 +162,7 @@ async fn app() -> Router { ); let auth_state_integration = - AuthState::new(&env.supabase.supabase_url).with_required_entitlement("hyprnote_pro"); + AuthState::new(&env.supabase.supabase_url).with_required_entitlement("char_pro"); let pro_routes = Router::new() .merge(hypr_api_research::router(research_config)) @@ -286,19 +286,19 @@ async fn app() -> Router { server.address = tracing::field::Empty, server.port = tracing::field::Empty, client.address = tracing::field::Empty, - hyprnote.subsystem = "edge", + char.subsystem = "edge", enduser.id = tracing::field::Empty, enduser.pseudo.id = tracing::field::Empty, - hyprnote.stt.provider.name = tracing::field::Empty, - hyprnote.stt.routing_strategy = tracing::field::Empty, - hyprnote.stt.model = tracing::field::Empty, - hyprnote.stt.language_codes = tracing::field::Empty, - hyprnote.audio.sample_rate_hz = tracing::field::Empty, - hyprnote.audio.channel_count = tracing::field::Empty, + char.stt.provider.name = tracing::field::Empty, + char.stt.routing_strategy = tracing::field::Empty, + char.stt.model = tracing::field::Empty, + char.stt.language_codes = tracing::field::Empty, + char.audio.sample_rate_hz = tracing::field::Empty, + char.audio.channel_count = tracing::field::Empty, gen_ai.provider.name = tracing::field::Empty, - hyprnote.gen_ai.request.streaming = tracing::field::Empty, - hyprnote.gen_ai.request.message_count = tracing::field::Empty, - hyprnote.request.id = tracing::field::Empty, + char.gen_ai.request.streaming = tracing::field::Empty, + char.gen_ai.request.message_count = tracing::field::Empty, + char.request.id = tracing::field::Empty, error.type = tracing::field::Empty, otel.status_code = tracing::field::Empty, otel.kind = "server", @@ -327,7 +327,7 @@ async fn app() -> Router { .get(REQUEST_ID_HEADER) .and_then(|v| v.to_str().ok()) { - span.record("hyprnote.request.id", request_id); + span.record("char.request.id", request_id); } configure_sentry_trace_scope(span, env, SystemTime::now()); tracing::info!( @@ -357,7 +357,7 @@ async fn app() -> Router { tracing::info!( parent: span, http.response.status_code = %response.status().as_u16(), - hyprnote.duration_ms = %latency.as_millis(), + char.duration_ms = %latency.as_millis(), "http_request_finished" ); }, @@ -382,7 +382,7 @@ async fn app() -> Router { parent: span, error.type = %error_type, error = %failure_class, - hyprnote.duration_ms = %latency.as_millis(), + char.duration_ms = %latency.as_millis(), "http_request_failed" ); }, @@ -416,7 +416,7 @@ fn main() -> std::io::Result<()> { let _guard = sentry::init(sentry::ClientOptions { dsn: env.sentry_dsn.as_ref().and_then(|s| s.parse().ok()), - release: option_env!("APP_VERSION").map(|v| format!("hyprnote-api@{}", v).into()), + release: option_env!("APP_VERSION").map(|v| format!("char-api@{}", v).into()), environment: Some( if cfg!(debug_assertions) { "development" @@ -436,7 +436,7 @@ fn main() -> std::io::Result<()> { }); sentry::configure_scope(|scope| { - scope.set_tag("service.namespace", "hyprnote"); + scope.set_tag("service.namespace", "char"); scope.set_tag("service.name", "api"); }); @@ -489,15 +489,15 @@ fn configure_sentry_trace_scope(span: &tracing::Span, env: &Env, request_started let trace_url = build_honeycomb_trace_url(env, &trace_identifiers, request_started_at); sentry::configure_scope(|scope| { scope.set_tag( - "hyprnote.honeycomb.trace_id", + "char.honeycomb.trace_id", trace_identifiers.trace_id.as_str(), ); scope.set_tag( - "hyprnote.honeycomb.span_id", + "char.honeycomb.span_id", trace_identifiers.span_id.as_str(), ); if let Some(trace_url) = trace_url.as_deref() { - scope.set_tag("hyprnote.honeycomb.trace_url", trace_url); + scope.set_tag("char.honeycomb.trace_url", trace_url); } let mut context = std::collections::BTreeMap::new(); @@ -506,7 +506,7 @@ fn configure_sentry_trace_scope(span: &tracing::Span, env: &Env, request_started if let Some(trace_url) = trace_url { context.insert("trace_url".into(), Value::String(trace_url)); } - scope.set_context("hyprnote.honeycomb", Context::Other(context)); + scope.set_context("char.honeycomb", Context::Other(context)); }); } diff --git a/apps/api/src/observability.rs b/apps/api/src/observability.rs index 55ada0aad5..d4daf690b5 100644 --- a/apps/api/src/observability.rs +++ b/apps/api/src/observability.rs @@ -103,7 +103,7 @@ fn init_otel_tracer_provider(service_name: &str, env: &Env) -> Option - com.hyprnote.Hyprnote + com.char.Char Char The AI notepad for private meetings @@ -25,14 +25,14 @@ - com.hyprnote.Hyprnote.desktop + com.char.Char.desktop - https://hyprnote.com + https://char.com https://github.com/fastrepl/char/issues https://github.com/fastrepl/char https://github.com/fastrepl/char/blob/main/CONTRIBUTING.md - + Fastrepl diff --git a/apps/desktop/flatpak/com.hyprnote.Hyprnote.yml b/apps/desktop/flatpak/com.char.Char.yml similarity index 73% rename from apps/desktop/flatpak/com.hyprnote.Hyprnote.yml rename to apps/desktop/flatpak/com.char.Char.yml index b00fa07f09..e5d00a5cd7 100644 --- a/apps/desktop/flatpak/com.hyprnote.Hyprnote.yml +++ b/apps/desktop/flatpak/com.char.Char.yml @@ -6,12 +6,12 @@ # flatpak-cargo-generator.py -d Cargo.lock -o flatpak/cargo-sources.json # # To build locally: -# flatpak-builder --user --install --force-clean flatpak-build-dir flatpak/com.hyprnote.Hyprnote.yml +# flatpak-builder --user --install --force-clean flatpak-build-dir flatpak/com.char.Char.yml # # To run: -# flatpak run com.hyprnote.Hyprnote +# flatpak run com.char.Char -id: com.hyprnote.Hyprnote +id: com.char.Char runtime: org.gnome.Platform runtime-version: "47" @@ -20,7 +20,7 @@ sdk-extensions: - org.freedesktop.Sdk.Extension.rust-stable - org.freedesktop.Sdk.Extension.node22 -command: hyprnote +command: char finish-args: # Display - Wayland and X11 fallback @@ -42,7 +42,7 @@ finish-args: - --talk-name=org.kde.StatusNotifierWatcher # Single instance D-Bus - - --own-name=com.hyprnote.Hyprnote + - --own-name=com.char.Char # Filesystem access for user documents and notes - --filesystem=xdg-documents @@ -54,11 +54,11 @@ finish-args: build-options: append-path: /usr/lib/sdk/node22/bin:/usr/lib/sdk/rust-stable/bin env: - CARGO_HOME: /run/build/hyprnote/cargo + CARGO_HOME: /run/build/char/cargo npm_config_nodedir: /usr/lib/sdk/node22 modules: - - name: hyprnote + - name: char buildsystem: simple sources: @@ -77,8 +77,8 @@ modules: build-options: env: - XDG_CACHE_HOME: /run/build/hyprnote/cache - npm_config_cache: /run/build/hyprnote/npm-cache + XDG_CACHE_HOME: /run/build/char/cache + npm_config_cache: /run/build/char/npm-cache npm_config_offline: "true" build-commands: @@ -92,17 +92,17 @@ modules: - pnpm -F desktop tauri build --no-bundle --target x86_64-unknown-linux-gnu --config src-tauri/tauri.conf.flatpak.json # Install the binary - - install -Dm755 apps/desktop/src-tauri/target/x86_64-unknown-linux-gnu/release/hyprnote /app/bin/hyprnote + - install -Dm755 apps/desktop/src-tauri/target/x86_64-unknown-linux-gnu/release/char /app/bin/char # Install desktop file - - install -Dm644 apps/desktop/flatpak/com.hyprnote.Hyprnote.desktop /app/share/applications/com.hyprnote.Hyprnote.desktop + - install -Dm644 apps/desktop/flatpak/com.char.Char.desktop /app/share/applications/com.char.Char.desktop # Install metainfo - - install -Dm644 apps/desktop/flatpak/com.hyprnote.Hyprnote.metainfo.xml /app/share/metainfo/com.hyprnote.Hyprnote.metainfo.xml + - install -Dm644 apps/desktop/flatpak/com.char.Char.metainfo.xml /app/share/metainfo/com.char.Char.metainfo.xml # Install icons - - install -Dm644 apps/desktop/src-tauri/icons/stable/32x32.png /app/share/icons/hicolor/32x32/apps/com.hyprnote.Hyprnote.png - - install -Dm644 apps/desktop/src-tauri/icons/stable/64x64.png /app/share/icons/hicolor/64x64/apps/com.hyprnote.Hyprnote.png - - install -Dm644 apps/desktop/src-tauri/icons/stable/128x128.png /app/share/icons/hicolor/128x128/apps/com.hyprnote.Hyprnote.png - - install -Dm644 apps/desktop/src-tauri/icons/stable/128x128@2x.png /app/share/icons/hicolor/256x256/apps/com.hyprnote.Hyprnote.png - - install -Dm644 apps/desktop/src-tauri/icons/stable/icon.png /app/share/icons/hicolor/512x512/apps/com.hyprnote.Hyprnote.png + - install -Dm644 apps/desktop/src-tauri/icons/stable/32x32.png /app/share/icons/hicolor/32x32/apps/com.char.Char.png + - install -Dm644 apps/desktop/src-tauri/icons/stable/64x64.png /app/share/icons/hicolor/64x64/apps/com.char.Char.png + - install -Dm644 apps/desktop/src-tauri/icons/stable/128x128.png /app/share/icons/hicolor/128x128/apps/com.char.Char.png + - install -Dm644 apps/desktop/src-tauri/icons/stable/128x128@2x.png /app/share/icons/hicolor/256x256/apps/com.char.Char.png + - install -Dm644 apps/desktop/src-tauri/icons/stable/icon.png /app/share/icons/hicolor/512x512/apps/com.char.Char.png diff --git a/apps/desktop/public/assets/hyprnote-pro.png b/apps/desktop/public/assets/char-pro.png similarity index 100% rename from apps/desktop/public/assets/hyprnote-pro.png rename to apps/desktop/public/assets/char-pro.png diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index 9c15bd2e7b..ecc7c2fee6 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -11,7 +11,7 @@ description = "Char Desktop App" # The `_lib` suffix may seem redundant but it is necessary # to make the lib name unique and wouldn't conflict with the bin name. # This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519 -name = "hyprnote_desktop_lib" +name = "char_desktop_lib" # Note: cdylib removed because V8 (used by deno_core) has TLS relocations incompatible # with shared libraries on Linux. Mobile support is not needed (macOS/Linux/Windows only). crate-type = ["staticlib", "rlib"] diff --git a/apps/desktop/src-tauri/src/embedded_cli.rs b/apps/desktop/src-tauri/src/embedded_cli.rs index 1676de5fdc..129b9e3261 100644 --- a/apps/desktop/src-tauri/src/embedded_cli.rs +++ b/apps/desktop/src-tauri/src/embedded_cli.rs @@ -2,10 +2,10 @@ use std::path::{Path, PathBuf}; use serde::Serialize; -const DEV_BUNDLE_ID: &str = "com.hyprnote.dev"; -const STABLE_BUNDLE_ID: &str = "com.hyprnote.stable"; -const STAGING_BUNDLE_ID: &str = "com.hyprnote.staging"; -const NIGHTLY_BUNDLE_ID: &str = "com.hyprnote.nightly"; +const DEV_BUNDLE_ID: &str = "com.char.dev"; +const STABLE_BUNDLE_ID: &str = "com.char.stable"; +const STAGING_BUNDLE_ID: &str = "com.char.staging"; +const NIGHTLY_BUNDLE_ID: &str = "com.char.nightly"; const INSTALL_DIR: &str = "/usr/local/bin"; #[cfg_attr(target_os = "macos", allow(dead_code))] diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 6e7aa76aab..9df6b80ded 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -13,7 +13,7 @@ use tauri_plugin_permissions::{Permission, PermissionsPluginExt}; use tauri_plugin_windows::{AppWindow, WindowsPluginExt}; #[cfg(any(feature = "dev", feature = "devtools"))] -const STAGING_BUNDLE_ID: &str = "com.hyprnote.staging"; +const STAGING_BUNDLE_ID: &str = "com.char.staging"; fn create_audio_provider(_bundle_id: &str) -> std::sync::Arc { #[cfg(any(feature = "dev", feature = "devtools"))] @@ -49,7 +49,7 @@ pub async fn main() { if let Some(dsn) = dsn { let release = - option_env!("APP_VERSION").map(|v| format!("hyprnote-desktop@{}", v).into()); + option_env!("APP_VERSION").map(|v| format!("char-desktop@{}", v).into()); let client = sentry::init(( dsn, @@ -62,7 +62,7 @@ pub async fn main() { )); sentry::configure_scope(|scope| { - scope.set_tag("service.namespace", "hyprnote"); + scope.set_tag("service.namespace", "char"); scope.set_tag("service.name", "desktop"); scope.set_tag("enduser.pseudo.id", hypr_host::fingerprint()); scope.set_user(Some(sentry::User { diff --git a/apps/desktop/src-tauri/src/main.rs b/apps/desktop/src-tauri/src/main.rs index a51a714cdf..7c62c77287 100644 --- a/apps/desktop/src-tauri/src/main.rs +++ b/apps/desktop/src-tauri/src/main.rs @@ -2,5 +2,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] fn main() { - hyprnote_desktop_lib::main() + char_desktop_lib::main() } diff --git a/apps/desktop/src-tauri/tauri.conf.flatpak.json b/apps/desktop/src-tauri/tauri.conf.flatpak.json index b33958904a..90b4d69575 100644 --- a/apps/desktop/src-tauri/tauri.conf.flatpak.json +++ b/apps/desktop/src-tauri/tauri.conf.flatpak.json @@ -1,8 +1,8 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Char", - "mainBinaryName": "hyprnote", - "identifier": "com.hyprnote.Hyprnote", + "mainBinaryName": "char", + "identifier": "com.char.Char", "plugins": { "updater": { "active": false diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json index 3a32ea1dd1..3637c485c9 100644 --- a/apps/desktop/src-tauri/tauri.conf.json +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -2,7 +2,7 @@ "$schema": "https://schema.tauri.app/config/2", "productName": "Char Dev", "mainBinaryName": "char-dev", - "identifier": "com.hyprnote.dev", + "identifier": "com.char.dev", "build": { "beforeDevCommand": "pnpm -F desktop dev", "devUrl": "http://localhost:1422", @@ -72,7 +72,7 @@ "deep-link": { "desktop": { "schemes": [ - "hyprnote", + "char", "char" ] } diff --git a/apps/desktop/src-tauri/tauri.conf.nightly.json b/apps/desktop/src-tauri/tauri.conf.nightly.json index 07db16fa68..e1de1e362c 100644 --- a/apps/desktop/src-tauri/tauri.conf.nightly.json +++ b/apps/desktop/src-tauri/tauri.conf.nightly.json @@ -2,15 +2,15 @@ "$schema": "https://schema.tauri.app/config/2", "productName": "Char Nightly", "mainBinaryName": "char-nightly", - "identifier": "com.hyprnote.nightly", + "identifier": "com.char.nightly", "plugins": { "deep-link": { - "desktop": { "schemes": ["hyprnote-nightly"] } + "desktop": { "schemes": ["char-nightly"] } }, "updater": { "active": true, "endpoints": [ - "https://desktop2.hyprnote.com/update/{{target}}-{{arch}}/{{current_version}}?channel=nightly" + "https://desktop2.char.com/update/{{target}}-{{arch}}/{{current_version}}?channel=nightly" ] } }, diff --git a/apps/desktop/src-tauri/tauri.conf.stable.json b/apps/desktop/src-tauri/tauri.conf.stable.json index a2fab90f71..128c29e5f2 100644 --- a/apps/desktop/src-tauri/tauri.conf.stable.json +++ b/apps/desktop/src-tauri/tauri.conf.stable.json @@ -2,15 +2,15 @@ "$schema": "https://schema.tauri.app/config/2", "productName": "Char", "mainBinaryName": "char", - "identifier": "com.hyprnote.stable", + "identifier": "com.char.stable", "plugins": { "deep-link": { - "desktop": { "schemes": ["hyprnote"] } + "desktop": { "schemes": ["char"] } }, "updater": { "active": true, "endpoints": [ - "https://desktop2.hyprnote.com/update/{{target}}-{{arch}}/{{current_version}}?channel=stable" + "https://desktop2.char.com/update/{{target}}-{{arch}}/{{current_version}}?channel=stable" ] } } diff --git a/apps/desktop/src-tauri/tauri.conf.staging.json b/apps/desktop/src-tauri/tauri.conf.staging.json index ffc0dd585d..0b173751f8 100644 --- a/apps/desktop/src-tauri/tauri.conf.staging.json +++ b/apps/desktop/src-tauri/tauri.conf.staging.json @@ -2,10 +2,10 @@ "$schema": "https://schema.tauri.app/config/2", "productName": "Char Staging", "mainBinaryName": "char-staging", - "identifier": "com.hyprnote.staging", + "identifier": "com.char.staging", "plugins": { "deep-link": { - "desktop": { "schemes": ["hyprnote-staging"] } + "desktop": { "schemes": ["char-staging"] } }, "updater": { "active": false diff --git a/apps/desktop/src/ai/hooks/useLLMConnection.ts b/apps/desktop/src/ai/hooks/useLLMConnection.ts index 2fd2fc7253..fdce45a2e9 100644 --- a/apps/desktop/src/ai/hooks/useLLMConnection.ts +++ b/apps/desktop/src/ai/hooks/useLLMConnection.ts @@ -38,8 +38,8 @@ export type LLMConnectionStatus = | { status: "pending"; reason: "missing_provider" } | { status: "pending"; reason: "missing_model"; providerId: ProviderId } | { status: "error"; reason: "provider_not_found"; providerId: string } - | { status: "error"; reason: "unauthenticated"; providerId: "hyprnote" } - | { status: "error"; reason: "not_pro"; providerId: "hyprnote" } + | { status: "error"; reason: "unauthenticated"; providerId: "char" } + | { status: "error"; reason: "not_pro"; providerId: "char" } | { status: "error"; reason: "missing_config"; @@ -66,7 +66,7 @@ export const useLanguageModel = (task?: CharTask): LanguageModelV3 | null => { if (!conn) return null; const hostedFetch = - conn.providerId === "hyprnote" + conn.providerId === "char" ? createAuthFetch( task ? createTracedFetch(task) : tracedFetch, () => accessTokenRef.current, @@ -177,13 +177,13 @@ const resolveLLMConnection = (params: { if (blockers.length > 0) { const blocker = blockers[0]; - if (blocker.code === "requires_auth" && providerId === "hyprnote") { + if (blocker.code === "requires_auth" && providerId === "char") { return { conn: null, status: { status: "error", reason: "unauthenticated", providerId }, }; } - if (blocker.code === "requires_entitlement" && providerId === "hyprnote") { + if (blocker.code === "requires_entitlement" && providerId === "char") { return { conn: null, status: { status: "error", reason: "not_pro", providerId }, @@ -202,7 +202,7 @@ const resolveLLMConnection = (params: { } } - if (providerId === "hyprnote" && session) { + if (providerId === "char" && session) { return { conn: { providerId, @@ -255,7 +255,7 @@ const createLanguageModel = ( hostedFetch?: typeof fetch, ): LanguageModelV3 => { switch (conn.providerId) { - case "hyprnote": { + case "char": { const provider = createOpenRouter({ fetch: hostedFetch ?? (task ? createTracedFetch(task) : tracedFetch), baseURL: conn.baseUrl, diff --git a/apps/desktop/src/auth/billing.tsx b/apps/desktop/src/auth/billing.tsx index 9fbc94ac18..4e7c10ec58 100644 --- a/apps/desktop/src/auth/billing.tsx +++ b/apps/desktop/src/auth/billing.tsx @@ -104,7 +104,7 @@ export function BillingProvider({ children }: { children: ReactNode }) { return; } - if (current_llm_provider !== "hyprnote") { + if (current_llm_provider !== "char") { return; } diff --git a/apps/desktop/src/chat/mcp/useResearchMCP.ts b/apps/desktop/src/chat/mcp/useResearchMCP.ts index b4772d8170..aa1294d58e 100644 --- a/apps/desktop/src/chat/mcp/useResearchMCP.ts +++ b/apps/desktop/src/chat/mcp/useResearchMCP.ts @@ -4,7 +4,7 @@ export function useResearchMCP(enabled: boolean, accessToken?: string | null) { return useMCP({ enabled, endpoint: "/research/mcp", - clientName: "hyprnote-research-client", + clientName: "char-research-client", accessToken, promptName: "research_chat", }); diff --git a/apps/desktop/src/chat/mcp/useSupportMCP.ts b/apps/desktop/src/chat/mcp/useSupportMCP.ts index 6f9f5151a0..4926e3a5e3 100644 --- a/apps/desktop/src/chat/mcp/useSupportMCP.ts +++ b/apps/desktop/src/chat/mcp/useSupportMCP.ts @@ -46,7 +46,7 @@ export function useSupportMCP(enabled: boolean, accessToken?: string | null) { return useMCP({ enabled, endpoint: "/support/mcp", - clientName: "hyprnote-support-client", + clientName: "char-support-client", accessToken, promptName: "support_chat", collectContext, diff --git a/apps/desktop/src/contexts/notifications.tsx b/apps/desktop/src/contexts/notifications.tsx index 8616086e50..a05efba404 100644 --- a/apps/desktop/src/contexts/notifications.tsx +++ b/apps/desktop/src/contexts/notifications.tsx @@ -63,7 +63,7 @@ export function NotificationProvider({ children }: { children: ReactNode }) { const sttModel = current_stt_model as string | undefined; const isLocalSttModel = - current_stt_provider === "hyprnote" && !!sttModel && sttModel !== "cloud"; + current_stt_provider === "char" && !!sttModel && sttModel !== "cloud"; const localSttQuery = useQuery({ enabled: isLocalSttModel, diff --git a/apps/desktop/src/instruction/index.tsx b/apps/desktop/src/instruction/index.tsx index ec39f7f3d2..1cca999709 100644 --- a/apps/desktop/src/instruction/index.tsx +++ b/apps/desktop/src/instruction/index.tsx @@ -199,7 +199,7 @@ function SignInInstruction({ onBack }: { onBack: () => void }) { setCallbackUrl(e.target.value)} /> diff --git a/apps/desktop/src/main.tsx b/apps/desktop/src/main.tsx index 4954b96305..9fc979a0e1 100644 --- a/apps/desktop/src/main.tsx +++ b/apps/desktop/src/main.tsx @@ -89,7 +89,7 @@ if (env.VITE_SENTRY_DSN) { Sentry.init({ dsn: env.VITE_SENTRY_DSN, release: env.VITE_APP_VERSION - ? `hyprnote-desktop@${env.VITE_APP_VERSION}` + ? `char-desktop@${env.VITE_APP_VERSION}` : undefined, environment: import.meta.env.MODE, tracePropagationTargets: [], diff --git a/apps/desktop/src/services/calendar/ctx.test.ts b/apps/desktop/src/services/calendar/ctx.test.ts index ecae3b0072..7ac5416e37 100644 --- a/apps/desktop/src/services/calendar/ctx.test.ts +++ b/apps/desktop/src/services/calendar/ctx.test.ts @@ -47,10 +47,10 @@ describe("syncCalendars", () => { user_id: "user-1", created_at: "2026-03-25T00:00:00.000Z", tracking_id_calendar: "primary", - name: "John (Hyprnote)", + name: "John (Char)", enabled: true, provider: "google", - source: "john@hyprnote.com", + source: "john@char.com", color: "#4285f4", connection_id: "conn-john", }); @@ -63,8 +63,8 @@ describe("syncCalendars", () => { data: [ { id: "primary", - title: "John (Hyprnote)", - source: "john@hyprnote.com", + title: "John (Char)", + source: "john@char.com", color: "#4285f4", }, ], @@ -103,9 +103,9 @@ describe("syncCalendars", () => { calendars.find((calendar) => calendar.connection_id === "conn-john"), ).toMatchObject({ tracking_id_calendar: "primary", - name: "John (Hyprnote)", + name: "John (Char)", enabled: true, - source: "john@hyprnote.com", + source: "john@char.com", }); expect( calendars.find((calendar) => calendar.connection_id === "conn-gmail"), @@ -124,10 +124,10 @@ describe("syncCalendars", () => { user_id: "user-1", created_at: "2026-03-25T00:00:00.000Z", tracking_id_calendar: "primary", - name: "John (Hyprnote)", + name: "John (Char)", enabled: true, provider: "google", - source: "john@hyprnote.com", + source: "john@char.com", color: "#4285f4", connection_id: "conn-john", }); diff --git a/apps/desktop/src/settings/ai/llm/configure.tsx b/apps/desktop/src/settings/ai/llm/configure.tsx index 772d37a96d..411debfc4e 100644 --- a/apps/desktop/src/settings/ai/llm/configure.tsx +++ b/apps/desktop/src/settings/ai/llm/configure.tsx @@ -36,12 +36,12 @@ export function ConfigureProviders() { onValueChange={setAccordionValue} > } - badge={PROVIDERS.find((p) => p.id === "hyprnote")?.badge} + badge={PROVIDERS.find((p) => p.id === "char")?.badge} /> - {PROVIDERS.filter((provider) => provider.id !== "hyprnote").map( + {PROVIDERS.filter((provider) => provider.id !== "char").map( (provider) => ( ( - hasLlmConfigured ? "" : "hyprnote", + hasLlmConfigured ? "" : "char", ); const [shouldHighlight, setShouldHighlight] = useState(false); const { upgradeToPro } = useBillingAccess(); @@ -45,7 +45,7 @@ export function LlmSettingsProvider({ useEffect(() => { if (toastActionTarget === "llm") { - setAccordionValue("hyprnote"); + setAccordionValue("char"); setShouldHighlight(true); const timer = setTimeout(() => { @@ -67,7 +67,7 @@ export function LlmSettingsProvider({ }, [hasLlmConfigured, shouldHighlight]); const openHyprAccordion = useCallback(() => { - setAccordionValue("hyprnote"); + setAccordionValue("char"); }, []); const startTrial = useCallback(() => { diff --git a/apps/desktop/src/settings/ai/llm/select.tsx b/apps/desktop/src/settings/ai/llm/select.tsx index 1de00b605f..94e065569f 100644 --- a/apps/desktop/src/settings/ai/llm/select.tsx +++ b/apps/desktop/src/settings/ai/llm/select.tsx @@ -118,7 +118,7 @@ export function SelectProviderAndModel() { }; const handleProviderChange = (provider: string) => { - if (provider === "hyprnote" && !billing.isPaid) { + if (provider === "char" && !billing.isPaid) { billing.upgradeToPro(); return; } @@ -293,7 +293,7 @@ function useConfiguredMapping(): Record { return [provider.id, { listModels: undefined }]; } - if (provider.id === "hyprnote") { + if (provider.id === "char") { const result: ListModelsResult = { models: ["Auto"], ignored: [], diff --git a/apps/desktop/src/settings/ai/llm/shared.tsx b/apps/desktop/src/settings/ai/llm/shared.tsx index 6d83418192..ededbdc9eb 100644 --- a/apps/desktop/src/settings/ai/llm/shared.tsx +++ b/apps/desktop/src/settings/ai/llm/shared.tsx @@ -36,7 +36,7 @@ type Provider = { const _PROVIDERS = [ { - id: "hyprnote", + id: "char", displayName: "Char", badge: "Recommended", icon: , diff --git a/apps/desktop/src/settings/ai/shared/model-combobox.tsx b/apps/desktop/src/settings/ai/shared/model-combobox.tsx index 4ab12c6351..ff2d0297e6 100644 --- a/apps/desktop/src/settings/ai/shared/model-combobox.tsx +++ b/apps/desktop/src/settings/ai/shared/model-combobox.tsx @@ -67,7 +67,7 @@ const formatIgnoreReason = (reason: ModelIgnoreReason): string => { }; const getDisplayName = (providerId: string, model: string): string => { - if (providerId === "hyprnote" && model === "Auto") { + if (providerId === "char" && model === "Auto") { return "Pro (Cloud)"; } return model; diff --git a/apps/desktop/src/settings/ai/shared/sort-providers.ts b/apps/desktop/src/settings/ai/shared/sort-providers.ts index 2431a95f99..13694cc768 100644 --- a/apps/desktop/src/settings/ai/shared/sort-providers.ts +++ b/apps/desktop/src/settings/ai/shared/sort-providers.ts @@ -8,8 +8,8 @@ export function sortProviders( providers: readonly T[], ): T[] { return [...providers].sort((a, b) => { - if (a.id === "hyprnote") return -1; - if (b.id === "hyprnote") return 1; + if (a.id === "char") return -1; + if (b.id === "char") return 1; if (a.disabled && !b.disabled) return 1; if (!a.disabled && b.disabled) return -1; diff --git a/apps/desktop/src/settings/ai/stt/configure.tsx b/apps/desktop/src/settings/ai/stt/configure.tsx index a7abec0ca5..d06e601713 100644 --- a/apps/desktop/src/settings/ai/stt/configure.tsx +++ b/apps/desktop/src/settings/ai/stt/configure.tsx @@ -54,12 +54,12 @@ export function ConfigureProviders() { > } - badge={PROVIDERS.find((p) => p.id === "hyprnote")?.badge} + badge={PROVIDERS.find((p) => p.id === "char")?.badge} /> - {PROVIDERS.filter((provider) => provider.id !== "hyprnote").map( + {PROVIDERS.filter((provider) => provider.id !== "char").map( (provider) => ( ( - hasSttConfigured ? "" : "hyprnote", + hasSttConfigured ? "" : "char", ); const [shouldHighlight, setShouldHighlight] = useState(false); const { upgradeToPro } = useBillingAccess(); @@ -51,7 +51,7 @@ export function SttSettingsProvider({ useEffect(() => { if (toastActionTarget === "stt") { - setAccordionValue("hyprnote"); + setAccordionValue("char"); setShouldHighlight(true); const timer = setTimeout(() => { @@ -73,7 +73,7 @@ export function SttSettingsProvider({ }, [hasSttConfigured, shouldHighlight]); const openHyprAccordion = useCallback(() => { - setAccordionValue("hyprnote"); + setAccordionValue("char"); }, []); const startDownload = useCallback( diff --git a/apps/desktop/src/settings/ai/stt/health.tsx b/apps/desktop/src/settings/ai/stt/health.tsx index f0f60c4f7b..05d53a32c7 100644 --- a/apps/desktop/src/settings/ai/stt/health.tsx +++ b/apps/desktop/src/settings/ai/stt/health.tsx @@ -53,8 +53,8 @@ export function useConnectionHealth(): HealthStatus { ] as const); const isCloud = - (current_stt_provider === "hyprnote" && current_stt_model === "cloud") || - current_stt_provider !== "hyprnote"; + (current_stt_provider === "char" && current_stt_model === "cloud") || + current_stt_provider !== "char"; const isDeepgram = current_stt_provider === "deepgram"; const deepgramHealth = useDeepgramHealth(isDeepgram && !!conn, conn?.apiKey); diff --git a/apps/desktop/src/settings/ai/stt/select.tsx b/apps/desktop/src/settings/ai/stt/select.tsx index 61b58f8369..f2eded9cb1 100644 --- a/apps/desktop/src/settings/ai/stt/select.tsx +++ b/apps/desktop/src/settings/ai/stt/select.tsx @@ -361,7 +361,7 @@ function useConfiguredMapping(): Record< return [provider.id, { configured: false, models: [] }]; } - if (provider.id === "hyprnote") { + if (provider.id === "char") { const models: ModelEntry[] = [ { id: "cloud", isDownloaded: billing.isPaid, category: "latest" }, ]; diff --git a/apps/desktop/src/settings/ai/stt/shared.tsx b/apps/desktop/src/settings/ai/stt/shared.tsx index f41594ad6d..7ce31269b1 100644 --- a/apps/desktop/src/settings/ai/stt/shared.tsx +++ b/apps/desktop/src/settings/ai/stt/shared.tsx @@ -95,7 +95,7 @@ export const displayModelId = (model: string) => { const _PROVIDERS = [ { disabled: false, - id: "hyprnote", + id: "char", displayName: "Char", badge: "Recommended", icon: , diff --git a/apps/desktop/src/settings/lab/download-buttons.tsx b/apps/desktop/src/settings/lab/download-buttons.tsx index 53639826ec..39e2aeecc1 100644 --- a/apps/desktop/src/settings/lab/download-buttons.tsx +++ b/apps/desktop/src/settings/lab/download-buttons.tsx @@ -19,8 +19,8 @@ export function DownloadButtons() { staleTime: Infinity, }); - const isDev = identifierQuery.data === "com.hyprnote.dev"; - const isNightly = identifierQuery.data === "com.hyprnote.nightly"; + const isDev = identifierQuery.data === "com.char.dev"; + const isNightly = identifierQuery.data === "com.char.nightly"; const channels: Array<"stable" | "nightly"> = isDev ? ["stable", "nightly"] @@ -32,15 +32,15 @@ export function DownloadButtons() { const targetArch = archQuery.data; if (platformName === "macos") { if (targetArch === "aarch64") { - return `https://desktop2.hyprnote.com/download/latest/dmg-aarch64?channel=${channel}`; + return `https://desktop2.char.com/download/latest/dmg-aarch64?channel=${channel}`; } - return `https://desktop2.hyprnote.com/download/latest/dmg-x86_64?channel=${channel}`; + return `https://desktop2.char.com/download/latest/dmg-x86_64?channel=${channel}`; } if (platformName === "linux") { if (targetArch === "aarch64") { - return `https://desktop2.hyprnote.com/download/latest/appimage-aarch64?channel=${channel}`; + return `https://desktop2.char.com/download/latest/appimage-aarch64?channel=${channel}`; } - return `https://desktop2.hyprnote.com/download/latest/appimage-x86_64?channel=${channel}`; + return `https://desktop2.char.com/download/latest/appimage-x86_64?channel=${channel}`; } return null; }; diff --git a/apps/desktop/src/shared/config/configure-paid-settings.ts b/apps/desktop/src/shared/config/configure-paid-settings.ts index 5b9d1d0028..1a2973b42e 100644 --- a/apps/desktop/src/shared/config/configure-paid-settings.ts +++ b/apps/desktop/src/shared/config/configure-paid-settings.ts @@ -7,12 +7,12 @@ export function configurePaidSettings(store: SettingsStore): void { const currentLlmProvider = store.getValue("current_llm_provider"); if (!currentSttProvider) { - store.setValue("current_stt_provider", "hyprnote"); + store.setValue("current_stt_provider", "char"); store.setValue("current_stt_model", "cloud"); } if (!currentLlmProvider) { - store.setValue("current_llm_provider", "hyprnote"); + store.setValue("current_llm_provider", "char"); store.setValue("current_llm_model", "Auto"); } } diff --git a/apps/desktop/src/shared/ui/resource-list/hooks.ts b/apps/desktop/src/shared/ui/resource-list/hooks.ts index b4c2a2f350..18aa60a0ee 100644 --- a/apps/desktop/src/shared/ui/resource-list/hooks.ts +++ b/apps/desktop/src/shared/ui/resource-list/hooks.ts @@ -4,7 +4,7 @@ export function useWebResources(endpoint: string) { return useQuery({ queryKey: ["settings", endpoint, "suggestions"], queryFn: async () => { - const response = await fetch(`https://hyprnote.com/api/${endpoint}`, { + const response = await fetch(`https://char.com/api/${endpoint}`, { headers: { Accept: "application/json" }, }); return response.json() as Promise; diff --git a/apps/desktop/src/shared/utils.ts b/apps/desktop/src/shared/utils.ts index 3977f3b55f..019233655c 100644 --- a/apps/desktop/src/shared/utils.ts +++ b/apps/desktop/src/shared/utils.ts @@ -11,10 +11,10 @@ export const id = () => crypto.randomUUID() as string; export const getScheme = async (): Promise => { const id = await getIdentifier(); const schemes: Record = { - "com.hyprnote.stable": "hyprnote", - "com.hyprnote.nightly": "hyprnote-nightly", - "com.hyprnote.staging": "hyprnote-staging", - "com.hyprnote.dev": "hypr", + "com.char.stable": "char", + "com.char.nightly": "char-nightly", + "com.char.staging": "char-staging", + "com.char.dev": "hypr", }; return schemes[id] ?? "hypr"; }; diff --git a/apps/desktop/src/sidebar/toast/index.tsx b/apps/desktop/src/sidebar/toast/index.tsx index 7eef64c2f2..51c6ef3b79 100644 --- a/apps/desktop/src/sidebar/toast/index.tsx +++ b/apps/desktop/src/sidebar/toast/index.tsx @@ -46,8 +46,8 @@ export function ToastArea({ const hasLLMConfigured = !!(current_llm_provider && current_llm_model); const hasSttConfigured = !!(current_stt_provider && current_stt_model); const hasProSttConfigured = - current_stt_provider === "hyprnote" && current_stt_model === "cloud"; - const hasProLlmConfigured = current_llm_provider === "hyprnote"; + current_stt_provider === "char" && current_stt_model === "cloud"; + const hasProLlmConfigured = current_llm_provider === "char"; const currentTab = useTabs((state) => state.currentTab); const isAiTranscriptionTabActive = diff --git a/apps/desktop/src/sidebar/toast/registry.tsx b/apps/desktop/src/sidebar/toast/registry.tsx index 60d91fb358..f1ac69a004 100644 --- a/apps/desktop/src/sidebar/toast/registry.tsx +++ b/apps/desktop/src/sidebar/toast/registry.tsx @@ -141,7 +141,7 @@ export function createToastRegistry({ id: "pro-requires-login", icon: ( Char Pro @@ -166,7 +166,7 @@ export function createToastRegistry({ id: "upgrade-to-pro", icon: ( Char Pro diff --git a/apps/desktop/src/store/tinybase/persister/chat/changes.test.ts b/apps/desktop/src/store/tinybase/persister/chat/changes.test.ts index 87d5820e57..8f3c4e6a27 100644 --- a/apps/desktop/src/store/tinybase/persister/chat/changes.test.ts +++ b/apps/desktop/src/store/tinybase/persister/chat/changes.test.ts @@ -52,7 +52,7 @@ describe("parseChatGroupIdFromPath", () => { test("extracts chat group ID from absolute path", () => { expect( parseChatGroupIdFromPath( - "/Users/test/data/hyprnote/chats/abc-123/file", + "/Users/test/data/char/chats/abc-123/file", ), ).toBe("abc-123"); }); diff --git a/apps/desktop/src/store/tinybase/persister/factories/json-file.test.ts b/apps/desktop/src/store/tinybase/persister/factories/json-file.test.ts index bdd39c8446..4f339240da 100644 --- a/apps/desktop/src/store/tinybase/persister/factories/json-file.test.ts +++ b/apps/desktop/src/store/tinybase/persister/factories/json-file.test.ts @@ -10,7 +10,7 @@ import { const settingsMocks = vi.hoisted(() => ({ vaultBase: vi .fn() - .mockResolvedValue({ status: "ok", data: "/mock/data/dir/hyprnote" }), + .mockResolvedValue({ status: "ok", data: "/mock/data/dir/char" }), })); const fs2Mocks = vi.hoisted(() => ({ diff --git a/apps/desktop/src/store/tinybase/persister/factories/markdown-dir.test.ts b/apps/desktop/src/store/tinybase/persister/factories/markdown-dir.test.ts index 7310440e3c..fa2877f524 100644 --- a/apps/desktop/src/store/tinybase/persister/factories/markdown-dir.test.ts +++ b/apps/desktop/src/store/tinybase/persister/factories/markdown-dir.test.ts @@ -14,7 +14,7 @@ import { const settingsMocks = vi.hoisted(() => ({ vaultBase: vi .fn() - .mockResolvedValue({ status: "ok", data: "/mock/data/dir/hyprnote" }), + .mockResolvedValue({ status: "ok", data: "/mock/data/dir/char" }), })); const fsSyncMocks = vi.hoisted(() => ({ diff --git a/apps/desktop/src/store/tinybase/persister/factories/multi-table-dir.test.ts b/apps/desktop/src/store/tinybase/persister/factories/multi-table-dir.test.ts index 963bec46d3..5832ad084e 100644 --- a/apps/desktop/src/store/tinybase/persister/factories/multi-table-dir.test.ts +++ b/apps/desktop/src/store/tinybase/persister/factories/multi-table-dir.test.ts @@ -8,7 +8,7 @@ import { createTestMainStore } from "~/store/tinybase/persister/testing/mocks"; const settingsMocks = vi.hoisted(() => ({ base: vi .fn() - .mockResolvedValue({ status: "ok", data: "/mock/data/dir/hyprnote" }), + .mockResolvedValue({ status: "ok", data: "/mock/data/dir/char" }), })); const fsSyncMocks = vi.hoisted(() => ({ diff --git a/apps/desktop/src/store/tinybase/persister/human/changes.test.ts b/apps/desktop/src/store/tinybase/persister/human/changes.test.ts index d83093ba5f..f70f34ec61 100644 --- a/apps/desktop/src/store/tinybase/persister/human/changes.test.ts +++ b/apps/desktop/src/store/tinybase/persister/human/changes.test.ts @@ -45,7 +45,7 @@ describe("parseHumanIdFromPath", () => { describe("absolute paths (defensive handling)", () => { test("parses id from absolute path", () => { - expect(parseHumanIdFromPath("/data/hyprnote/humans/person-123.md")).toBe( + expect(parseHumanIdFromPath("/data/char/humans/person-123.md")).toBe( "person-123", ); }); diff --git a/apps/desktop/src/store/tinybase/persister/human/persister.test.ts b/apps/desktop/src/store/tinybase/persister/human/persister.test.ts index a7d788a528..da125cee98 100644 --- a/apps/desktop/src/store/tinybase/persister/human/persister.test.ts +++ b/apps/desktop/src/store/tinybase/persister/human/persister.test.ts @@ -11,7 +11,7 @@ import { const settingsMocks = vi.hoisted(() => ({ vaultBase: vi .fn() - .mockResolvedValue({ status: "ok", data: "/mock/data/dir/hyprnote" }), + .mockResolvedValue({ status: "ok", data: "/mock/data/dir/char" }), })); const fsSyncMocks = vi.hoisted(() => ({ diff --git a/apps/desktop/src/store/tinybase/persister/organization/changes.test.ts b/apps/desktop/src/store/tinybase/persister/organization/changes.test.ts index 7621fb2714..509038104c 100644 --- a/apps/desktop/src/store/tinybase/persister/organization/changes.test.ts +++ b/apps/desktop/src/store/tinybase/persister/organization/changes.test.ts @@ -53,7 +53,7 @@ describe("parseOrganizationIdFromPath", () => { test("parses id from absolute path", () => { expect( parseOrganizationIdFromPath( - "/data/hyprnote/organizations/acme-corp.md", + "/data/char/organizations/acme-corp.md", ), ).toBe("acme-corp"); }); diff --git a/apps/desktop/src/store/tinybase/persister/organization/persister.test.ts b/apps/desktop/src/store/tinybase/persister/organization/persister.test.ts index c33ae77788..26483960bf 100644 --- a/apps/desktop/src/store/tinybase/persister/organization/persister.test.ts +++ b/apps/desktop/src/store/tinybase/persister/organization/persister.test.ts @@ -10,7 +10,7 @@ import { const settingsMocks = vi.hoisted(() => ({ vaultBase: vi .fn() - .mockResolvedValue({ status: "ok", data: "/mock/data/dir/hyprnote" }), + .mockResolvedValue({ status: "ok", data: "/mock/data/dir/char" }), })); const fsSyncMocks = vi.hoisted(() => ({ diff --git a/apps/desktop/src/store/tinybase/persister/prompts/changes.test.ts b/apps/desktop/src/store/tinybase/persister/prompts/changes.test.ts index 5ea8b457c1..ac560301bf 100644 --- a/apps/desktop/src/store/tinybase/persister/prompts/changes.test.ts +++ b/apps/desktop/src/store/tinybase/persister/prompts/changes.test.ts @@ -47,7 +47,7 @@ describe("parsePromptIdFromPath", () => { describe("absolute paths (defensive handling)", () => { test("parses id from absolute path", () => { - expect(parsePromptIdFromPath("/data/hyprnote/prompts/my-prompt.md")).toBe( + expect(parsePromptIdFromPath("/data/char/prompts/my-prompt.md")).toBe( "my-prompt", ); }); diff --git a/apps/desktop/src/store/tinybase/persister/session/load/meta.test.ts b/apps/desktop/src/store/tinybase/persister/session/load/meta.test.ts index a2c459580c..b2e3b2a4f5 100644 --- a/apps/desktop/src/store/tinybase/persister/session/load/meta.test.ts +++ b/apps/desktop/src/store/tinybase/persister/session/load/meta.test.ts @@ -7,31 +7,31 @@ describe("extractSessionIdAndFolder", () => { describe("standard paths", () => { test("extracts session id and empty folder from root session path", () => { const result = extractSessionIdAndFolder( - "/data/hyprnote/sessions/session-123/_meta.json", + "/data/char/sessions/session-123/_meta.json", ); expect(result).toEqual({ sessionId: "session-123", - folderPath: "/data/hyprnote/sessions", + folderPath: "/data/char/sessions", }); }); test("extracts session id and folder from nested path", () => { const result = extractSessionIdAndFolder( - "/data/hyprnote/sessions/work/session-123/_meta.json", + "/data/char/sessions/work/session-123/_meta.json", ); expect(result).toEqual({ sessionId: "session-123", - folderPath: "/data/hyprnote/sessions/work", + folderPath: "/data/char/sessions/work", }); }); test("extracts session id and folder from deeply nested path", () => { const result = extractSessionIdAndFolder( - "/data/hyprnote/sessions/work/project-a/meetings/session-123/_meta.json", + "/data/char/sessions/work/project-a/meetings/session-123/_meta.json", ); expect(result).toEqual({ sessionId: "session-123", - folderPath: "/data/hyprnote/sessions/work/project-a/meetings", + folderPath: "/data/char/sessions/work/project-a/meetings", }); }); }); diff --git a/apps/desktop/src/store/tinybase/persister/shared/paths.test.ts b/apps/desktop/src/store/tinybase/persister/shared/paths.test.ts index 04ca10516d..2d446a5e83 100644 --- a/apps/desktop/src/store/tinybase/persister/shared/paths.test.ts +++ b/apps/desktop/src/store/tinybase/persister/shared/paths.test.ts @@ -15,79 +15,79 @@ vi.mock("@tauri-apps/api/path", () => ({ })); describe("buildSessionPath", () => { - const dataDir = "/data/hyprnote"; + const dataDir = "/data/char"; test("builds path without folder", () => { expect(buildSessionPath(dataDir, "session-123")).toBe( - "/data/hyprnote/sessions/session-123", + "/data/char/sessions/session-123", ); }); test("builds path with empty folder", () => { expect(buildSessionPath(dataDir, "session-123", "")).toBe( - "/data/hyprnote/sessions/session-123", + "/data/char/sessions/session-123", ); }); test("builds path with single-level folder", () => { expect(buildSessionPath(dataDir, "session-123", "work")).toBe( - "/data/hyprnote/sessions/work/session-123", + "/data/char/sessions/work/session-123", ); }); test("builds path with nested folder", () => { expect(buildSessionPath(dataDir, "session-123", "work/project-a")).toBe( - "/data/hyprnote/sessions/work/project-a/session-123", + "/data/char/sessions/work/project-a/session-123", ); }); test("builds path with deeply nested folder", () => { expect( buildSessionPath(dataDir, "session-123", "work/project-a/meetings"), - ).toBe("/data/hyprnote/sessions/work/project-a/meetings/session-123"); + ).toBe("/data/char/sessions/work/project-a/meetings/session-123"); }); }); describe("buildChatPath", () => { - const dataDir = "/data/hyprnote"; + const dataDir = "/data/char"; test("builds chat path", () => { expect(buildChatPath(dataDir, "chat-456")).toBe( - "/data/hyprnote/chats/chat-456", + "/data/char/chats/chat-456", ); }); test("builds chat path with uuid", () => { expect(buildChatPath(dataDir, "550e8400-e29b-41d4-a716-446655440000")).toBe( - "/data/hyprnote/chats/550e8400-e29b-41d4-a716-446655440000", + "/data/char/chats/550e8400-e29b-41d4-a716-446655440000", ); }); }); describe("buildEntityPath", () => { - const dataDir = "/data/hyprnote"; + const dataDir = "/data/char"; test("builds entity path for humans", () => { - expect(buildEntityPath(dataDir, "humans")).toBe("/data/hyprnote/humans"); + expect(buildEntityPath(dataDir, "humans")).toBe("/data/char/humans"); }); test("builds entity path for organizations", () => { expect(buildEntityPath(dataDir, "organizations")).toBe( - "/data/hyprnote/organizations", + "/data/char/organizations", ); }); test("builds entity path for prompts", () => { - expect(buildEntityPath(dataDir, "prompts")).toBe("/data/hyprnote/prompts"); + expect(buildEntityPath(dataDir, "prompts")).toBe("/data/char/prompts"); }); }); describe("buildEntityFilePath", () => { - const dataDir = "/data/hyprnote"; + const dataDir = "/data/char"; test("builds entity file path with .md extension", () => { expect(buildEntityFilePath(dataDir, "humans", "person-123")).toBe( - "/data/hyprnote/humans/person-123.md", + "/data/char/humans/person-123.md", ); }); @@ -98,12 +98,12 @@ describe("buildEntityFilePath", () => { "humans", "550e8400-e29b-41d4-a716-446655440000", ), - ).toBe("/data/hyprnote/humans/550e8400-e29b-41d4-a716-446655440000.md"); + ).toBe("/data/char/humans/550e8400-e29b-41d4-a716-446655440000.md"); }); test("builds entity file path for organizations", () => { expect(buildEntityFilePath(dataDir, "organizations", "acme-corp")).toBe( - "/data/hyprnote/organizations/acme-corp.md", + "/data/char/organizations/acme-corp.md", ); }); }); @@ -237,7 +237,7 @@ describe("createMarkdownEntityParser", () => { describe("absolute paths (defensive handling)", () => { test("parses id from path with leading segments", () => { - expect(parseHumanId("/data/hyprnote/humans/person-123.md")).toBe( + expect(parseHumanId("/data/char/humans/person-123.md")).toBe( "person-123", ); }); diff --git a/apps/desktop/src/store/tinybase/persister/testing/mocks.ts b/apps/desktop/src/store/tinybase/persister/testing/mocks.ts index 4aac510735..623e20c444 100644 --- a/apps/desktop/src/store/tinybase/persister/testing/mocks.ts +++ b/apps/desktop/src/store/tinybase/persister/testing/mocks.ts @@ -7,7 +7,7 @@ import { type Store as SettingsStore, } from "~/store/tinybase/store/settings"; -export const MOCK_DATA_DIR = "/mock/data/dir/hyprnote"; +export const MOCK_DATA_DIR = "/mock/data/dir/char"; export const TEST_UUID_1 = "550e8400-e29b-41d4-a716-446655440000"; export const TEST_UUID_2 = "550e8400-e29b-41d4-a716-446655440001"; diff --git a/apps/desktop/src/store/tinybase/store/settings.ts b/apps/desktop/src/store/tinybase/store/settings.ts index 02f62fa7b5..fab760755a 100644 --- a/apps/desktop/src/store/tinybase/store/settings.ts +++ b/apps/desktop/src/store/tinybase/store/settings.ts @@ -310,7 +310,7 @@ const SETTINGS_LISTENERS: SettingsListeners = { | undefined; const model = store.getValue("current_stt_model") as string | undefined; - if (provider === "hyprnote" && model && model !== "cloud") { + if (provider === "char" && model && model !== "cloud") { localSttCommands.startServer(model as LocalModel).catch(console.error); } }, @@ -320,7 +320,7 @@ const SETTINGS_LISTENERS: SettingsListeners = { | undefined; const model = store.getValue("current_stt_model") as string | undefined; - if (provider === "hyprnote" && model && model !== "cloud") { + if (provider === "char" && model && model !== "cloud") { localSttCommands.startServer(model as LocalModel).catch(console.error); } else { localSttCommands.stopServer(null).catch(console.error); diff --git a/apps/desktop/src/store/zustand/listener/general-batch.test.ts b/apps/desktop/src/store/zustand/listener/general-batch.test.ts index 9eb475dc6b..2c137f9698 100644 --- a/apps/desktop/src/store/zustand/listener/general-batch.test.ts +++ b/apps/desktop/src/store/zustand/listener/general-batch.test.ts @@ -93,7 +93,7 @@ describe("runBatchSession", () => { "session-1", { session_id: "session-1", - provider: "hyprnote", + provider: "char", file_path: "/tmp/session.wav", base_url: "", api_key: "", @@ -195,7 +195,7 @@ describe("runBatchSession", () => { "session-1", { session_id: "session-1", - provider: "hyprnote", + provider: "char", file_path: "/tmp/session.wav", base_url: "", api_key: "", @@ -271,7 +271,7 @@ describe("runBatchSession", () => { "session-1", { session_id: "session-1", - provider: "hyprnote", + provider: "char", file_path: "/tmp/session.wav", base_url: "", api_key: "", @@ -349,7 +349,7 @@ describe("runBatchSession", () => { "session-1", { session_id: "session-1", - provider: "hyprnote", + provider: "char", file_path: "/tmp/session.wav", base_url: "", api_key: "", diff --git a/apps/desktop/src/store/zustand/listener/general-live.ts b/apps/desktop/src/store/zustand/listener/general-live.ts index f09de8b2ab..bd1629fcb3 100644 --- a/apps/desktop/src/store/zustand/listener/general-live.ts +++ b/apps/desktop/src/store/zustand/listener/general-live.ts @@ -236,7 +236,7 @@ export const startLiveSession = ( .then((r) => r.status === "ok" ? r.data.map((app) => app.id) : null, ), - getIdentifier().catch(() => "com.hyprnote.stable"), + getIdentifier().catch(() => "com.char.stable"), ]), catch: (error) => error, }); @@ -250,7 +250,7 @@ export const startLiveSession = ( beforeListeningStarted: { args: { resource_dir: sessionPath, - app_hyprnote: bundleId, + app_char: bundleId, app_meeting, }, }, @@ -318,7 +318,7 @@ export const stopLiveSession = ( if (r.status === "error") throw new Error(r.error); return r.data; }), - getIdentifier().catch(() => "com.hyprnote.stable"), + getIdentifier().catch(() => "com.char.stable"), ]) .then(([dataDirPath, bundleId]) => { const sessionPath = buildSessionPath(dataDirPath, sessionId); @@ -326,7 +326,7 @@ export const stopLiveSession = ( afterListeningStopped: { args: { resource_dir: sessionPath, - app_hyprnote: bundleId, + app_char: bundleId, app_meeting: null, }, }, diff --git a/apps/desktop/src/store/zustand/listener/general.test.ts b/apps/desktop/src/store/zustand/listener/general.test.ts index da11376eeb..aa006d9d23 100644 --- a/apps/desktop/src/store/zustand/listener/general.test.ts +++ b/apps/desktop/src/store/zustand/listener/general.test.ts @@ -234,7 +234,7 @@ describe("General Listener Slice", () => { await expect( store.getState().startTranscription({ session_id: sessionId, - provider: "hyprnote", + provider: "char", file_path: "/tmp/session.wav", base_url: "", api_key: "", diff --git a/apps/desktop/src/stt/useRunBatch.ts b/apps/desktop/src/stt/useRunBatch.ts index e7ac1f45cc..fff9e0177f 100644 --- a/apps/desktop/src/stt/useRunBatch.ts +++ b/apps/desktop/src/stt/useRunBatch.ts @@ -49,10 +49,10 @@ export function getBatchProvider( provider: string, model: string, ): TranscriptionParams["provider"] | null { - if (provider === "hyprnote") { + if (provider === "char") { if (model.startsWith("am-")) return "am"; if (model.startsWith("cactus-")) return "cactus"; - return "hyprnote"; + return "char"; } return BATCH_PROVIDER_MAP[provider] ?? null; } diff --git a/apps/desktop/src/stt/useSTTConnection.ts b/apps/desktop/src/stt/useSTTConnection.ts index 23a4235ae6..8bac4fa2da 100644 --- a/apps/desktop/src/stt/useSTTConnection.ts +++ b/apps/desktop/src/stt/useSTTConnection.ts @@ -31,15 +31,15 @@ export const useSTTConnection = () => { ) as AIProviderStorage | undefined; const isLocalModel = - current_stt_provider === "hyprnote" && + current_stt_provider === "char" && !!current_stt_model && current_stt_model !== "cloud"; const isCloudModel = - current_stt_provider === "hyprnote" && current_stt_model === "cloud"; + current_stt_provider === "char" && current_stt_model === "cloud"; const local = useQuery({ - enabled: current_stt_provider === "hyprnote", + enabled: current_stt_provider === "char", queryKey: ["stt-connection", isLocalModel, current_stt_model], refetchInterval: 1000, queryFn: async () => { diff --git a/apps/k6/package.json b/apps/k6/package.json index 6667c866dd..7c8cbf7f93 100644 --- a/apps/k6/package.json +++ b/apps/k6/package.json @@ -3,6 +3,6 @@ "private": true, "scripts": { "test:local": "k6 run scripts/websocket/listen.js --env API_URL=ws://localhost:4000", - "test:loadtest": "k6 run scripts/websocket/listen.js --env API_URL=wss://hyprnote-api-loadtest.fly.dev" + "test:loadtest": "k6 run scripts/websocket/listen.js --env API_URL=wss://char-api-loadtest.fly.dev" } } diff --git a/apps/slack-internal/fly.toml b/apps/slack-internal/fly.toml index 75f8aa5e6c..7d14d93fc5 100644 --- a/apps/slack-internal/fly.toml +++ b/apps/slack-internal/fly.toml @@ -1,4 +1,4 @@ -app = 'hyprnote-slack-internal' +app = 'char-slack-internal' primary_region = 'sjc' [build] diff --git a/apps/slack-internal/manifest.json b/apps/slack-internal/manifest.json index f43187a93a..97e38df4c9 100644 --- a/apps/slack-internal/manifest.json +++ b/apps/slack-internal/manifest.json @@ -4,13 +4,13 @@ "minor_version": 1 }, "display_information": { - "name": "Hyprnote Internal", - "description": "Hyprnote Slack bot for internal usage", + "name": "Char Internal", + "description": "Char Slack bot for internal usage", "background_color": "#1a1a2e" }, "features": { "bot_user": { - "display_name": "Hyprnote", + "display_name": "Char", "always_online": true } }, diff --git a/apps/stripe/fly.toml b/apps/stripe/fly.toml index c5251d3b5b..11c9939304 100644 --- a/apps/stripe/fly.toml +++ b/apps/stripe/fly.toml @@ -1,9 +1,9 @@ -# fly.toml app configuration file generated for hyprnote-stripe on 2026-02-05T16:52:13+09:00 +# fly.toml app configuration file generated for char-stripe on 2026-02-05T16:52:13+09:00 # # See https://fly.io/docs/reference/configuration/ for information about how to use this file. # -app = 'hyprnote-stripe' +app = 'char-stripe' primary_region = 'sjc' kill_signal = 'SIGTERM' kill_timeout = '30s' diff --git a/apps/stripe/src/scripts/stripe-backfill-entitlements.ts b/apps/stripe/src/scripts/stripe-backfill-entitlements.ts index a09d6e075f..fbd7d04f7f 100644 --- a/apps/stripe/src/scripts/stripe-backfill-entitlements.ts +++ b/apps/stripe/src/scripts/stripe-backfill-entitlements.ts @@ -31,7 +31,7 @@ if (!DATABASE_URL) { const pool = new pg.Pool({ connectionString: DATABASE_URL }); const PRODUCT_ID = "prod_SHWUtH1i2DPvSD"; -const ENTITLEMENT_LOOKUP_KEY = "hyprnote_pro"; +const ENTITLEMENT_LOOKUP_KEY = "char_pro"; class DbError { readonly _tag = "DbError"; diff --git a/apps/web/content-collections.ts b/apps/web/content-collections.ts index 2b6cda0f5d..4e29a8b2af 100644 --- a/apps/web/content-collections.ts +++ b/apps/web/content-collections.ts @@ -73,7 +73,7 @@ async function embedGithubCode(content: string): Promise { const [fullMatch, url] = match; const repoMatch = url.match( - /github\.com\/fastrepl\/(hyprnote|char)\/blob\/[^/]+\/(.+?)(?:#L\d+(?:-L\d+)?)?$/, + /github\.com\/fastrepl\/(char|char)\/blob\/[^/]+\/(.+?)(?:#L\d+(?:-L\d+)?)?$/, ); if (repoMatch) { const filePath = repoMatch[2]; diff --git a/apps/web/content/AGENTS.md b/apps/web/content/AGENTS.md index dcff04e8b1..8a4fb6ca80 100644 --- a/apps/web/content/AGENTS.md +++ b/apps/web/content/AGENTS.md @@ -102,7 +102,7 @@ A notepad that gives complete control without getting in your way. Clean, simple ## Critical Reminders -- **Name:** Always use "Char" (not "Hyprnote" - that's the old name) +- **Name:** Always use "Char" (not "Char" - that's the old name) - **Tone:** Direct, engineering-minded, respects user intelligence - **Focus:** Zero lock-in, true ownership, complete control - **Avoid:** Generic productivity language, corporate marketing speak, fear-based messaging diff --git a/apps/web/content/articles/best-ai-meeting-assistant-for-taking-notes.mdx b/apps/web/content/articles/best-ai-meeting-assistant-for-taking-notes.mdx index 1186fad2cf..c29ee08825 100644 --- a/apps/web/content/articles/best-ai-meeting-assistant-for-taking-notes.mdx +++ b/apps/web/content/articles/best-ai-meeting-assistant-for-taking-notes.mdx @@ -63,7 +63,7 @@ _If you're specifically looking for budget-friendly options, check out our compr Everything is stored as plain markdown files—not in proprietary databases. You choose your AI: managed cloud, bring your own keys, or run local models. -Char app mockup +Char app mockup #### About Char's AI notetaker: diff --git a/apps/web/content/articles/bot-free-ai-meeting-assistants.mdx b/apps/web/content/articles/bot-free-ai-meeting-assistants.mdx index b9da696afc..e0ebc9ed15 100644 --- a/apps/web/content/articles/bot-free-ai-meeting-assistants.mdx +++ b/apps/web/content/articles/bot-free-ai-meeting-assistants.mdx @@ -49,7 +49,7 @@ Char's Smart Consent Management Package for enterprises stands out. It handles c [Download Char for MacOS](/download). -Char +Char #### Top features diff --git a/apps/web/content/articles/building-editorial-workflow-github-slack.mdx b/apps/web/content/articles/building-editorial-workflow-github-slack.mdx index 67c0767cff..6298ea3b95 100644 --- a/apps/web/content/articles/building-editorial-workflow-github-slack.mdx +++ b/apps/web/content/articles/building-editorial-workflow-github-slack.mdx @@ -216,5 +216,5 @@ This editorial workflow is one piece of that vision. It's how we publish our blo 3. [Choosing a CMS in 2026](/blog/choosing-a-cms) 4. [Don't Use a CMS Until You Absolutely Need One](/blog/dont-use-a-cms) 5. [Docs Edition: What to Use for Developer Documentation](/blog/developer-documentation-tools) -6. [How We Built Char's Publishing Stack](/blog/hyprnote-publishing-stack) +6. [How We Built Char's Publishing Stack](/blog/char-publishing-stack) 7. [Building an Editorial Workflow with GitHub and Slack](/blog/building-editorial-workflow-github-slack) (You are here) \ No newline at end of file diff --git a/apps/web/content/articles/hyprnote-publishing-stack.mdx b/apps/web/content/articles/char-publishing-stack.mdx similarity index 98% rename from apps/web/content/articles/hyprnote-publishing-stack.mdx rename to apps/web/content/articles/char-publishing-stack.mdx index d433206e27..d959fc984d 100644 --- a/apps/web/content/articles/hyprnote-publishing-stack.mdx +++ b/apps/web/content/articles/char-publishing-stack.mdx @@ -110,5 +110,5 @@ Our publishing system isn't static. Every piece of content we write becomes more 3. [Choosing a CMS in 2026](/blog/choosing-a-cms) 4. [Don't Use a CMS Until You Absolutely Need One](/blog/dont-use-a-cms) 5. [Docs Edition: What to Use for Developer Documentation](/blog/developer-documentation-tools) -6. [How We Built Char's Publishing Stack](/blog/hyprnote-publishing-stack) (You are here) +6. [How We Built Char's Publishing Stack](/blog/char-publishing-stack) (You are here) 7. [Building an Editorial Workflow with GitHub and Slack](/blog/building-editorial-workflow-github-slack) \ No newline at end of file diff --git a/apps/web/content/articles/choosing-a-cms.mdx b/apps/web/content/articles/choosing-a-cms.mdx index 19f02a91f4..5b2afd30af 100644 --- a/apps/web/content/articles/choosing-a-cms.mdx +++ b/apps/web/content/articles/choosing-a-cms.mdx @@ -24,7 +24,7 @@ I won't tell you "X is the best CMS." Instead, I'll show you what each category 3. [Choosing a CMS in 2026](/blog/choosing-a-cms) (You are here) 4. [Don't Use a CMS Until You Absolutely Need One](/blog/dont-use-a-cms) 5. [Docs Edition: What to Use for Developer Documentation](/blog/developer-documentation-tools) -6. [How We Built Char's Publishing Stack](/blog/hyprnote-publishing-stack) +6. [How We Built Char's Publishing Stack](/blog/char-publishing-stack) 7. [Building an Editorial Workflow with GitHub and Slack](/blog/building-editorial-workflow-github-slack) ## Step 0: decide what problem you're actually solving @@ -257,5 +257,5 @@ The tools will change. Your content shouldn't have to. 3. [Choosing a CMS in 2026](/blog/choosing-a-cms) (You are here) 4. [Don't Use a CMS Until You Absolutely Need One](/blog/dont-use-a-cms) 5. [Docs Edition: What to Use for Developer Documentation](/blog/developer-documentation-tools) -6. [How We Built Char's Publishing Stack](/blog/hyprnote-publishing-stack) +6. [How We Built Char's Publishing Stack](/blog/char-publishing-stack) 7. [Building an Editorial Workflow with GitHub and Slack](/blog/building-editorial-workflow-github-slack) \ No newline at end of file diff --git a/apps/web/content/articles/developer-documentation-tools.mdx b/apps/web/content/articles/developer-documentation-tools.mdx index f75eece5d4..5cd39809ad 100644 --- a/apps/web/content/articles/developer-documentation-tools.mdx +++ b/apps/web/content/articles/developer-documentation-tools.mdx @@ -191,5 +191,5 @@ This is Part 5 of our publishing series. 3. [Choosing a CMS in 2026](/blog/choosing-a-cms) 4. [Don't Use a CMS Until You Absolutely Need One](/blog/dont-use-a-cms) 5. [Docs Edition: What to Use for Developer Documentation](/blog/developer-documentation-tools) (You are here) -6. [How We Built Char's Publishing Stack](/blog/hyprnote-publishing-stack) +6. [How We Built Char's Publishing Stack](/blog/char-publishing-stack) 7. [Building an Editorial Workflow with GitHub and Slack](/blog/building-editorial-workflow-github-slack) \ No newline at end of file diff --git a/apps/web/content/articles/dont-use-a-cms.mdx b/apps/web/content/articles/dont-use-a-cms.mdx index d613a332c1..cd4ac7feb3 100644 --- a/apps/web/content/articles/dont-use-a-cms.mdx +++ b/apps/web/content/articles/dont-use-a-cms.mdx @@ -97,5 +97,5 @@ Just write and ship in the simplest system that respects your time. You can add 3. [Choosing a CMS in 2026](/blog/choosing-a-cms) 4. [Don't Use a CMS Until You Absolutely Need One](/blog/dont-use-a-cms) (You are here) 5. [Docs Edition: What to Use for Developer Documentation](/blog/developer-documentation-tools) -6. [How We Built Char's Publishing Stack](/blog/hyprnote-publishing-stack) +6. [How We Built Char's Publishing Stack](/blog/char-publishing-stack) 7. [Building an Editorial Workflow with GitHub and Slack](/blog/building-editorial-workflow-github-slack) \ No newline at end of file diff --git a/apps/web/content/articles/fireflies-ai-alternatives.mdx b/apps/web/content/articles/fireflies-ai-alternatives.mdx index e34cf7c304..1059d51ece 100644 --- a/apps/web/content/articles/fireflies-ai-alternatives.mdx +++ b/apps/web/content/articles/fireflies-ai-alternatives.mdx @@ -79,7 +79,7 @@ Fireflies does what it promises for basic transcription and automation, especial Char is an open-source AI notepad for meetings built for high-agency people who demand complete control over their data and AI stack. Everything is stored as plain markdown files—not in proprietary databases. It works like Apple Notes with AI superpowers, capturing audio from any meeting platform without joining as a bot. -Char +Char #### How it compares to Fireflies diff --git a/apps/web/content/articles/hyprnote-is-now-char.mdx b/apps/web/content/articles/hyprnote-is-now-char.mdx index 81a57d9c2f..26cdd47757 100644 --- a/apps/web/content/articles/hyprnote-is-now-char.mdx +++ b/apps/web/content/articles/hyprnote-is-now-char.mdx @@ -1,7 +1,7 @@ --- -meta_title: "Hyprnote Is Now Char" -display_title: "Hyprnote Is Now Char" -meta_description: "From sales notes to AI notepad — why we renamed Hyprnote to Char, the story behind the name, and what it means for our users." +meta_title: "Char Is Now Char" +display_title: "Char Is Now Char" +meta_description: "From sales notes to AI notepad — why we renamed Char to Char, the story behind the name, and what it means for our users." author: "John Jeong" coverImage: "/api/assets/blog/announcement.jpg" featured: true @@ -9,11 +9,11 @@ category: "Founders' notes" date: "2026-02-14" --- -We're renaming Hyprnote to Char. If you've been following us for a while, this might come as a surprise. But the name change has been building for a long time. +We're renaming Char to Char. If you've been following us for a while, this might come as a surprise. But the name change has been building for a long time. ### We never liked the name -When we started, we were building a note-taker for salespeople, born from my own experience in sales. Yujong, my co-founder, came up with "Hypernote", but when I searched for domains, nothing close was available with a .com. So I dropped a letter and went with Hyprnote. +When we started, we were building a note-taker for salespeople, born from my own experience in sales. Yujong, my co-founder, came up with "Hypernote", but when I searched for domains, nothing close was available with a .com. So I dropped a letter and went with Char. I was never happy with it. People constantly called us "Hypernote". Every email. Every calendar invite: "Acme {'<>'} Hypernote sync". It was a recurring frustration, but we shipped and moved on. @@ -35,7 +35,7 @@ Control over which AI models process their data. Control over their notes as act Privacy mattered, but it was never the full picture. What we'd really built was ownership. Total ownership. -The product had evolved. We knew exactly who we were building for. But we were still calling it Hyprnote, a name we'd grabbed in a hurry just to ship. +The product had evolved. We knew exactly who we were building for. But we were still calling it Char, a name we'd grabbed in a hurry just to ship. That needed to change. diff --git a/apps/web/content/articles/krisp-alternatives.mdx b/apps/web/content/articles/krisp-alternatives.mdx index aa74085e4a..1fcad5cfc5 100644 --- a/apps/web/content/articles/krisp-alternatives.mdx +++ b/apps/web/content/articles/krisp-alternatives.mdx @@ -33,7 +33,7 @@ That's a lot of product for people who only need one piece of it. Some just want ### 1. Char -![Char (formerly Hyprnote)](/api/assets/blog/blog/char-summary.png "char-editor-width=80") +![Char (formerly Char)](/api/assets/blog/blog/char-summary.png "char-editor-width=80") [Char](char.com) is open-source and built on a different set of assumptions than everything else on this list. It captures system audio without joining your call and without requiring calendar permissions, then stores everything on your device. Not in a proprietary database. Not on someone else's server. diff --git a/apps/web/content/articles/mac-productivity-apps.mdx b/apps/web/content/articles/mac-productivity-apps.mdx index 0f4117c7d5..ab84b97aba 100644 --- a/apps/web/content/articles/mac-productivity-apps.mdx +++ b/apps/web/content/articles/mac-productivity-apps.mdx @@ -43,7 +43,7 @@ If you're a Mac user looking to actually get more done (not just feel productive ## Best Mac productivity apps in 2026 -### 1. Char (formerly Hyprnote): best Mac app for meeting notes +### 1. Char (formerly Char): best Mac app for meeting notes ![Char review](/api/assets/blog/articles/mac-productivity-apps/image-1.png) diff --git a/apps/web/content/articles/meeting-productivity-tools.mdx b/apps/web/content/articles/meeting-productivity-tools.mdx index 4c2c5863c7..e7f7c9ab54 100644 --- a/apps/web/content/articles/meeting-productivity-tools.mdx +++ b/apps/web/content/articles/meeting-productivity-tools.mdx @@ -31,7 +31,7 @@ Let's get into it. Most AI meeting tools make the same trade: convenience in exchange for your data living in someone else's database, locked into their format, processed by their AI. Char doesn't make that trade. -[Char](https://char.com) (formerly Hyprnote) is an open-source AI notepad for meetings, built for people who want complete control over their files, their AI stack, and what happens to their data.  +[Char](https://char.com) (formerly Char) is an open-source AI notepad for meetings, built for people who want complete control over their files, their AI stack, and what happens to their data.  Every meeting is saved as a plain .md file on your device. You choose which AI processes it. Nothing is locked behind Char's ecosystem. diff --git a/apps/web/content/articles/open-source-meeting-transcription-software.mdx b/apps/web/content/articles/open-source-meeting-transcription-software.mdx index 529b7d9b12..a24cbba1f4 100644 --- a/apps/web/content/articles/open-source-meeting-transcription-software.mdx +++ b/apps/web/content/articles/open-source-meeting-transcription-software.mdx @@ -43,7 +43,7 @@ While you're writing, Char listens to both your microphone and system audio. Onc **Available as:** Native Mac app (desktop application, Windows and Linux coming in Q2 2026) -![Char](/api/assets/blog/open-source-meeting-transcription-software/hyprnote.webp) +![Char](/api/assets/blog/open-source-meeting-transcription-software/char.webp) #### **Top Features** diff --git a/apps/web/content/articles/plaud-ai-alternatives.mdx b/apps/web/content/articles/plaud-ai-alternatives.mdx index 87a5b028aa..7ec0f57ae8 100644 --- a/apps/web/content/articles/plaud-ai-alternatives.mdx +++ b/apps/web/content/articles/plaud-ai-alternatives.mdx @@ -54,7 +54,7 @@ Continue reading for detailed reviews of each of these tools. You can inspect the code, customize functionality, and choose which AI processes your data. Zero lock-in, zero compromises. -![Char](/api/assets/blog/plaud-ai-alternatives/hyprnote.webp) +![Char](/api/assets/blog/plaud-ai-alternatives/char.webp) #### How it works diff --git a/apps/web/content/articles/using-ide-for-writing.mdx b/apps/web/content/articles/using-ide-for-writing.mdx index 4ef1ac7e2c..a52676c64b 100644 --- a/apps/web/content/articles/using-ide-for-writing.mdx +++ b/apps/web/content/articles/using-ide-for-writing.mdx @@ -79,5 +79,5 @@ Once you experience that kind of writing, it's hard to go back. 3. [Choosing a CMS in 2026](/blog/choosing-a-cms) 4. [Don't Use a CMS Until You Absolutely Need One](/blog/dont-use-a-cms) 5. [Docs Edition: What to Use for Developer Documentation](/blog/developer-documentation-tools) -6. [How We Built Char's Publishing Stack](/blog/hyprnote-publishing-stack) +6. [How We Built Char's Publishing Stack](/blog/char-publishing-stack) 7. [Building an Editorial Workflow with GitHub and Slack](/blog/building-editorial-workflow-github-slack) \ No newline at end of file diff --git a/apps/web/content/articles/why-our-cms-is-github.mdx b/apps/web/content/articles/why-our-cms-is-github.mdx index 76a3aaf463..7f870d572b 100644 --- a/apps/web/content/articles/why-our-cms-is-github.mdx +++ b/apps/web/content/articles/why-our-cms-is-github.mdx @@ -74,5 +74,5 @@ This is Part 2 of our publishing series. 3. [Choosing a CMS in 2026](/blog/choosing-a-cms) 4. [Don't Use a CMS Until You Absolutely Need One](/blog/dont-use-a-cms) 5. [Docs Edition: What to Use for Developer Documentation](/blog/developer-documentation-tools) -6. [How We Built Char's Publishing Stack](/blog/hyprnote-publishing-stack) +6. [How We Built Char's Publishing Stack](/blog/char-publishing-stack) 7. [Building an Editorial Workflow with GitHub and Slack](/blog/building-editorial-workflow-github-slack) \ No newline at end of file diff --git a/apps/web/content/docs/about/1.what-is-hyprnote.mdx b/apps/web/content/docs/about/1.what-is-char.mdx similarity index 92% rename from apps/web/content/docs/about/1.what-is-hyprnote.mdx rename to apps/web/content/docs/about/1.what-is-char.mdx index 25efc02e29..3f8cef9491 100644 --- a/apps/web/content/docs/about/1.what-is-hyprnote.mdx +++ b/apps/web/content/docs/about/1.what-is-char.mdx @@ -16,7 +16,7 @@ Key features: Char Concept Left Char Concept Right + For example, when transcribing Korean + English, Soniox is selected over Deepgram because it has better multilingual support — even though Deepgram has higher base priority. The router sorts by language quality first, then falls back to priority order: - + ### How audio flows -![Audio Flow](https://agents.craft.do/mermaid?code=flowchart%20LR%0A%20%20%20%20A%5BYour%20Device%5D%20--%3E%7CWebSocket%7C%20B%5BChar%20API%20Server%3Cbr%2F%3Epro.hyprnote.com%5D%0A%20%20%20%20B%20--%3E%7CWebSocket%7C%20C%5BSTT%20Provider%3Cbr%2F%3Ee.g.%2C%20Deepgram%2C%20Soniox%5D) +![Audio Flow](https://agents.craft.do/mermaid?code=flowchart%20LR%0A%20%20%20%20A%5BYour%20Device%5D%20--%3E%7CWebSocket%7C%20B%5BChar%20API%20Server%3Cbr%2F%3Epro.char.com%5D%0A%20%20%20%20B%20--%3E%7CWebSocket%7C%20C%5BSTT%20Provider%3Cbr%2F%3Ee.g.%2C%20Deepgram%2C%20Soniox%5D) 1. **Your device** opens a WebSocket to the Char API server, authenticated with your Supabase JWT token. 2. **Char API server** validates your Pro subscription, selects a provider based on your language, and opens a WebSocket to that provider. @@ -73,7 +73,7 @@ Char checks if your selected provider supports your configured languages. If the When using cloud transcription, your recorded audio is sent to the selected provider for processing: -- **Pro curated models:** Your audio is proxied through `pro.hyprnote.com` and forwarded to a curated STT provider. The proxy does not store your audio. +- **Pro curated models:** Your audio is proxied through `pro.char.com` and forwarded to a curated STT provider. The proxy does not store your audio. - **BYOK:** Your audio is sent directly from your device to the provider you selected. Char acts only as the client. Here is how Char selects the correct adapter for your configured provider — each provider has its own adapter that handles the audio stream: diff --git a/apps/web/content/docs/pro/2.cloud.mdx b/apps/web/content/docs/pro/2.cloud.mdx index 42c009c4e8..721128b5f9 100644 --- a/apps/web/content/docs/pro/2.cloud.mdx +++ b/apps/web/content/docs/pro/2.cloud.mdx @@ -62,7 +62,7 @@ And here are the two model pools defined in the server config: ### How the request flows -![Request Flow](https://agents.craft.do/mermaid?code=flowchart%20LR%0A%20%20%20%20A%5BYour%20Device%5D%20--%3E%7CHTTPS%7C%20B%5BChar%20API%20Server%3Cbr%3Epro.hyprnote.com%5D%0A%20%20%20%20B%20--%3E%7CHTTPS%7C%20C%5BOpenRouter%3Cbr%3Eopenrouter.ai%5D%0A%20%20%20%20C%20--%3E%20D%5BModel%20Provider%3Cbr%3EOpenAI%2FAnthropic%2FMoonshot%5D) +![Request Flow](https://agents.craft.do/mermaid?code=flowchart%20LR%0A%20%20%20%20A%5BYour%20Device%5D%20--%3E%7CHTTPS%7C%20B%5BChar%20API%20Server%3Cbr%3Epro.char.com%5D%0A%20%20%20%20B%20--%3E%7CHTTPS%7C%20C%5BOpenRouter%3Cbr%3Eopenrouter.ai%5D%0A%20%20%20%20C%20--%3E%20D%5BModel%20Provider%3Cbr%3EOpenAI%2FAnthropic%2FMoonshot%5D) 1. **Your device** sends a chat completion request to the Char API server, authenticated with your Supabase JWT token. 2. **Char API server** validates your Pro subscription, then forwards the request to [OpenRouter](https://openrouter.ai). @@ -111,7 +111,7 @@ While Char aims to be fully transparent and controllable, cloud services help in ## Privacy & security -The cloud server (`pro.hyprnote.com`) is [open-source](https://github.com/fastrepl/hyprnote/tree/main/apps/pro) and deployed in our Kubernetes cluster on AWS via GitHub Actions. +The cloud server (`pro.char.com`) is [open-source](https://github.com/fastrepl/char/tree/main/apps/pro) and deployed in our Kubernetes cluster on AWS via GitHub Actions. **Data handling:** - Nothing is stored by us — the server proxies requests and discards them diff --git a/apps/web/content/handbook/onboarding/2.staging-builds.mdx b/apps/web/content/handbook/onboarding/2.staging-builds.mdx index 46eb61c0c7..f58ac594ce 100644 --- a/apps/web/content/handbook/onboarding/2.staging-builds.mdx +++ b/apps/web/content/handbook/onboarding/2.staging-builds.mdx @@ -16,9 +16,9 @@ Staging builds are created when testing new features before they go to nightly o 4. Click "Run workflow" 5. Once complete, download the artifact from the workflow run -Staging builds are stored in R2 with filenames like `hyprnote-staging-{timestamp}-{commit}-macos-aarch64.dmg`. +Staging builds are stored in R2 with filenames like `char-staging-{timestamp}-{commit}-macos-aarch64.dmg`. -**Alternative:** Download directly from [desktop_staging workflow](https://github.com/fastrepl/char/actions/workflows/desktop_staging.yaml) artifacts — look for `hyprnote-staging-macos-silicon`. +**Alternative:** Download directly from [desktop_staging workflow](https://github.com/fastrepl/char/actions/workflows/desktop_staging.yaml) artifacts — look for `char-staging-macos-silicon`. ## Triggering staging builds diff --git a/apps/web/content/handbook/onboarding/5.cursor.mdx b/apps/web/content/handbook/onboarding/5.cursor.mdx index d22c82bc88..625fdea609 100644 --- a/apps/web/content/handbook/onboarding/5.cursor.mdx +++ b/apps/web/content/handbook/onboarding/5.cursor.mdx @@ -28,7 +28,7 @@ Or download directly from [cursor.com](https://cursor.com). 1. Open Cursor 2. Sign in with your account (free tier works, Pro is better) 3. When prompted to import VS Code settings, do it if you have existing VS Code config — otherwise skip -4. Open the Char repo: `File > Open Folder` and select your `hyprnote` directory +4. Open the Char repo: `File > Open Folder` and select your `char` directory ## Key features to know diff --git a/apps/web/content/handbook/onboarding/6.gitbutler.mdx b/apps/web/content/handbook/onboarding/6.gitbutler.mdx index 817d8cc18b..b3fcc60020 100644 --- a/apps/web/content/handbook/onboarding/6.gitbutler.mdx +++ b/apps/web/content/handbook/onboarding/6.gitbutler.mdx @@ -31,7 +31,7 @@ brew install --cask gitbutler 1. Open GitButler 2. Sign in with your GitHub account -3. Add the Char repo: click "Add Repository" and select your `hyprnote` folder +3. Add the Char repo: click "Add Repository" and select your `char` folder 4. GitButler will detect the existing git state and show your workspace ## Key concepts diff --git a/apps/web/content/handbook/onboarding/7.claude-code.mdx b/apps/web/content/handbook/onboarding/7.claude-code.mdx index 272f3abc67..74d0a9ffed 100644 --- a/apps/web/content/handbook/onboarding/7.claude-code.mdx +++ b/apps/web/content/handbook/onboarding/7.claude-code.mdx @@ -25,7 +25,7 @@ npm install -g @anthropic-ai/claude-code ## First launch 1. Open your terminal -2. Navigate to the Char repo: `cd ~/Dev/hyprnote` (or wherever you cloned it) +2. Navigate to the Char repo: `cd ~/Dev/char` (or wherever you cloned it) 3. Run: `claude` 4. On first launch, it will ask you to authenticate with your Anthropic account diff --git a/apps/web/content/handbook/onboarding/8.warp.mdx b/apps/web/content/handbook/onboarding/8.warp.mdx index 0bd58f5ccf..acbb22a50d 100644 --- a/apps/web/content/handbook/onboarding/8.warp.mdx +++ b/apps/web/content/handbook/onboarding/8.warp.mdx @@ -50,7 +50,7 @@ If you don't have Homebrew yet, install Warp from the website first, then use Wa If you're new to the terminal, here are the commands you'll use most: ```bash -cd # Go to a folder (e.g., cd ~/Dev/hyprnote) +cd # Go to a folder (e.g., cd ~/Dev/char) ls # List files in the current folder pwd # Show where you are ``` diff --git a/apps/web/content/handbook/onboarding/9.node-and-pnpm.mdx b/apps/web/content/handbook/onboarding/9.node-and-pnpm.mdx index 4b43854ec5..932b8cb11f 100644 --- a/apps/web/content/handbook/onboarding/9.node-and-pnpm.mdx +++ b/apps/web/content/handbook/onboarding/9.node-and-pnpm.mdx @@ -49,7 +49,7 @@ You should see `9.x.x` or higher. Once both are installed, navigate to the Char repo and install all project dependencies: ```bash -cd ~/Dev/hyprnote +cd ~/Dev/char pnpm install ``` diff --git a/apps/web/content/hooks/afterListeningStopped.mdx b/apps/web/content/hooks/afterListeningStopped.mdx index cfabff3259..39c076059a 100644 --- a/apps/web/content/hooks/afterListeningStopped.mdx +++ b/apps/web/content/hooks/afterListeningStopped.mdx @@ -5,7 +5,7 @@ args: - name: "--resource-dir" description: null type_name: "string" - - name: "--app-hyprnote" + - name: "--app-char" description: null type_name: "string" - name: "--app-meeting" diff --git a/apps/web/content/hooks/beforeListeningStarted.mdx b/apps/web/content/hooks/beforeListeningStarted.mdx index 0138e72468..ca14146e9c 100644 --- a/apps/web/content/hooks/beforeListeningStarted.mdx +++ b/apps/web/content/hooks/beforeListeningStarted.mdx @@ -5,7 +5,7 @@ args: - name: "--resource-dir" description: null type_name: "string" - - name: "--app-hyprnote" + - name: "--app-char" description: null type_name: "string" - name: "--app-meeting" diff --git a/apps/web/content/updates/2026-02-22.mdx b/apps/web/content/updates/2026-02-22.mdx index 2e0d0fd552..090d994758 100644 --- a/apps/web/content/updates/2026-02-22.mdx +++ b/apps/web/content/updates/2026-02-22.mdx @@ -21,7 +21,7 @@ Calendar got a visual cleanup, onboarding now shows a proper loading state while ## Smaller improvements -Contact avatars now fall back to colored initials, telemetry opt-out now properly disables analytics, and the remaining Hyprnote references in the UI were updated to Char. +Contact avatars now fall back to colored initials, telemetry opt-out now properly disables analytics, and the remaining Char references in the UI were updated to Char. ## What's next diff --git a/apps/web/content/updates/2026-03-15.mdx b/apps/web/content/updates/2026-03-15.mdx index a4ff23abcd..e02b0b7f2c 100644 --- a/apps/web/content/updates/2026-03-15.mdx +++ b/apps/web/content/updates/2026-03-15.mdx @@ -11,9 +11,9 @@ You can now choose a custom location for your Char content in Settings, and dete Folder moves and renames are also more reliable now because filesystem changes are applied before app state updates. The Today timeline's current-time marker is more accurate as well, especially during ongoing events and in your selected timezone. -## Hyprnote is now Char +## Char is now Char -This release also carries the Hyprnote-to-Char rename through the stable app. Existing users can update without changing how they work, and the app now consistently presents itself as Char. +This release also carries the Char-to-Char rename through the stable app. Existing users can update without changing how they work, and the app now consistently presents itself as Char. ## Calendar and notes diff --git a/apps/web/netlify.toml b/apps/web/netlify.toml index d764ac921f..66a4f6913e 100644 --- a/apps/web/netlify.toml +++ b/apps/web/netlify.toml @@ -13,8 +13,8 @@ NODE_VERSION = "22" [images] # https://docs.netlify.com/build/image-cdn/overview/#remote-path remote_images = [ - "https://char\\.com/.*", "https://hyprnote\\.com/.*", + "https://char\\.com/.*", "https://ijoptyyjrfqwaqhyxkxj\\.supabase\\.co/.*", ] diff --git a/apps/web/netlify/edge-functions/og.tsx b/apps/web/netlify/edge-functions/og.tsx index ffc28bdd1f..d7855a3001 100644 --- a/apps/web/netlify/edge-functions/og.tsx +++ b/apps/web/netlify/edge-functions/og.tsx @@ -302,7 +302,7 @@ function renderChangelogTemplate(params: z.infer) { bottom: -69, position: "absolute", }} - src="https://hyprnote.com/api/assets/icons/nightly-icon.png" + src="https://char.com/api/assets/icons/nightly-icon.png" /> ); @@ -400,7 +400,7 @@ function renderChangelogTemplate(params: z.infer) { bottom: -69, position: "absolute", }} - src="https://hyprnote.com/api/assets/icons/stable-icon.png" + src="https://char.com/api/assets/icons/stable-icon.png" /> ); @@ -408,15 +408,15 @@ function renderChangelogTemplate(params: z.infer) { // Keep in sync with apps/web/src/lib/team.ts const AUTHOR_AVATARS: Record = { - "John Jeong": "https://hyprnote.com/api/assets/team/john.png", - "Yujong Lee": "https://hyprnote.com/api/assets/team/yujong.png", - Harshika: "https://hyprnote.com/api/assets/team/harshika.jpeg", + "John Jeong": "https://char.com/api/assets/team/john.png", + "Yujong Lee": "https://char.com/api/assets/team/yujong.png", + Harshika: "https://char.com/api/assets/team/harshika.jpeg", }; function getAuthorAvatar(author: string): string { return ( AUTHOR_AVATARS[author] || - "https://hyprnote.com/api/assets/icons/stable-icon.png" + "https://char.com/api/assets/icons/stable-icon.png" ); } @@ -502,7 +502,7 @@ function renderBlogTemplate(params: z.infer) {
  • Support diff --git a/apps/web/src/components/header.tsx b/apps/web/src/components/header.tsx index 89e3cbc1a5..1d7f0632e9 100644 --- a/apps/web/src/components/header.tsx +++ b/apps/web/src/components/header.tsx @@ -72,7 +72,7 @@ const resourcesList: { { to: "/changelog/", label: "Changelog", icon: History }, { to: "/company-handbook/", label: "Company Handbook", icon: Building2 }, { - to: "https://discord.gg/hyprnote", + to: "https://discord.gg/char", label: "Community", icon: MessageCircle, external: true, diff --git a/apps/web/src/components/mdx/link.tsx b/apps/web/src/components/mdx/link.tsx index d78a820828..d334cb9185 100644 --- a/apps/web/src/components/mdx/link.tsx +++ b/apps/web/src/components/mdx/link.tsx @@ -15,12 +15,12 @@ export function MDXLink({ return {children}; } - const isHyprnoteUrl = href.startsWith("https://hyprnote.com"); + const isCharUrl = href.startsWith("https://char.com"); const isInternalPath = href.startsWith("/") || href.startsWith("."); const isAnchor = href.startsWith("#"); - if (isHyprnoteUrl) { - const relativePath = href.replace("https://hyprnote.com", "") || "/"; + if (isCharUrl) { + const relativePath = href.replace("https://char.com", "") || "/"; return ( = { - yujonglee: { name: "Yujong Lee", email: "yujonglee@hyprnote.com" }, - ComputelessComputer: { name: "John Jeong", email: "john@hyprnote.com" }, + yujonglee: { name: "Yujong Lee", email: "yujonglee@char.com" }, + ComputelessComputer: { name: "John Jeong", email: "john@char.com" }, }; export interface GitHubCredentials { diff --git a/apps/web/src/functions/github-stars.ts b/apps/web/src/functions/github-stars.ts index 5bffd91427..814a3d1046 100644 --- a/apps/web/src/functions/github-stars.ts +++ b/apps/web/src/functions/github-stars.ts @@ -14,7 +14,7 @@ function getSql() { function getGitHubHeaders(accept?: string): Record { const headers: Record = { - "User-Agent": "hyprnote-admin", + "User-Agent": "char-admin", Accept: accept || "application/vnd.github.v3+json", }; if (env.GITHUB_TOKEN) { diff --git a/apps/web/src/lib/team.ts b/apps/web/src/lib/team.ts index 49853bc6ef..68013f69f0 100644 --- a/apps/web/src/lib/team.ts +++ b/apps/web/src/lib/team.ts @@ -2,7 +2,7 @@ export const TEAM_MEMBERS = { john: { id: "john", name: "John Jeong", - email: "john@hyprnote.com", + email: "john@char.com", avatar: "/api/assets/team/john.png", role: "Chief Wisdom Seeker", bio: "I love designing simple and intuitive user interfaces.", @@ -15,7 +15,7 @@ export const TEAM_MEMBERS = { yujong: { id: "yujong", name: "Yujong Lee", - email: "yujonglee@hyprnote.com", + email: "yujonglee@char.com", avatar: "/api/assets/team/yujong.png", role: "Chief OSS Lover", bio: "I am super bullish about open-source software.", @@ -48,14 +48,14 @@ export const AUTHORS = Object.values(TEAM_MEMBERS).map((m) => ({ })); export const ADMIN_EMAILS = [ - "yujonglee@hyprnote.com", + "yujonglee@char.com", "yujonglee.dev@gmail.com", - "john@hyprnote.com", - "marketing@hyprnote.com", + "john@char.com", + "marketing@char.com", "harshika.alagh@gmail.com", "yunhyungjo@yonsei.ac.kr", "goranmoomin@daum.net", - "artem@hyprnote.com", + "artem@char.com", "stua@fastmail.com", "thestua@gmail.com", ]; diff --git a/apps/web/src/router.tsx b/apps/web/src/router.tsx index f6d29faa6c..abea68d032 100644 --- a/apps/web/src/router.tsx +++ b/apps/web/src/router.tsx @@ -141,7 +141,7 @@ export function getRouter() { Sentry.init({ dsn: env.VITE_SENTRY_DSN, release: env.VITE_APP_VERSION - ? `hyprnote-web@${env.VITE_APP_VERSION}` + ? `char-web@${env.VITE_APP_VERSION}` : undefined, sendDefaultPii: true, tracePropagationTargets: [], diff --git a/apps/web/src/routes/_view/app/integration.tsx b/apps/web/src/routes/_view/app/integration.tsx index 804c72aa74..d5560eb553 100644 --- a/apps/web/src/routes/_view/app/integration.tsx +++ b/apps/web/src/routes/_view/app/integration.tsx @@ -78,7 +78,7 @@ function Component() { ); } diff --git a/apps/web/src/routes/_view/brand.tsx b/apps/web/src/routes/_view/brand.tsx index 053ec71e1a..1ddf430e70 100644 --- a/apps/web/src/routes/_view/brand.tsx +++ b/apps/web/src/routes/_view/brand.tsx @@ -47,25 +47,25 @@ const VISUAL_ASSETS = [ { id: "icon", name: "Icon", - url: "/api/assets/hyprnote/icon.png", + url: "/api/assets/char/icon.png", description: "Char app icon", }, { id: "logo", name: "Logo", - url: "/api/assets/hyprnote/logo.png", + url: "/api/assets/char/logo.png", description: "Char wordmark logo", }, { id: "symbol-logo", name: "Symbol + Logo", - url: "/api/assets/hyprnote/symbol+logo.png", + url: "/api/assets/char/symbol+logo.png", description: "Char icon with wordmark", }, { id: "og-image", name: "OpenGraph Image", - url: "/api/assets/hyprnote/og-image.jpg", + url: "/api/assets/char/og-image.jpg", description: "Social media preview image", }, ]; diff --git a/apps/web/src/routes/_view/callback/auth.tsx b/apps/web/src/routes/_view/callback/auth.tsx index 778a93c9c2..8c2567ba70 100644 --- a/apps/web/src/routes/_view/callback/auth.tsx +++ b/apps/web/src/routes/_view/callback/auth.tsx @@ -25,7 +25,7 @@ const validateSearch = z.object({ ]) .optional(), flow: z.enum(["desktop", "web"]).default("desktop"), - scheme: desktopSchemeSchema.catch("hyprnote"), + scheme: desktopSchemeSchema.catch("char"), redirect: z.string().optional(), access_token: z.string().optional(), refresh_token: z.string().optional(), diff --git a/apps/web/src/routes/_view/callback/billing.tsx b/apps/web/src/routes/_view/callback/billing.tsx index da1d50931e..781b4aea5d 100644 --- a/apps/web/src/routes/_view/callback/billing.tsx +++ b/apps/web/src/routes/_view/callback/billing.tsx @@ -25,7 +25,7 @@ export const Route = createFileRoute("/_view/callback/billing")({ }); function Component() { - const { scheme = "hyprnote" } = Route.useSearch(); + const { scheme = "char" } = Route.useSearch(); const [copied, setCopied] = useState(false); const deeplink = `${scheme}://billing/refresh`; diff --git a/apps/web/src/routes/_view/callback/integration.tsx b/apps/web/src/routes/_view/callback/integration.tsx index 35c1f6e6ef..4a34d2b6e3 100644 --- a/apps/web/src/routes/_view/callback/integration.tsx +++ b/apps/web/src/routes/_view/callback/integration.tsx @@ -48,7 +48,7 @@ function buildDeeplinkUrl( function Component() { const search = Route.useSearch(); - const scheme = search.scheme ?? "hyprnote"; + const scheme = search.scheme ?? "char"; const navigate = useNavigate(); const queryClient = useQueryClient(); const [copied, setCopied] = useState(false); diff --git a/apps/web/src/routes/_view/download/apple-intel.tsx b/apps/web/src/routes/_view/download/apple-intel.tsx index e0e35e8e8f..3e2bfee820 100644 --- a/apps/web/src/routes/_view/download/apple-intel.tsx +++ b/apps/web/src/routes/_view/download/apple-intel.tsx @@ -3,7 +3,7 @@ import { createFileRoute, redirect } from "@tanstack/react-router"; export const Route = createFileRoute("/_view/download/apple-intel")({ beforeLoad: async () => { throw redirect({ - href: "https://desktop2.hyprnote.com/download/latest/dmg-x86_64?channel=stable", + href: "https://desktop2.char.com/download/latest/dmg-x86_64?channel=stable", } as any); }, }); diff --git a/apps/web/src/routes/_view/download/apple-silicon.tsx b/apps/web/src/routes/_view/download/apple-silicon.tsx index 04919170d5..df0a35ae23 100644 --- a/apps/web/src/routes/_view/download/apple-silicon.tsx +++ b/apps/web/src/routes/_view/download/apple-silicon.tsx @@ -3,7 +3,7 @@ import { createFileRoute, redirect } from "@tanstack/react-router"; export const Route = createFileRoute("/_view/download/apple-silicon")({ beforeLoad: async () => { throw redirect({ - href: "https://desktop2.hyprnote.com/download/latest/dmg-aarch64?channel=stable", + href: "https://desktop2.char.com/download/latest/dmg-aarch64?channel=stable", } as any); }, }); diff --git a/apps/web/src/routes/_view/download/index.tsx b/apps/web/src/routes/_view/download/index.tsx index d812b69284..d1a18339e3 100644 --- a/apps/web/src/routes/_view/download/index.tsx +++ b/apps/web/src/routes/_view/download/index.tsx @@ -214,7 +214,7 @@ function CTASection() {
    Char { throw redirect({ - href: "https://desktop2.hyprnote.com/download/latest/appimage-x86_64?channel=nightly", + href: "https://desktop2.char.com/download/latest/appimage-x86_64?channel=nightly", } as any); }, }); diff --git a/apps/web/src/routes/_view/download/linux-deb.tsx b/apps/web/src/routes/_view/download/linux-deb.tsx index ef8d2ca0d1..9a082a249c 100644 --- a/apps/web/src/routes/_view/download/linux-deb.tsx +++ b/apps/web/src/routes/_view/download/linux-deb.tsx @@ -3,7 +3,7 @@ import { createFileRoute, redirect } from "@tanstack/react-router"; export const Route = createFileRoute("/_view/download/linux-deb")({ beforeLoad: async () => { throw redirect({ - href: "https://desktop2.hyprnote.com/download/latest/debian-x86_64?channel=nightly", + href: "https://desktop2.char.com/download/latest/debian-x86_64?channel=nightly", } as any); }, }); diff --git a/apps/web/src/routes/_view/download/nightly/apple-intel.tsx b/apps/web/src/routes/_view/download/nightly/apple-intel.tsx index 62feada086..fbaa7831b5 100644 --- a/apps/web/src/routes/_view/download/nightly/apple-intel.tsx +++ b/apps/web/src/routes/_view/download/nightly/apple-intel.tsx @@ -3,7 +3,7 @@ import { createFileRoute, redirect } from "@tanstack/react-router"; export const Route = createFileRoute("/_view/download/nightly/apple-intel")({ beforeLoad: async () => { throw redirect({ - href: "https://desktop2.hyprnote.com/download/latest/dmg-x86_64?channel=nightly", + href: "https://desktop2.char.com/download/latest/dmg-x86_64?channel=nightly", } as any); }, }); diff --git a/apps/web/src/routes/_view/download/nightly/apple-silicon.tsx b/apps/web/src/routes/_view/download/nightly/apple-silicon.tsx index 853907db74..7d6365f531 100644 --- a/apps/web/src/routes/_view/download/nightly/apple-silicon.tsx +++ b/apps/web/src/routes/_view/download/nightly/apple-silicon.tsx @@ -3,7 +3,7 @@ import { createFileRoute, redirect } from "@tanstack/react-router"; export const Route = createFileRoute("/_view/download/nightly/apple-silicon")({ beforeLoad: async () => { throw redirect({ - href: "https://desktop2.hyprnote.com/download/latest/dmg-aarch64?channel=nightly", + href: "https://desktop2.char.com/download/latest/dmg-aarch64?channel=nightly", } as any); }, }); diff --git a/apps/web/src/routes/_view/download/nightly/linux-appimage-aarch64.tsx b/apps/web/src/routes/_view/download/nightly/linux-appimage-aarch64.tsx index 848045fd69..03a85ff03c 100644 --- a/apps/web/src/routes/_view/download/nightly/linux-appimage-aarch64.tsx +++ b/apps/web/src/routes/_view/download/nightly/linux-appimage-aarch64.tsx @@ -5,7 +5,7 @@ export const Route = createFileRoute( )({ beforeLoad: async () => { throw redirect({ - href: "https://desktop2.hyprnote.com/download/latest/appimage-aarch64?channel=nightly", + href: "https://desktop2.char.com/download/latest/appimage-aarch64?channel=nightly", } as any); }, }); diff --git a/apps/web/src/routes/_view/download/nightly/linux-appimage.tsx b/apps/web/src/routes/_view/download/nightly/linux-appimage.tsx index e94f09da0e..979f94ded3 100644 --- a/apps/web/src/routes/_view/download/nightly/linux-appimage.tsx +++ b/apps/web/src/routes/_view/download/nightly/linux-appimage.tsx @@ -3,7 +3,7 @@ import { createFileRoute, redirect } from "@tanstack/react-router"; export const Route = createFileRoute("/_view/download/nightly/linux-appimage")({ beforeLoad: async () => { throw redirect({ - href: "https://desktop2.hyprnote.com/download/latest/appimage-x86_64?channel=nightly", + href: "https://desktop2.char.com/download/latest/appimage-x86_64?channel=nightly", } as any); }, }); diff --git a/apps/web/src/routes/_view/download/nightly/linux-deb-aarch64.tsx b/apps/web/src/routes/_view/download/nightly/linux-deb-aarch64.tsx index 5113346d63..db9d84db7d 100644 --- a/apps/web/src/routes/_view/download/nightly/linux-deb-aarch64.tsx +++ b/apps/web/src/routes/_view/download/nightly/linux-deb-aarch64.tsx @@ -5,7 +5,7 @@ export const Route = createFileRoute( )({ beforeLoad: async () => { throw redirect({ - href: "https://desktop2.hyprnote.com/download/latest/debian-aarch64?channel=nightly", + href: "https://desktop2.char.com/download/latest/debian-aarch64?channel=nightly", } as any); }, }); diff --git a/apps/web/src/routes/_view/download/nightly/linux-deb.tsx b/apps/web/src/routes/_view/download/nightly/linux-deb.tsx index ca623d512e..72039ad539 100644 --- a/apps/web/src/routes/_view/download/nightly/linux-deb.tsx +++ b/apps/web/src/routes/_view/download/nightly/linux-deb.tsx @@ -3,7 +3,7 @@ import { createFileRoute, redirect } from "@tanstack/react-router"; export const Route = createFileRoute("/_view/download/nightly/linux-deb")({ beforeLoad: async () => { throw redirect({ - href: "https://desktop2.hyprnote.com/download/latest/debian-x86_64?channel=nightly", + href: "https://desktop2.char.com/download/latest/debian-x86_64?channel=nightly", } as any); }, }); diff --git a/apps/web/src/routes/_view/download/nightly/windows.tsx b/apps/web/src/routes/_view/download/nightly/windows.tsx index 3b58aac942..e8eba60d4b 100644 --- a/apps/web/src/routes/_view/download/nightly/windows.tsx +++ b/apps/web/src/routes/_view/download/nightly/windows.tsx @@ -3,7 +3,7 @@ import { createFileRoute, redirect } from "@tanstack/react-router"; export const Route = createFileRoute("/_view/download/nightly/windows")({ beforeLoad: async () => { throw redirect({ - href: "https://desktop2.hyprnote.com/download/latest/msi?channel=nightly", + href: "https://desktop2.char.com/download/latest/msi?channel=nightly", } as any); }, }); diff --git a/apps/web/src/routes/_view/download/windows.tsx b/apps/web/src/routes/_view/download/windows.tsx index ac03cc6e16..d542f97f1c 100644 --- a/apps/web/src/routes/_view/download/windows.tsx +++ b/apps/web/src/routes/_view/download/windows.tsx @@ -4,7 +4,7 @@ export const Route = createFileRoute("/_view/download/windows")({ beforeLoad: async () => { throw redirect({ // TODO: needs to be fixed - href: "https://desktop2.hyprnote.com/download/latest/msi?channel=stable", + href: "https://desktop2.char.com/download/latest/msi?channel=stable", } as any); }, }); diff --git a/apps/web/src/routes/_view/free.tsx b/apps/web/src/routes/_view/free.tsx index 76507884aa..d9319e4d3e 100644 --- a/apps/web/src/routes/_view/free.tsx +++ b/apps/web/src/routes/_view/free.tsx @@ -68,16 +68,16 @@ const freeFeatures = [ ]; const comparisonFeatures = [ - { feature: "Meeting recording", hyprnote: true, others: "Limited" }, - { feature: "AI transcription", hyprnote: true, others: "Paid" }, - { feature: "Meeting summaries", hyprnote: true, others: "Paid" }, - { feature: "Local AI processing", hyprnote: true, others: false }, - { feature: "Offline support", hyprnote: true, others: false }, - { feature: "Calendar integration", hyprnote: true, others: "Limited" }, - { feature: "Custom templates", hyprnote: true, others: "Paid" }, - { feature: "No usage limits", hyprnote: true, others: false }, - { feature: "Open source", hyprnote: true, others: false }, - { feature: "Self-hosting option", hyprnote: true, others: false }, + { feature: "Meeting recording", char: true, others: "Limited" }, + { feature: "AI transcription", char: true, others: "Paid" }, + { feature: "Meeting summaries", char: true, others: "Paid" }, + { feature: "Local AI processing", char: true, others: false }, + { feature: "Offline support", char: true, others: false }, + { feature: "Calendar integration", char: true, others: "Limited" }, + { feature: "Custom templates", char: true, others: "Paid" }, + { feature: "No usage limits", char: true, others: false }, + { feature: "Open source", char: true, others: false }, + { feature: "Self-hosting option", char: true, others: false }, ]; const useCases = [ @@ -254,14 +254,14 @@ function ComparisonSection() { >
    {row.feature}
    - {row.hyprnote === true ? ( + {row.char === true ? ( ) : ( - {row.hyprnote} + {row.char} )}
    diff --git a/apps/web/src/routes/_view/index.tsx b/apps/web/src/routes/_view/index.tsx index 1d211664be..f64d1002b4 100644 --- a/apps/web/src/routes/_view/index.tsx +++ b/apps/web/src/routes/_view/index.tsx @@ -46,7 +46,7 @@ const mainFeatures = [ title: "Real-time transcription", description: "While you take notes, Char listens and generates a live transcript", - image: "/api/assets/hyprnote/transcript.jpg", + image: "/api/assets/char/transcript.jpg", muxPlaybackId: "rbkYuZpGJGLHx023foq9DCSt3pY1RegJU5PvMCkRE3rE", link: "/product/ai-notetaking/#transcription", }, @@ -55,7 +55,7 @@ const mainFeatures = [ title: "AI summary", description: "Char combines your notes and the transcript to create a perfect summary", - image: "/api/assets/hyprnote/summary.jpg", + image: "/api/assets/char/summary.jpg", muxPlaybackId: "lKr5l1fWGNnRqOehiz15mV79VHtFOCiuO9urmgqs6V8", link: "/product/ai-notetaking/#summaries", }, @@ -64,21 +64,21 @@ const mainFeatures = [ title: "AI Chat", description: "Use natural language to get answers pulled directly from your transcript", - image: "/api/assets/hyprnote/chat.jpg", + image: "/api/assets/char/chat.jpg", link: "/product/ai-assistant", }, { icon: "mdi:window-restore", title: "Floating panel", description: "Overlay to quick access recording controls during calls", - image: "/api/assets/hyprnote/floating.jpg", + image: "/api/assets/char/floating.jpg", link: "/product/ai-notetaking/#floating-panel", }, { icon: "mdi:keyboard-outline", title: "Keyboard shortcuts", description: "Navigate and format quickly without touching your mouse", - image: "/api/assets/hyprnote/editor.jpg", + image: "/api/assets/char/editor.jpg", muxPlaybackId: "sMWkuSxKWfH3RYnX51Xa2acih01ZP5yfQy01Q00XRd1yTQ", link: "/docs/faq/keyboard-shortcuts", }, @@ -415,7 +415,7 @@ function SocialTestimonialsSection() {

    What people are saying - Char was formerly Hyprnote.{" "} + Char was formerly Char.{" "}

    @@ -452,7 +452,7 @@ function SocialTestimonialsSection() { avatar="/avatars/tobi.jpg" body={`I'm actually very pro meeting recording and ai summarization. But I'm not ok with bots joining as fake humans accomplish this. It's a meeting between you and me. Not you and me and some startup's viral growth strategy. -Granola is great. Gemini does this well in Google Meet. Hyprnote is great and fully local. But use them with consent. +Granola is great. Gemini does this well in Google Meet. Char is great and fully local. But use them with consent. My tweet is about how ridiculous and self important it looks when you show up to a meeting with random bots as entourage.`} url="https://x.com/tobi/status/1983892259230699921" @@ -462,15 +462,15 @@ My tweet is about how ridiculous and self important it looks when you show up to author="Anand Chowdhary" username="AnandChowdhary" avatar="/avatars/anand.jpg" - body={`Hyprnote has been on my radar since their time in YC S25 as “that local-first meeting notes thing,” and I finally took a closer look today. It immediately hit a nerve I’ve had with AI note tools for years. I love the idea of getting help with meetings. I really don’t love bots joining every Zoom call or my audio being streamed to some mystery server “for quality purposes”. + body={`Char has been on my radar since their time in YC S25 as “that local-first meeting notes thing,” and I finally took a closer look today. It immediately hit a nerve I’ve had with AI note tools for years. I love the idea of getting help with meetings. I really don’t love bots joining every Zoom call or my audio being streamed to some mystery server “for quality purposes”. -@tryhyprnote leans into that tension in a pretty honest way. It calls itself a local-first AI notepad for private meetings, and the “private” bit is not just a tagline. There are no meeting bots and no calendar guests. It just listens directly to the audio going in and out of your computer, gives you a realtime transcript, and lets you stay in the conversation instead of turning into a court reporter. +@trychar leans into that tension in a pretty honest way. It calls itself a local-first AI notepad for private meetings, and the “private” bit is not just a tagline. There are no meeting bots and no calendar guests. It just listens directly to the audio going in and out of your computer, gives you a realtime transcript, and lets you stay in the conversation instead of turning into a court reporter. -You still have a simple notepad to jot quick memos during the call. Those act more like hints than homework. After the meeting, Hyprnote can use your memos to shape a personalized summary, but that part is optional. If you forget to take notes altogether, it can still generate a recap from the transcript. +You still have a simple notepad to jot quick memos during the call. Those act more like hints than homework. After the meeting, Char can use your memos to shape a personalized summary, but that part is optional. If you forget to take notes altogether, it can still generate a recap from the transcript. The tech stack is pretty nice if you are into that sort of thing. TypeScript and React on the UI, Rust and Tauri for the desktop app. The cool part is what that enables. You can run the whole thing offline with LM Studio or Ollama. No Wi‑Fi, no outbound requests. That makes it genuinely interesting for teams that care a lot about compliance or even air‑gapped environments. And if you do want cloud models, it does the “bring your own LLM” thing with Gemini, Claude, Azure‑hosted GPT, etc., so it can fit into whatever your company’s approved stack is. -If you have been waiting for an AI meeting assistant that behaves like a real desktop app and respects the fact that you might not want to ship your raw meeting audio to the cloud, Hyprnote is worth a look`} +If you have been waiting for an AI meeting assistant that behaves like a real desktop app and respects the fact that you might not want to ship your raw meeting audio to the cloud, Char is worth a look`} url="https://x.com/AnandChowdhary/status/1997980479698723119" />
    @@ -489,7 +489,7 @@ No affiliation, just love their product & hope they succeed`} author="Tom Yang" username="tomyang11_" avatar="/avatars/tom.jpg" - body="I love the flexibility that @tryhyprnote gives me to integrate personal notes with AI summaries. I can quickly jot down important points during the meeting without getting distracted, then trust that the AI will capture them in full detail for review afterwards." + body="I love the flexibility that @trychar gives me to integrate personal notes with AI summaries. I can quickly jot down important points during the meeting without getting distracted, then trust that the AI will capture them in full detail for review afterwards." url="https://twitter.com/tomyang11_/status/1956395933538902092" />
    @@ -1895,7 +1895,7 @@ export function MainFeaturesSection({
    Char ) : ( {`${feature.title} @@ -2206,7 +2206,7 @@ function FeaturesDesktopGrid() { /> ) : ( {`${feature.title} diff --git a/apps/web/src/routes/_view/integrations/$category.$slug.tsx b/apps/web/src/routes/_view/integrations/$category.$slug.tsx index 39f01f4618..5ef3c35cf7 100644 --- a/apps/web/src/routes/_view/integrations/$category.$slug.tsx +++ b/apps/web/src/routes/_view/integrations/$category.$slug.tsx @@ -119,7 +119,7 @@ function HeroSection({
    Char diff --git a/apps/web/src/routes/_view/opensource.tsx b/apps/web/src/routes/_view/opensource.tsx index ff6f0fa805..4d34bbe9b2 100644 --- a/apps/web/src/routes/_view/opensource.tsx +++ b/apps/web/src/routes/_view/opensource.tsx @@ -642,7 +642,7 @@ function ProgressSection() { { label: "Downloads", value: "40k+", - imageUrl: "/api/assets/hyprnote/icon.png", + imageUrl: "/api/assets/char/icon.png", color: "text-purple-500", }, { diff --git a/apps/web/src/routes/_view/oss-friends.tsx b/apps/web/src/routes/_view/oss-friends.tsx index a18d58a399..65cf4d9e1c 100644 --- a/apps/web/src/routes/_view/oss-friends.tsx +++ b/apps/web/src/routes/_view/oss-friends.tsx @@ -153,7 +153,7 @@ function FriendsSection({ >
    {friend.name} {appIcon ? ( Char diff --git a/apps/web/src/routes/_view/privacy.tsx b/apps/web/src/routes/_view/privacy.tsx index 8a3314dd49..eab260481d 100644 --- a/apps/web/src/routes/_view/privacy.tsx +++ b/apps/web/src/routes/_view/privacy.tsx @@ -369,32 +369,32 @@ function PrivacyComparisonSection() { const comparisons = [ { feature: "Audio processing", - hyprnote: "Local or chosen provider", + char: "Local or chosen provider", others: "Usually vendor servers", }, { feature: "Data storage", - hyprnote: "Local by default", + char: "Local by default", others: "Vendor-controlled", }, { feature: "AI training", - hyprnote: "Never", + char: "Never", others: "Often", }, { feature: "Account required", - hyprnote: "No for local use", + char: "No for local use", others: "Usually yes", }, { feature: "Data monetization", - hyprnote: "Never", + char: "Never", others: "Common", }, { feature: "Source code", - hyprnote: "Open", + char: "Open", others: "Closed", }, ]; @@ -437,7 +437,7 @@ function PrivacyComparisonSection() { icon="mdi:check-circle" className="text-lg text-green-600" /> - {row.hyprnote} + {row.char} diff --git a/apps/web/src/routes/_view/product/ai-assistant.tsx b/apps/web/src/routes/_view/product/ai-assistant.tsx index 9ecdceef7b..eed2cb3410 100644 --- a/apps/web/src/routes/_view/product/ai-assistant.tsx +++ b/apps/web/src/routes/_view/product/ai-assistant.tsx @@ -89,7 +89,7 @@ function HeroSection() {

    AI Chat AI Chat @@ -1122,7 +1122,7 @@ function CTASection() {
    Char
    $ curl -X POST - https://api.hyprnote.com/v1/notes \ + https://api.char.com/v1/notes \
    -H{" "} diff --git a/apps/web/src/routes/_view/product/bot.tsx b/apps/web/src/routes/_view/product/bot.tsx index 7af5d09a01..50e8813cc2 100644 --- a/apps/web/src/routes/_view/product/bot.tsx +++ b/apps/web/src/routes/_view/product/bot.tsx @@ -173,7 +173,7 @@ function DraggableIcon({ >
    Char { @@ -389,7 +389,7 @@ function FoldersSection() {
    Folders interface @@ -426,7 +426,7 @@ function FoldersSection() {
    Folders interface @@ -441,20 +441,20 @@ const ADVANCED_SEARCH_AUTO_ADVANCE_DURATION = 5000; const advancedSearchImages = [ { id: 1, - url: "/api/assets/hyprnote/mini-apps/search-default.jpg", + url: "/api/assets/char/mini-apps/search-default.jpg", title: "Suggestions", description: "Get instant search result suggestions based on recent activities", }, { id: 2, - url: "/api/assets/hyprnote/mini-apps/search-semantic.jpg", + url: "/api/assets/char/mini-apps/search-semantic.jpg", title: "Semantic search", description: "Find relevant info even without exact keywords", }, { id: 3, - url: "/api/assets/hyprnote/mini-apps/search-filter.jpg", + url: "/api/assets/char/mini-apps/search-filter.jpg", title: "Filters", description: "Filter out result types easily", }, @@ -578,7 +578,7 @@ function CTASection() {
    Char
    Char @@ -131,7 +131,7 @@ function HeroSection({
    Char diff --git a/apps/web/src/routes/api/assets.$.ts b/apps/web/src/routes/api/assets.$.ts index cbd76e26f2..d77d3ee9a7 100644 --- a/apps/web/src/routes/api/assets.$.ts +++ b/apps/web/src/routes/api/assets.$.ts @@ -2,8 +2,8 @@ import { createFileRoute } from "@tanstack/react-router"; const STORAGE_BUCKETS = { public_images: - "https://auth.hyprnote.com/storage/v1/object/public/public_images", - blog: "https://auth.hyprnote.com/storage/v1/object/public/blog", + "https://auth.char.com/storage/v1/object/public/public_images", + blog: "https://auth.char.com/storage/v1/object/public/blog", } as const; const SAFE_SEGMENT = /^[A-Za-z0-9._+\- ]+$/; diff --git a/apps/web/src/routes/auth.tsx b/apps/web/src/routes/auth.tsx index 9faf899bc8..04dc0f0452 100644 --- a/apps/web/src/routes/auth.tsx +++ b/apps/web/src/routes/auth.tsx @@ -54,7 +54,7 @@ export const Route = createFileRoute("/auth")({ to: "/callback/auth/", search: { flow: "desktop", - scheme: search.scheme ?? "hyprnote", + scheme: search.scheme ?? "char", access_token: result.access_token, refresh_token: result.refresh_token, }, @@ -80,7 +80,7 @@ function Component() {
    ); diff --git a/apps/web/src/routes/bluesky.tsx b/apps/web/src/routes/bluesky.tsx index 424834131e..554f83067c 100644 --- a/apps/web/src/routes/bluesky.tsx +++ b/apps/web/src/routes/bluesky.tsx @@ -3,7 +3,7 @@ import { createFileRoute, redirect } from "@tanstack/react-router"; export const Route = createFileRoute("/bluesky")({ beforeLoad: () => { throw redirect({ - href: "https://bsky.app/profile/hyprnote.bsky.social", + href: "https://bsky.app/profile/char.bsky.social", } as any); }, }); diff --git a/apps/web/src/routes/reset-password.tsx b/apps/web/src/routes/reset-password.tsx index 6decc19369..185ced40f9 100644 --- a/apps/web/src/routes/reset-password.tsx +++ b/apps/web/src/routes/reset-password.tsx @@ -57,7 +57,7 @@ function Component() { ])} > Char Char &str { match self { AmModel::ParakeetV2 => { - "https://hyprnote.s3.us-east-1.amazonaws.com/v0/nvidia_parakeet-v2_476MB.tar" + "https://char.s3.us-east-1.amazonaws.com/v0/nvidia_parakeet-v2_476MB.tar" } AmModel::ParakeetV3 => { - "https://hyprnote.s3.us-east-1.amazonaws.com/v0/nvidia_parakeet-v3_494MB.tar" + "https://char.s3.us-east-1.amazonaws.com/v0/nvidia_parakeet-v3_494MB.tar" } AmModel::WhisperLargeV3 => { - "https://hyprnote.s3.us-east-1.amazonaws.com/v0/openai_whisper-large-v3-v20240930_626MB.tar" + "https://char.s3.us-east-1.amazonaws.com/v0/openai_whisper-large-v3-v20240930_626MB.tar" } } } diff --git a/crates/api-auth/src/lib.rs b/crates/api-auth/src/lib.rs index c4cf969cac..36f94d1098 100644 --- a/crates/api-auth/src/lib.rs +++ b/crates/api-auth/src/lib.rs @@ -175,24 +175,24 @@ mod tests { #[test] fn test_auth_state_with_required_entitlement() { let state = - AuthState::new("https://example.supabase.co").with_required_entitlement("hyprnote_pro"); + AuthState::new("https://example.supabase.co").with_required_entitlement("char_pro"); assert_eq!( state.required_entitlements, - Some(vec!["hyprnote_pro".to_string()]) + Some(vec!["char_pro".to_string()]) ); } #[test] fn test_auth_state_with_required_entitlements() { let state = AuthState::new("https://example.supabase.co").with_required_entitlements(vec![ - "hyprnote_pro".to_string(), - "hyprnote_lite".to_string(), + "char_pro".to_string(), + "char_lite".to_string(), ]); assert_eq!( state.required_entitlements, Some(vec![ - "hyprnote_pro".to_string(), - "hyprnote_lite".to_string() + "char_pro".to_string(), + "char_lite".to_string() ]) ); } diff --git a/crates/api-bot/src/config.rs b/crates/api-bot/src/config.rs index 4ca70f2f8d..9df9d131d7 100644 --- a/crates/api-bot/src/config.rs +++ b/crates/api-bot/src/config.rs @@ -4,7 +4,7 @@ use serde::Deserialize; pub struct BotConfig { pub recall_api_key: String, /// Publicly reachable base URL for this API server, used to build webhook URLs. - /// e.g. "https://api.hyprnote.com" + /// e.g. "https://api.char.com" pub public_url: String, /// Publicly accessible MP4 URL played by the onboarding bot. pub demo_video_url: String, diff --git a/crates/api-bot/src/routes/bot.rs b/crates/api-bot/src/routes/bot.rs index 644abdc62d..516afe997a 100644 --- a/crates/api-bot/src/routes/bot.rs +++ b/crates/api-bot/src/routes/bot.rs @@ -45,7 +45,7 @@ pub async fn send_bot( let bot = client .create_bot(CreateBotRequest { meeting_url: req.meeting_url, - bot_name: req.bot_name.unwrap_or_else(|| "Hyprnote".into()), + bot_name: req.bot_name.unwrap_or_else(|| "Char".into()), transcription_options: Some(TranscriptionOptions { provider: TranscriptionProvider::MeetingCaptions, }), @@ -100,7 +100,7 @@ pub async fn start_demo( let bot = client .create_bot(CreateBotRequest { meeting_url: req.meeting_url.clone(), - bot_name: "Hyprnote Demo".into(), + bot_name: "Char Demo".into(), transcription_options: None, real_time_transcription: None, output_media: Some(OutputMedia { diff --git a/crates/api-bot/src/routes/webhook.rs b/crates/api-bot/src/routes/webhook.rs index 5b6340a5d6..3dcbabe182 100644 --- a/crates/api-bot/src/routes/webhook.rs +++ b/crates/api-bot/src/routes/webhook.rs @@ -13,19 +13,19 @@ pub async fn status_change( let code = &event.data.status.code; tracing::info!( - hyprnote.bot.id = %bot_id, - hyprnote.bot.status_code = ?code, + char.bot.id = %bot_id, + char.bot.status_code = ?code, "bot_status_change" ); match code { BotStatusCode::CallEnded => { - tracing::info!(hyprnote.bot.id = %bot_id, "bot_call_ended"); + tracing::info!(char.bot.id = %bot_id, "bot_call_ended"); } BotStatusCode::Fatal => { let message = event.data.status.message.as_deref().unwrap_or("unknown"); tracing::error!( - hyprnote.bot.id = %bot_id, + char.bot.id = %bot_id, error = %message, "bot_fatal" ); @@ -53,10 +53,10 @@ pub async fn transcript(Json(payload): Json) -> Result<()> { .join(" "); tracing::info!( - hyprnote.bot.id = %payload.bot_id, - hyprnote.transcript.speaker = %payload.transcript.speaker, - hyprnote.transcript.is_final = payload.transcript.is_final, - hyprnote.transcript.char_count = text.chars().count() as u64, + char.bot.id = %payload.bot_id, + char.transcript.speaker = %payload.transcript.speaker, + char.transcript.is_final = payload.transcript.is_final, + char.transcript.char_count = text.chars().count() as u64, "transcript_received" ); diff --git a/crates/api-nango/src/routes/connect.rs b/crates/api-nango/src/routes/connect.rs index fd98ed4752..a4850df71b 100644 --- a/crates/api-nango/src/routes/connect.rs +++ b/crates/api-nango/src/routes/connect.rs @@ -118,10 +118,10 @@ pub async fn create_session( Err(hypr_nango::Error::Api(404, response_body)) => { tracing::warn!( enduser.id = %user_id, - hyprnote.integration.id = %body.integration_id, - hyprnote.connection.id = %existing.connection_id, - hyprnote.connection.status = %existing.status, - hyprnote.http.response.body = %response_body, + char.integration.id = %body.integration_id, + char.connection.id = %existing.connection_id, + char.connection.status = %existing.status, + char.http.response.body = %response_body, "reconnect session failed with not found, cleaning stale local row" ); state diff --git a/crates/api-nango/src/routes/disconnect.rs b/crates/api-nango/src/routes/disconnect.rs index ce5eb94341..f2ed77055b 100644 --- a/crates/api-nango/src/routes/disconnect.rs +++ b/crates/api-nango/src/routes/disconnect.rs @@ -47,8 +47,8 @@ pub async fn delete_connection( if !owns { tracing::warn!( enduser.id = %auth.claims.sub, - hyprnote.connection.id = %body.connection_id, - hyprnote.integration.id = %body.integration_id, + char.connection.id = %body.connection_id, + char.integration.id = %body.integration_id, "disconnect denied: connection not owned by user" ); return Err(crate::error::NangoError::Forbidden( @@ -65,9 +65,9 @@ pub async fn delete_connection( Err(hypr_nango::Error::Api(404, response_body)) => { tracing::warn!( enduser.id = %auth.claims.sub, - hyprnote.connection.id = %body.connection_id, - hyprnote.integration.id = %body.integration_id, - hyprnote.http.response.body = %response_body, + char.connection.id = %body.connection_id, + char.integration.id = %body.integration_id, + char.http.response.body = %response_body, "nango connection already deleted, cleaning local row" ); } diff --git a/crates/api-nango/src/routes/webhook.rs b/crates/api-nango/src/routes/webhook.rs index 553c048f07..901546c9f2 100644 --- a/crates/api-nango/src/routes/webhook.rs +++ b/crates/api-nango/src/routes/webhook.rs @@ -146,8 +146,8 @@ pub(crate) async fn handle_auth_webhook(state: &AppState, payload: NangoAuthWebh })?; tracing::warn!( - hyprnote.connection.id = %payload.connection_id, - hyprnote.integration.id = %payload.provider_config_key, + char.connection.id = %payload.connection_id, + char.integration.id = %payload.provider_config_key, error.type = error_type, error = error_description, "nango token refresh failed" @@ -167,8 +167,8 @@ pub(crate) async fn handle_auth_webhook(state: &AppState, payload: NangoAuthWebh })?; tracing::info!( - hyprnote.integration.id = %payload.provider_config_key, - hyprnote.connection.id = %payload.connection_id, + char.integration.id = %payload.provider_config_key, + char.connection.id = %payload.connection_id, "nango connection deleted locally from webhook" ); @@ -178,8 +178,8 @@ pub(crate) async fn handle_auth_webhook(state: &AppState, payload: NangoAuthWebh if payload.success && payload.operation != AuthOperation::Deletion { let Some(end_user_id) = payload.end_user_id() else { tracing::warn!( - hyprnote.connection.id = %payload.connection_id, - hyprnote.integration.id = %payload.provider_config_key, + char.connection.id = %payload.connection_id, + char.integration.id = %payload.provider_config_key, "nango auth webhook missing end user id, skipping persistence" ); return Ok(()); @@ -201,9 +201,9 @@ pub(crate) async fn handle_auth_webhook(state: &AppState, payload: NangoAuthWebh tracing::info!( enduser.id = end_user_id, - hyprnote.integration.id = %payload.provider_config_key, - hyprnote.connection.id = %payload.connection_id, - hyprnote.auth.operation = ?payload.operation, + char.integration.id = %payload.provider_config_key, + char.connection.id = %payload.connection_id, + char.auth.operation = ?payload.operation, "nango connection upserted" ); @@ -238,8 +238,8 @@ fn spawn_identity_task( Ok(connection) => connection.tags.unwrap_or_default(), Err(e) => { tracing::warn!( - hyprnote.connection.id = %connection_id, - hyprnote.integration.id = %integration_id, + char.connection.id = %connection_id, + char.integration.id = %integration_id, error = %e, "failed to fetch connection before patching account_identity tag" ); @@ -259,16 +259,16 @@ fn spawn_identity_task( { Ok(()) => { tracing::info!( - hyprnote.connection.id = %connection_id, - hyprnote.integration.id = %integration_id, + char.connection.id = %connection_id, + char.integration.id = %integration_id, account_identity = %identity, "account_identity tag set" ); } Err(e) => { tracing::warn!( - hyprnote.connection.id = %connection_id, - hyprnote.integration.id = %integration_id, + char.connection.id = %connection_id, + char.integration.id = %integration_id, error = %e, "failed to patch account_identity tag" ); @@ -277,8 +277,8 @@ fn spawn_identity_task( } Err(e) => { tracing::warn!( - hyprnote.connection.id = %connection_id, - hyprnote.integration.id = %integration_id, + char.connection.id = %connection_id, + char.integration.id = %integration_id, error = %e, "failed to fetch identity for account_identity tag" ); diff --git a/crates/api-research/assets/research_chat.md.jinja b/crates/api-research/assets/research_chat.md.jinja index c9b92dd572..3fc10f947e 100644 --- a/crates/api-research/assets/research_chat.md.jinja +++ b/crates/api-research/assets/research_chat.md.jinja @@ -1,4 +1,4 @@ -You are a helpful research assistant for Hyprnote, a privacy-first AI-powered note-taking application. +You are a helpful research assistant for Char, a privacy-first AI-powered note-taking application. Your role is to help users with: diff --git a/crates/api-research/src/mcp/server.rs b/crates/api-research/src/mcp/server.rs index 519015d20f..8151bef47c 100644 --- a/crates/api-research/src/mcp/server.rs +++ b/crates/api-research/src/mcp/server.rs @@ -82,7 +82,7 @@ impl ServerHandler for ResearchMcpServer { .enable_prompts() .build(), server_info: Implementation { - name: "hyprnote-research".to_string(), + name: "char-research".to_string(), title: None, version: env!("CARGO_PKG_VERSION").to_string(), icons: None, diff --git a/crates/api-subscription/src/routes/account.rs b/crates/api-subscription/src/routes/account.rs index 98aa82d115..de3f3fd144 100644 --- a/crates/api-subscription/src/routes/account.rs +++ b/crates/api-subscription/src/routes/account.rs @@ -130,7 +130,7 @@ async fn try_delete_stripe_customer(state: &AppState, user_id: &str) -> Result<( Ok(_) => { tracing::info!( enduser.id = %user_id, - hyprnote.billing.customer.id = %customer_id, + char.billing.customer.id = %customer_id, "stripe_customer_deleted" ); Ok(()) @@ -138,7 +138,7 @@ async fn try_delete_stripe_customer(state: &AppState, user_id: &str) -> Result<( Err(e) => { tracing::error!( enduser.id = %user_id, - hyprnote.billing.customer.id = %customer_id, + char.billing.customer.id = %customer_id, error = %e, "stripe_customer_deletion_failed" ); diff --git a/crates/api-subscription/src/routes/billing.rs b/crates/api-subscription/src/routes/billing.rs index ad6bc52848..0d07d43ada 100644 --- a/crates/api-subscription/src/routes/billing.rs +++ b/crates/api-subscription/src/routes/billing.rs @@ -24,7 +24,7 @@ use crate::trial::{Interval, StartTrialQuery, StartTrialResponse, TrialOutcome}; #[tracing::instrument( name = "subscription.start_trial", skip(state, query, auth, device_fingerprint), - fields(hyprnote.subsystem = "subscription") + fields(char.subsystem = "subscription") )] pub async fn start_trial( State(state): State, diff --git a/crates/api-subscription/src/stripe.rs b/crates/api-subscription/src/stripe.rs index a0eeb9a9d9..5afb3592ed 100644 --- a/crates/api-subscription/src/stripe.rs +++ b/crates/api-subscription/src/stripe.rs @@ -61,8 +61,8 @@ pub(crate) async fn get_or_create_customer( .map_err(|e: stripe::StripeError| SubscriptionError::Stripe(e.to_string()))?; tracing::info!( service.peer.name = "stripe", - hyprnote.stripe.operation = "create_customer", - hyprnote.duration_ms = start.elapsed().as_millis() as u64, + char.stripe.operation = "create_customer", + char.duration_ms = start.elapsed().as_millis() as u64, "stripe_request_finished" ); @@ -132,8 +132,8 @@ pub(crate) async fn create_trial_subscription( .map_err(|e: stripe::StripeError| SubscriptionError::Stripe(e.to_string()))?; tracing::info!( service.peer.name = "stripe", - hyprnote.stripe.operation = "create_trial_subscription", - hyprnote.duration_ms = start.elapsed().as_millis() as u64, + char.stripe.operation = "create_trial_subscription", + char.duration_ms = start.elapsed().as_millis() as u64, "stripe_request_finished" ); diff --git a/crates/api-subscription/src/supabase.rs b/crates/api-subscription/src/supabase.rs index 7cc0daca4d..2aadb395ee 100644 --- a/crates/api-subscription/src/supabase.rs +++ b/crates/api-subscription/src/supabase.rs @@ -58,10 +58,10 @@ impl SupabaseClient { .map_err(|e| SubscriptionError::SupabaseRequest(e.to_string()))?; tracing::info!( service.peer.name = "supabase", - hyprnote.supabase.operation = "rpc", - hyprnote.supabase.function = %function_name, + char.supabase.operation = "rpc", + char.supabase.function = %function_name, http.response.status_code = response.status().as_u16(), - hyprnote.duration_ms = start.elapsed().as_millis() as u64, + char.duration_ms = start.elapsed().as_millis() as u64, "supabase_request_finished" ); @@ -113,10 +113,10 @@ impl SupabaseClient { .map_err(|e| SubscriptionError::SupabaseRequest(e.to_string()))?; tracing::info!( service.peer.name = "supabase", - hyprnote.supabase.operation = "select", - hyprnote.supabase.table = %table, + char.supabase.operation = "select", + char.supabase.table = %table, http.response.status_code = response.status().as_u16(), - hyprnote.duration_ms = start.elapsed().as_millis() as u64, + char.duration_ms = start.elapsed().as_millis() as u64, "supabase_request_finished" ); @@ -171,10 +171,10 @@ impl SupabaseClient { .map_err(|e| SubscriptionError::SupabaseRequest(e.to_string()))?; tracing::info!( service.peer.name = "supabase", - hyprnote.supabase.operation = "update", - hyprnote.supabase.table = %table, + char.supabase.operation = "update", + char.supabase.table = %table, http.response.status_code = response.status().as_u16(), - hyprnote.duration_ms = start.elapsed().as_millis() as u64, + char.duration_ms = start.elapsed().as_millis() as u64, "supabase_request_finished" ); @@ -213,9 +213,9 @@ impl SupabaseClient { .map_err(|e| SubscriptionError::SupabaseRequest(e.to_string()))?; tracing::info!( service.peer.name = "supabase", - hyprnote.supabase.operation = "admin_get_stripe_customer_id", + char.supabase.operation = "admin_get_stripe_customer_id", http.response.status_code = response.status().as_u16(), - hyprnote.duration_ms = start.elapsed().as_millis() as u64, + char.duration_ms = start.elapsed().as_millis() as u64, "supabase_request_finished" ); @@ -265,9 +265,9 @@ impl SupabaseClient { .map_err(|e| SubscriptionError::SupabaseRequest(e.to_string()))?; tracing::info!( service.peer.name = "supabase", - hyprnote.supabase.operation = "admin_delete_storage_objects.list", + char.supabase.operation = "admin_delete_storage_objects.list", http.response.status_code = response.status().as_u16(), - hyprnote.duration_ms = start.elapsed().as_millis() as u64, + char.duration_ms = start.elapsed().as_millis() as u64, "supabase_request_finished" ); @@ -312,9 +312,9 @@ impl SupabaseClient { .map_err(|e| SubscriptionError::SupabaseRequest(e.to_string()))?; tracing::info!( service.peer.name = "supabase", - hyprnote.supabase.operation = "admin_delete_storage_objects.delete", + char.supabase.operation = "admin_delete_storage_objects.delete", http.response.status_code = response.status().as_u16(), - hyprnote.duration_ms = start.elapsed().as_millis() as u64, + char.duration_ms = start.elapsed().as_millis() as u64, "supabase_request_finished" ); @@ -341,9 +341,9 @@ impl SupabaseClient { .map_err(|e| SubscriptionError::SupabaseRequest(e.to_string()))?; tracing::info!( service.peer.name = "supabase", - hyprnote.supabase.operation = "admin_delete_user", + char.supabase.operation = "admin_delete_user", http.response.status_code = response.status().as_u16(), - hyprnote.duration_ms = start.elapsed().as_millis() as u64, + char.duration_ms = start.elapsed().as_millis() as u64, "supabase_request_finished" ); diff --git a/crates/audio-actual/src/mic.rs b/crates/audio-actual/src/mic.rs index 20fd3d280c..70012d0fed 100644 --- a/crates/audio-actual/src/mic.rs +++ b/crates/audio-actual/src/mic.rs @@ -109,7 +109,7 @@ impl MicInput { crate::Error::MicOpenFailed })?; tracing::info!( - hyprnote.audio.sample_rate_hz = ?config.sample_rate(), + char.audio.sample_rate_hz = ?config.sample_rate(), device_name, "mic_input_initialized" ); diff --git a/crates/audio-actual/src/speaker/linux.rs b/crates/audio-actual/src/speaker/linux.rs index a991b3dee2..b92e0d0a5a 100644 --- a/crates/audio-actual/src/speaker/linux.rs +++ b/crates/audio-actual/src/speaker/linux.rs @@ -247,7 +247,7 @@ fn pipewire_capture_loop( let stream = pw::stream::StreamBox::new( &core, - "hyprnote-speaker-capture", + "char-speaker-capture", properties! { *pw::keys::MEDIA_TYPE => "Audio", *pw::keys::MEDIA_CATEGORY => "Capture", @@ -305,7 +305,7 @@ fn pipewire_capture_loop( if rate > 0 { user_data.current_sample_rate.store(rate, Ordering::Release); tracing::info!( - hyprnote.audio.sample_rate_hz = rate, + char.audio.sample_rate_hz = rate, "pipewire_capture_initialized" ); } @@ -409,7 +409,7 @@ fn pulseaudio_capture_loop( init_tx: std::sync::mpsc::Sender>, ) -> Result<()> { let mut mainloop = Mainloop::new().context("Failed to create PulseAudio mainloop")?; - let mut context = PaContext::new(&mainloop, "hyprnote-speaker-capture") + let mut context = PaContext::new(&mainloop, "char-speaker-capture") .context("Failed to create PulseAudio context")?; context @@ -434,11 +434,11 @@ fn pulseaudio_capture_loop( let monitor_device = get_default_monitor_device(&mut mainloop, &context) .context("Failed to resolve PulseAudio monitor source")?; - tracing::info!(hyprnote.audio.device = %monitor_device, "connecting_to_monitor_device"); + tracing::info!(char.audio.device = %monitor_device, "connecting_to_monitor_device"); mainloop.lock(); let stream_result = (|| -> Result<_> { - let mut stream = PaStream::new(&mut context, "hyprnote-capture", &spec, None) + let mut stream = PaStream::new(&mut context, "char-capture", &spec, None) .context("Failed to create PulseAudio stream")?; stream .connect_record( @@ -475,7 +475,7 @@ fn pulseaudio_capture_loop( current_sample_rate.store(actual_rate, Ordering::Release); tracing::info!( - hyprnote.audio.sample_rate_hz = actual_rate, + char.audio.sample_rate_hz = actual_rate, "pulseaudio_capture_initialized" ); let _ = init_tx.send(Ok(())); diff --git a/crates/audio-actual/src/speaker/windows.rs b/crates/audio-actual/src/speaker/windows.rs index 5f6877941f..83fda3b95a 100644 --- a/crates/audio-actual/src/speaker/windows.rs +++ b/crates/audio-actual/src/speaker/windows.rs @@ -208,7 +208,7 @@ fn capture_audio_loop( current_sample_rate.store(capture_format.sample_rate, Ordering::Release); tracing::info!( - hyprnote.audio.sample_rate_hz = capture_format.sample_rate, + char.audio.sample_rate_hz = capture_format.sample_rate, "wasapi_loopback_initialized" ); let _ = init_tx.send(Ok(())); diff --git a/crates/audio-device/src/linux.rs b/crates/audio-device/src/linux.rs index 1c3723af46..922b864b00 100644 --- a/crates/audio-device/src/linux.rs +++ b/crates/audio-device/src/linux.rs @@ -22,7 +22,7 @@ impl PulseConnection { Error::AudioSystemError("Failed to create PulseAudio mainloop".into()) })?; - let context = Context::new(&mainloop, "hyprnote-audio-device") + let context = Context::new(&mainloop, "char-audio-device") .ok_or_else(|| Error::AudioSystemError("Failed to create PulseAudio context".into()))?; Ok(Self { mainloop, context }) @@ -561,7 +561,7 @@ impl AudioDeviceBackend for LinuxBackend { pub fn is_headphone_from_default_output_device() -> Option { let mut mainloop = Mainloop::new()?; - let mut context = Context::new(&mainloop, "hyprnote-headphone-check")?; + let mut context = Context::new(&mainloop, "char-headphone-check")?; if context .connect(None, ContextFlagSet::NOFLAGS, None) diff --git a/crates/audio-sync/tests/e2e.rs b/crates/audio-sync/tests/e2e.rs index ca6b252ee0..61d202af39 100644 --- a/crates/audio-sync/tests/e2e.rs +++ b/crates/audio-sync/tests/e2e.rs @@ -276,9 +276,9 @@ const PAIRS: &[AudioPair] = &[ lpb: include_bytes!("../../aec/data/inputs/doubletalk_lpb_sample.wav"), }, AudioPair { - name: "hyprnote", - mic: include_bytes!("../../aec/data/inputs/hyprnote_mic.wav"), - lpb: include_bytes!("../../aec/data/inputs/hyprnote_lpb.wav"), + name: "char", + mic: include_bytes!("../../aec/data/inputs/char_mic.wav"), + lpb: include_bytes!("../../aec/data/inputs/char_lpb.wav"), }, AudioPair { name: "theo", @@ -310,7 +310,7 @@ fn natural_lag_measurement() { assert!(result.skipped_low_confidence > 0); assert_eq!(result.locked_measurements, 0); } - "hyprnote" => { + "char" => { assert!(result.measurements.is_empty()); assert!(result.skipped_low_energy > 0); assert_eq!(result.locked_measurements, 0); @@ -359,7 +359,7 @@ fn artificial_delay_detection() { assert!(result.skipped_low_confidence > 0); assert_eq!(result.locked_measurements, 0); } - "hyprnote" => { + "char" => { assert!(result.measurements.is_empty()); assert!(result.skipped_low_energy > 0); assert_eq!(result.locked_measurements, 0); diff --git a/crates/buffer/src/lib.rs b/crates/buffer/src/lib.rs index 21064120f9..feb09c0a96 100644 --- a/crates/buffer/src/lib.rs +++ b/crates/buffer/src/lib.rs @@ -304,7 +304,7 @@ mod tests { fn test_md_to_md_3() { let input = r#" # Enhanced Meeting Notes -## What Hyprnote Does +## What Char Does - A smart notepad for people with back-to-back meetings. - Listens to the meeting so you don't have to write everything down. - Merges your notes and the transcript into a clean, context-aware summary. @@ -320,20 +320,20 @@ mod tests { - Offers powerful extensions—like real-time transcripts and CRM uploads (e.g. Twenty). ## Stay Connected -- Follow updates on [X](https://hyprnote.com/x). -- Join the community and chat on [Discord](https://hyprnote.com/discord). +- Follow updates on [X](https://char.com/x). +- Join the community and chat on [Discord](https://char.com/discord). # Participants: -* [John Jeong](mailto:john@hyprnote.com) -* [Yujong Lee](mailto:yujonglee@hyprnote.com) +* [John Jeong](mailto:john@char.com) +* [Yujong Lee](mailto:yujonglee@char.com) # Meeting Transcript (No raw excerpt provided, utilized to generate the enhanced note) "#; insta::assert_snapshot!(md_to_md(input).unwrap().to_string(), @" - # What Hyprnote Does + # What Char Does - A smart notepad for people with back-to-back meetings. - Listens to the meeting so you don't have to write everything down. @@ -359,15 +359,15 @@ mod tests { # Stay Connected - - Follow updates on [X](https://hyprnote.com/x). - - Join the community and chat on [Discord](https://hyprnote.com/discord). + - Follow updates on [X](https://char.com/x). + - Join the community and chat on [Discord](https://char.com/discord).   # Participants: - - [John Jeong](mailto:john@hyprnote.com) - - [Yujong Lee](mailto:yujonglee@hyprnote.com) + - [John Jeong](mailto:john@char.com) + - [Yujong Lee](mailto:yujonglee@char.com)   @@ -381,12 +381,12 @@ mod tests { #[test] fn test_md_to_md_4() { let input = r#" -# Hyprnote: Enhanced Meeting Notes +# Char: Enhanced Meeting Notes -# Objective: Introduce Hyprnote as a smart notepad for enhanced meeting productivity. +# Objective: Introduce Char as a smart notepad for enhanced meeting productivity. # Privacy & Performance: Built locally, prioritizing user data security and seamless experience. # Flexible & Extendable: Supports various use cases beyond sales, offering a simple and powerful solution. -# Stay Connected: Promote Hyprnote through X and Discord. +# Stay Connected: Promote Char through X and Discord. # Key Features: # - Offline transcription and note-taking. @@ -396,17 +396,17 @@ mod tests { # Benefits: Streamlines meetings, improves productivity, and enhances data capture. -# Further Information: Follow updates on [X](https://hyprnote.com/x) and [Discord](https://hyprnote.com/discord). +# Further Information: Follow updates on [X](https://char.com/x) and [Discord](https://char.com/discord). "#; - insta::assert_snapshot!(md_to_md(input).unwrap().to_string(), @"# Further Information: Follow updates on [X](https://hyprnote.com/x) and [Discord](https://hyprnote.com/discord)."); + insta::assert_snapshot!(md_to_md(input).unwrap().to_string(), @"# Further Information: Follow updates on [X](https://char.com/x) and [Discord](https://char.com/discord)."); } #[test] fn test_opinionated_md_to_html() { let input = r#" # Enhanced Meeting Notes -## What Hyprnote Does +## What Char Does - A smart notepad for people with back-to-back meetings. - Listens to the meeting so you don't have to write everything down. - Merges your notes and the transcript into a clean, context-aware summary. @@ -422,12 +422,12 @@ mod tests { - Offers powerful extensions—like real-time transcripts and CRM uploads (e.g. Twenty). ## Stay Connected -- Follow updates on [X](https://hyprnote.com/x). -- Join the community and chat on [Discord](https://hyprnote.com/discord). +- Follow updates on [X](https://char.com/x). +- Join the community and chat on [Discord](https://char.com/discord). "#; insta::assert_snapshot!(opinionated_md_to_html(input).unwrap().to_string(), @r#" -

    What Hyprnote Does

    +

    What Char Does