Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "hotusage"
version = "0.8.0"
version = "0.9.0"
edition = "2024"

[dependencies]
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@


# <img src="docs/logo.png" alt="" width="25" height="25"> hotusage
# <img src="docs/logo.png" alt="" width="25" height="25"> hototel

Know what your team's AI coding agents are actually doing — who uses them, on
which projects, with which models, and what it would cost.
Expand All @@ -21,14 +21,14 @@ whole team's:
macOS and Linux, one line:

```bash
curl -fsSL https://raw.githubusercontent.com/hotdata-dev/hotusage-client/main/install.sh | sh
curl -fsSL https://raw.githubusercontent.com/hotdata-dev/hototel-client/main/install.sh | sh
```

Your browser opens, you approve the machine, and the first sync runs
immediately. Nothing to configure.

On Windows, download the `.zip` from the
[latest release](https://github.com/hotdata-dev/hotusage-client/releases),
[latest release](https://github.com/hotdata-dev/hototel-client/releases),
put `hotusage.exe` somewhere on your PATH, and run `hotusage install`.

## Ask your agent
Expand Down
2 changes: 1 addition & 1 deletion docs/internals.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ First run writes `~/.hotusage/collector.json`:

```json
{
"server_url": "https://www.hotusage.ai",
"server_url": "https://hototel.com",
"token": "<written by Sign In>",
"user_email": "you@company.com",
"interval_minutes": 15,
Expand Down
6 changes: 3 additions & 3 deletions install.sh
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
#!/bin/sh
# hotusage installer for macOS and Linux.
#
# curl -fsSL https://raw.githubusercontent.com/hotdata-dev/hotusage-client/main/install.sh | sh
# curl -fsSL https://raw.githubusercontent.com/hotdata-dev/hototel-client/main/install.sh | sh
#
# Downloads the latest release binary for this machine, puts it on PATH,
# registers it as a background agent (macOS LaunchAgent / systemd user unit),
# and installs the agent skill so Claude Code and Codex can answer questions
# about your organization's usage.
set -eu

REPO=hotdata-dev/hotusage-client
REPO=hotdata-dev/hototel-client
BIN=hotusage
# What this was called before 0.4.0. `$BIN install` retires the old service
# registration itself; the stale binary is this script's job, because a copy
Expand Down Expand Up @@ -175,7 +175,7 @@ TXT
cat <<TXT

note: this machine can report usage but not read it, so the agent skill cannot
answer questions yet. The hotusage server needs updating; after that, run:
answer questions yet. The hototel server needs updating; after that, run:
$dest/$BIN signin --force
TXT
fi
Expand Down
51 changes: 30 additions & 21 deletions src/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ pub struct Config {
impl Default for Config {
fn default() -> Self {
Config {
server_url: "https://www.hotusage.ai".into(),
server_url: "https://hototel.com".into(),
token: String::new(),
user_email: String::new(),
interval_minutes: 15,
Expand Down Expand Up @@ -117,12 +117,14 @@ pub fn load_config() -> Config {
if cfg.user_email.is_empty() {
cfg.user_email = guess_email();
}
// Installs created before the default changed point at the apex, which
// 301s to www; a POST that follows it arrives as a GET (401 on sign-in,
// a silently discarded upload on sync). Changing the default cannot help
// those machines, because their config file already exists -- so repair
// the value itself, on the exact host and nothing else.
if let Some(fixed) = apex_to_www(&cfg.server_url) {
// The service moved from hotusage.ai to hototel.com (Sep 2026; the old
// domain still serves during the transition, but is being retired).
// Changing the default cannot help existing machines, because their
// config file already exists -- so repair the value itself, on the exact
// legacy hosts and nothing else. Same token, same backend: only the host
// changes. (This replaces the earlier apex-to-www repair, which existed
// because the old apex once 301'd POSTs into GETs.)
if let Some(fixed) = legacy_to_hototel(&cfg.server_url) {
cfg.server_url = fixed;
let _ = save_config(&cfg);
}
Expand All @@ -135,12 +137,14 @@ pub fn load_config() -> Config {
cfg
}

/// `https://hotusage.ai[/...]` -> `https://www.hotusage.ai[/...]`, or None
/// when nothing needs changing.
fn apex_to_www(url: &str) -> Option<String> {
let rest = url.strip_prefix("https://hotusage.ai")?;
/// `https://hotusage.ai[/...]` or `https://www.hotusage.ai[/...]` ->
/// `https://hototel.com[/...]`, or None when nothing needs changing.
fn legacy_to_hototel(url: &str) -> Option<String> {
let rest = url
.strip_prefix("https://www.hotusage.ai")
.or_else(|| url.strip_prefix("https://hotusage.ai"))?;
if rest.is_empty() || rest.starts_with('/') {
Some(format!("https://www.hotusage.ai{rest}"))
Some(format!("https://hototel.com{rest}"))
} else {
None
}
Expand Down Expand Up @@ -557,17 +561,22 @@ pub fn sync(config: &Config) -> Result<String, String> {

#[cfg(test)]
mod tests {
use super::{apex_to_www, is_loopback, validated_url};
use super::{is_loopback, legacy_to_hototel, validated_url};

#[test]
fn apex_is_repaired_but_nothing_else_is() {
assert_eq!(apex_to_www("https://hotusage.ai").as_deref(),
Some("https://www.hotusage.ai"));
assert_eq!(apex_to_www("https://hotusage.ai/").as_deref(),
Some("https://www.hotusage.ai/"));
assert!(apex_to_www("https://www.hotusage.ai").is_none());
assert!(apex_to_www("https://hotusage.ai.evil.example").is_none());
assert!(apex_to_www("https://internal.example").is_none());
fn legacy_hosts_are_repaired_but_nothing_else_is() {
assert_eq!(legacy_to_hototel("https://hotusage.ai").as_deref(),
Some("https://hototel.com"));
assert_eq!(legacy_to_hototel("https://hotusage.ai/").as_deref(),
Some("https://hototel.com/"));
assert_eq!(legacy_to_hototel("https://www.hotusage.ai").as_deref(),
Some("https://hototel.com"));
assert_eq!(legacy_to_hototel("https://www.hotusage.ai/x").as_deref(),
Some("https://hototel.com/x"));
assert!(legacy_to_hototel("https://hototel.com").is_none());
assert!(legacy_to_hototel("https://hotusage.ai.evil.example").is_none());
assert!(legacy_to_hototel("https://www.hotusage.ai.evil.example").is_none());
assert!(legacy_to_hototel("https://internal.example").is_none());
}

#[test]
Expand Down
2 changes: 1 addition & 1 deletion src/tray.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const UPDATE_ITEM_POSITION: usize = 4;

/// The releases page, for the platform that has no scripted install.
#[cfg(not(unix))]
const RELEASES_URL: &str = "https://github.com/hotdata-dev/hotusage-client/releases/latest";
const RELEASES_URL: &str = "https://github.com/hotdata-dev/hototel-client/releases/latest";

/// What the update row says. It names the version rather than saying "an
/// update is available", so the click is a decision about a known thing.
Expand Down
2 changes: 1 addition & 1 deletion src/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ fn private_tempdir() -> Result<std::path::PathBuf, String> {
Err("could not create a temporary directory".into())
}

const REPO: &str = "hotdata-dev/hotusage-client";
const REPO: &str = "hotdata-dev/hototel-client";

/// The installer as it stood at one tag. Pinned rather than tracking `main`:
/// see the module comment -- this script is executed, and the tag resolved by
Expand Down
Loading