Skip to content
Open
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
4 changes: 4 additions & 0 deletions .typos.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
[default]
# PostHog project keys (usage telemetry) are random text, not words.
extend-ignore-re = ["phc_[A-Za-z0-9]+"]

[default.extend-identifiers]
# Proper names - do not correct
Mno-hime = "Mno-hime"
Expand Down
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Added `skills` to `cloudsmith upstream`. Use `cloudsmith upstream skills list`, `create`, `update`, and `delete` to manage AI Skills upstreams. An AI Skills upstream caches skills and plugins from a GitHub repository.
- Added `--changelog` to `cloudsmith push skills`. Use it to add release notes to an AI Skills package. The option is available only for the `skills` format.
- Added `--compress` to `cloudsmith push skills`. With this option, you can pass a skill folder instead of a `.zip` archive. The CLI compresses the contents of the folder into a `.zip` archive, then uploads it. If you pass a folder without `--compress`, the command fails with a usage error. The option is available only for the `skills` format.
- Added the `cloudsmith telemetry` command group for usage telemetry. The CLI does not send usage telemetry yet.
- Added pseudonymous usage telemetry. After each command, the CLI sends one event to Cloudsmith through PostHog (EU). Usage telemetry is on by default, also in CI. For more information, see <https://docs.cloudsmith.com/developer-tools/cli-telemetry>.
- An event contains the command name, the names of the options and of the environment variables that you set, the output format, how the command ended (exit code and API status), and how long it took. It also contains where the credential came from (for example, an option, an environment variable, `credentials.ini`, the keyring, or OIDC, not the credential), where each setting came from (an option, an environment variable, or a config file, not the value), the CLI version and install method, the operating system and its version, the CPU architecture, and the Python version. It also shows whether the command ran in CI and which CI provider, or from an AI coding agent and which agent. It also lists the names of the keys that are set in `config.ini` and `credentials.ini`, as written (for example, `org` or `workspace`), but never their values.
- The CLI never sends option or argument values, file names, credentials, workspace, repository, or package names, or host names. PostHog discards the IP address.
- The CLI stores a random install ID in `telemetry.json` in its config directory, so that repeat use from the same machine can be counted. In CI, in containers, and in the Docker image, the CLI uses a new random ID for each command and stores nothing. To not store an ID, set `CLOUDSMITH_TELEMETRY_INSTALL_ID=0`.
- On the first interactive command, the CLI shows a notice and sends nothing for that command.
- The CLI does not send an event for the credential helpers, `cloudsmith mcp start`, the `cloudsmith telemetry` commands, shell completion, an API host that is not a Cloudsmith host, or when certificate verification is off.
- The CLI waits a maximum of 1 second for the event to send, shared with the update check. After a failed send, the CLI sends nothing for 24 hours.
- PostHog deletes the events 12 to 13 months after the CLI sends them. The events are not linked to your account, so Cloudsmith cannot delete them on request before then.
- To turn it off, use the same settings as for error reports: set `DO_NOT_TRACK` or `CLOUDSMITH_NO_TELEMETRY` to `1`, `true`, or `yes`, or set `telemetry = false` in `config.ini`. When you turn it off, the CLI deletes the stored install ID.
- Added the `cloudsmith telemetry` command group for usage telemetry.
- `cloudsmith telemetry status` shows whether usage telemetry is on, and the reason. The reason can be an opt-out, such as `DO_NOT_TRACK`, `CLOUDSMITH_NO_TELEMETRY`, or `telemetry = false` in `config.ini`. It can also be an API host that is not a Cloudsmith host, or certificate verification that is off.
- `cloudsmith telemetry id` shows the install ID that the CLI stores in `telemetry.json` in its config directory, or `none`.
- `cloudsmith telemetry reset` deletes the stored install ID. The CLI creates a new, unrelated ID on the next command.
Expand Down
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ You can specify the following configuration options:
- `ssl_verify_strict`: Whether or not to apply strict X.509 certificate verification (`VERIFY_X509_STRICT`, enabled by default on Python 3.13+). Set to `false` (or use `--without-ssl-verify-strict` / `CLOUDSMITH_SSL_VERIFY_STRICT=0`) if a TLS-intercepting proxy issues certificates that fail these checks. `CLOUDSMITH_SSL_VERIFY_STRICT=1` keeps strict verification enabled (the default). Certificates are still verified.
- `api_user_agent`: The user agent to use for requests.
- `workspace`: The Cloudsmith Workspace slug. Use `-w`/`--workspace` or `CLOUDSMITH_WORKSPACE` to set it for one command. The config keys `org`, `organization`, and `oidc_org`, the flags `--org`, `--organization`, `--oidc-org`, `--owner`, and `-o`, and the environment variable `CLOUDSMITH_ORG` still work, but they are deprecated and will be removed in version 2.0.0; the CLI prints a warning when you use them.
- `telemetry`: Set to `false` to turn off error reports and usage telemetry. See [Telemetry](#telemetry).

### Credentials (credentials.ini)

Expand Down Expand Up @@ -567,6 +568,25 @@ To downgrade failures to a warning (the package is still uploaded, and a copy-pa
```


## Telemetry

The CLI sends two kinds of data to Cloudsmith, to help us fix errors and improve the CLI:

- **Error reports** (Sentry): when a command fails with an unexpected error, the CLI sends the error and stack trace. It first removes credentials, email and IP addresses, user and machine names, and file paths.
- **Usage telemetry** (PostHog, EU): after each command, the CLI sends one pseudonymous event. It contains the command name, the names of the options and environment variables that you set, how the command ended and how long it took, and your CLI, Python, and operating system versions. It never contains option or argument values, file names, credentials, or workspace, repository, or package names. The first interactive command shows a notice and sends nothing. PostHog deletes the events after 12 to 13 months.

The CLI stores a random install ID for usage telemetry. Use `cloudsmith telemetry id` to see it, `cloudsmith telemetry reset` to replace it, and `cloudsmith telemetry status` to see whether usage telemetry is on.

To see what the CLI would send, without sending it, set `CLOUDSMITH_TELEMETRY=log`.

To turn off both kinds, do one of these:

- Set `DO_NOT_TRACK=1` or `CLOUDSMITH_NO_TELEMETRY=1`.
- Add `telemetry = false` to the `[default]` section or a `[profile:NAME]` section of `config.ini`.

For more information, see [CLI telemetry](https://docs.cloudsmith.com/developer-tools/cli-telemetry).


## Contributing

Yes! Please do contribute, this is why we love open source. Please see [CONTRIBUTING](https://github.com/cloudsmith-io/cloudsmith-cli/blob/master/CONTRIBUTING.md) for contribution guidelines when making code changes or raising issues for bug reports, ideas, discussions and/or questions (i.e. help required).
Expand Down
2 changes: 2 additions & 0 deletions cloudsmith_cli/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ def refuse(*args, **kwargs): # pylint: disable=unused-argument
settings.SAMPLE_RATE_ENV,
):
monkeypatch.delenv(name, raising=False)
# Tests run as if no key were built in; usage_events supplies a test key.
monkeypatch.setattr(settings, "POSTHOG_PROJECT_KEY", "")
monkeypatch.setattr(sender, "_post", refuse)
yield calls
if calls:
Expand Down
8 changes: 4 additions & 4 deletions cloudsmith_cli/core/tests/test_usage_notice.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ def test_show_prints_to_stderr_only(usage_state_dir, capsys):
assert notice.show() is True
out, err = capsys.readouterr()
assert out == ""
assert "usage data" in err and "CLOUDSMITH_NO_TELEMETRY" in err
# The notice names the install ID and how to see and reset it.
assert "install ID" in err
assert "cloudsmith telemetry id" in err and "cloudsmith telemetry reset" in err
assert "usage data" in err
# The details and the opt-outs live on the docs page; the notice links it.
assert "https://docs.cloudsmith.com/developer-tools/cli-telemetry" in err
assert "opt out" in err
assert state.read()["notice_version"] == notice.NOTICE_VERSION


Expand Down
18 changes: 15 additions & 3 deletions cloudsmith_cli/core/tests/test_usage_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,21 @@
from cloudsmith_cli.core.usage import settings


def test_no_production_key_and_override_wins():
assert settings.POSTHOG_PROJECT_KEY == ""
assert settings.project_key({}) == ""
def test_the_production_key_is_built_in():
# Read from the source: the autouse conftest guard blanks it at runtime.
import re

with open(settings.__file__, encoding="utf-8") as source:
match = re.search(
r'^POSTHOG_PROJECT_KEY = \(?\s*"([^"]*)"', source.read(), re.M
)
assert match and re.fullmatch(r"phc_[A-Za-z0-9]{40,50}", match.group(1))
assert settings.POSTHOG_HOST == "https://eu.i.posthog.com"


def test_the_override_replaces_the_key(monkeypatch):
monkeypatch.setattr(settings, "POSTHOG_PROJECT_KEY", "phc_builtin")
assert settings.project_key({}) == "phc_builtin"
assert settings.override_set({}) is False
env = {settings.KEY_ENV: "phc_test", settings.HOST_ENV: "https://eu.i.posthog.com"}
assert settings.project_key(env) == "phc_test"
Expand Down
18 changes: 6 additions & 12 deletions cloudsmith_cli/core/usage/notice.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"""The one-time usage telemetry notice.

Bump ``NOTICE_VERSION`` whenever what is collected changes, so the notice is
shown again. The wording is a draft.
shown again.
"""

import click
Expand All @@ -11,17 +11,11 @@

NOTICE_VERSION = 2
NOTICE_TEXT = (
"The Cloudsmith CLI collects pseudonymous usage data: which commands run, "
"how they end and how long they take, plus CLI, Python and OS versions. It "
"never collects argument values, file names, credentials or account "
"names. A random install ID is kept on this machine so repeat use can "
"be counted; see it with 'cloudsmith telemetry id' and replace it with "
"'cloudsmith telemetry reset'.\n"
"To opt out, set CLOUDSMITH_NO_TELEMETRY=1 or DO_NOT_TRACK=1, or add "
"'telemetry = false' to config.ini. To see exactly what is sent, set "
"CLOUDSMITH_TELEMETRY=log.\n"
"Learn more: https://docs.cloudsmith.com/developer-tools/cli-telemetry\n"
"Nothing was sent for this command. This notice will not be shown again."
"The Cloudsmith CLI collects pseudonymous usage data to help improve the "
"CLI. It never collects argument values, file names or credentials.\n"
"Read what is collected (and how to opt out): "
"https://docs.cloudsmith.com/developer-tools/cli-telemetry\n"
"Nothing was sent for this command."
)


Expand Down
12 changes: 9 additions & 3 deletions cloudsmith_cli/core/usage/settings.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Copyright 2026 Cloudsmith Ltd
"""Switches and endpoints for usage telemetry.

There is no production project key yet: only the developer override
(``CLOUDSMITH_TELEMETRY_POSTHOG_KEY``) can make the CLI send.
Released builds send to the "CLI" PostHog EU project (336063). Development
builds (source checkouts, editable installs) send nothing unless a developer
sets ``CLOUDSMITH_TELEMETRY_POSTHOG_KEY``, which also replaces the key.
"""

import os
Expand All @@ -11,7 +12,12 @@
from ..telemetry_common import MODE_ENV # noqa: F401
from ..telemetry_common import log_mode as _shared_log_mode

POSTHOG_PROJECT_KEY = ""
#: Project key of the dedicated "CLI" PostHog EU project (336063). Not a
#: secret: a PostHog project key can only send events, and every client that
#: reports to PostHog ships one (like the Sentry DSN in core/telemetry.py).
POSTHOG_PROJECT_KEY = (
"phc_p989pbFFZ98a4eDbLc4yxAjSPWF8en2fjodbrhtMc2yY" # gitleaks:allow
)
POSTHOG_HOST = "https://eu.i.posthog.com"

KEY_ENV = "CLOUDSMITH_TELEMETRY_POSTHOG_KEY"
Expand Down
Loading