feat(coding-agent): add install telemetry ping controls
This commit is contained in:
@@ -2,6 +2,32 @@
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## Telemetry
|
||||||
|
|
||||||
|
Interactive mode now sends a lightweight anonymous install/update telemetry ping to `https://pi.dev/install?version=x.y.z` after it writes `lastChangelogVersion` in `settings.json`.
|
||||||
|
|
||||||
|
Why this exists:
|
||||||
|
- Pi needs a reliable per-version usage signal to understand whether releases are being adopted and to help justify funding continued development.
|
||||||
|
- npm download counts are not a reliable proxy for actual Pi usage.
|
||||||
|
|
||||||
|
How it works:
|
||||||
|
- It only runs in interactive mode.
|
||||||
|
- It does not run in RPC mode, print mode, JSON mode, or SDK mode.
|
||||||
|
- On a fresh interactive install, Pi writes `lastChangelogVersion`, then sends the ping.
|
||||||
|
- On later interactive startups, if the local changelog contains entries newer than the previously stored `lastChangelogVersion`, Pi writes the new `lastChangelogVersion`, then sends the ping.
|
||||||
|
- The request is fire-and-forget. Startup does not wait for it, and any errors are ignored.
|
||||||
|
|
||||||
|
What data is collected:
|
||||||
|
- Only the Pi version in the request path, for example `https://pi.dev/install?version=0.66.1`.
|
||||||
|
- The server stores only aggregate per-version counters such as `{ "0.66.1": 3 }`.
|
||||||
|
- It does not store IP addresses, client identifiers, prompts, paths, models, auth state, or any other per-user data. It literally only increments a counter for that version.
|
||||||
|
|
||||||
|
How to disable it:
|
||||||
|
- `/settings` → disable `Install telemetry`
|
||||||
|
- `settings.json` → set `enableInstallTelemetry` to `false`
|
||||||
|
- `PI_OFFLINE=1`
|
||||||
|
- `PI_TELEMETRY=0`
|
||||||
|
|
||||||
### New Features
|
### New Features
|
||||||
|
|
||||||
- Full `openRouterRouting` support in `models.json`, including fallbacks, parameter requirements, data collection, ZDR, ignore lists, quantizations, provider sorting, max price, and preferred throughput and latency constraints. See [docs/models.md](docs/models.md).
|
- Full `openRouterRouting` support in `models.json`, including fallbacks, parameter requirements, data collection, ZDR, ignore lists, quantizations, provider sorting, max price, and preferred throughput and latency constraints. See [docs/models.md](docs/models.md).
|
||||||
|
|||||||
@@ -271,6 +271,8 @@ Use `/settings` to modify common options, or edit JSON files directly:
|
|||||||
|
|
||||||
See [docs/settings.md](docs/settings.md) for all options.
|
See [docs/settings.md](docs/settings.md) for all options.
|
||||||
|
|
||||||
|
To opt out of anonymous install/update telemetry tied to changelog detection, set `enableInstallTelemetry` to `false` in `settings.json`, or set `PI_TELEMETRY=0`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Context Files
|
## Context Files
|
||||||
@@ -594,6 +596,7 @@ pi --thinking high "Solve this complex problem"
|
|||||||
| `PI_CODING_AGENT_DIR` | Override config directory (default: `~/.pi/agent`) |
|
| `PI_CODING_AGENT_DIR` | Override config directory (default: `~/.pi/agent`) |
|
||||||
| `PI_PACKAGE_DIR` | Override package directory (useful for Nix/Guix where store paths tokenize poorly) |
|
| `PI_PACKAGE_DIR` | Override package directory (useful for Nix/Guix where store paths tokenize poorly) |
|
||||||
| `PI_SKIP_VERSION_CHECK` | Skip version check at startup |
|
| `PI_SKIP_VERSION_CHECK` | Skip version check at startup |
|
||||||
|
| `PI_TELEMETRY` | Override install telemetry. Use `1`/`true`/`yes` to enable or `0`/`false`/`no` to disable |
|
||||||
| `PI_CACHE_RETENTION` | Set to `long` for extended prompt cache (Anthropic: 1h, OpenAI: 24h) |
|
| `PI_CACHE_RETENTION` | Set to `long` for extended prompt cache (Anthropic: 1h, OpenAI: 24h) |
|
||||||
| `VISUAL`, `EDITOR` | External editor for Ctrl+G |
|
| `VISUAL`, `EDITOR` | External editor for Ctrl+G |
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ Edit directly or use `/settings` for common options.
|
|||||||
| `theme` | string | `"dark"` | Theme name (`"dark"`, `"light"`, or custom) |
|
| `theme` | string | `"dark"` | Theme name (`"dark"`, `"light"`, or custom) |
|
||||||
| `quietStartup` | boolean | `false` | Hide startup header |
|
| `quietStartup` | boolean | `false` | Hide startup header |
|
||||||
| `collapseChangelog` | boolean | `false` | Show condensed changelog after updates |
|
| `collapseChangelog` | boolean | `false` | Show condensed changelog after updates |
|
||||||
|
| `enableInstallTelemetry` | boolean | `true` | Send an anonymous version/update ping after changelog-detected updates |
|
||||||
| `doubleEscapeAction` | string | `"tree"` | Action for double-escape: `"tree"`, `"fork"`, or `"none"` |
|
| `doubleEscapeAction` | string | `"tree"` | Action for double-escape: `"tree"`, `"fork"`, or `"none"` |
|
||||||
| `treeFilterMode` | string | `"default"` | Default filter for `/tree`: `"default"`, `"no-tools"`, `"user-only"`, `"labeled-only"`, `"all"` |
|
| `treeFilterMode` | string | `"default"` | Default filter for `/tree`: `"default"`, `"no-tools"`, `"user-only"`, `"labeled-only"`, `"all"` |
|
||||||
| `editorPaddingX` | number | `0` | Horizontal padding for input editor (0-3) |
|
| `editorPaddingX` | number | `0` | Horizontal padding for input editor (0-3) |
|
||||||
|
|||||||
@@ -323,6 +323,7 @@ ${chalk.bold("Environment Variables:")}
|
|||||||
${ENV_AGENT_DIR.padEnd(32)} - Session storage directory (default: ~/${CONFIG_DIR_NAME}/agent)
|
${ENV_AGENT_DIR.padEnd(32)} - Session storage directory (default: ~/${CONFIG_DIR_NAME}/agent)
|
||||||
PI_PACKAGE_DIR - Override package directory (for Nix/Guix store paths)
|
PI_PACKAGE_DIR - Override package directory (for Nix/Guix store paths)
|
||||||
PI_OFFLINE - Disable startup network operations when set to 1/true/yes
|
PI_OFFLINE - Disable startup network operations when set to 1/true/yes
|
||||||
|
PI_TELEMETRY - Override install telemetry when set to 1/true/yes or 0/false/no
|
||||||
PI_SHARE_VIEWER_URL - Base URL for /share command (default: https://pi.dev/session/)
|
PI_SHARE_VIEWER_URL - Base URL for /share command (default: https://pi.dev/session/)
|
||||||
PI_AI_ANTIGRAVITY_VERSION - Override Antigravity User-Agent version (e.g., 1.23.0)
|
PI_AI_ANTIGRAVITY_VERSION - Override Antigravity User-Agent version (e.g., 1.23.0)
|
||||||
|
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ export interface Settings {
|
|||||||
shellCommandPrefix?: string; // Prefix prepended to every bash command (e.g., "shopt -s expand_aliases" for alias support)
|
shellCommandPrefix?: string; // Prefix prepended to every bash command (e.g., "shopt -s expand_aliases" for alias support)
|
||||||
npmCommand?: string[]; // Command used for npm package lookup/install operations, argv-style (e.g., ["mise", "exec", "node@20", "--", "npm"])
|
npmCommand?: string[]; // Command used for npm package lookup/install operations, argv-style (e.g., ["mise", "exec", "node@20", "--", "npm"])
|
||||||
collapseChangelog?: boolean; // Show condensed changelog after update (use /changelog for full)
|
collapseChangelog?: boolean; // Show condensed changelog after update (use /changelog for full)
|
||||||
|
enableInstallTelemetry?: boolean; // default: true - anonymous version/update ping after changelog-detected updates
|
||||||
packages?: PackageSource[]; // Array of npm/git package sources (string or object with filtering)
|
packages?: PackageSource[]; // Array of npm/git package sources (string or object with filtering)
|
||||||
extensions?: string[]; // Array of local extension file paths or directories
|
extensions?: string[]; // Array of local extension file paths or directories
|
||||||
skills?: string[]; // Array of local skill file paths or directories
|
skills?: string[]; // Array of local skill file paths or directories
|
||||||
@@ -736,6 +737,16 @@ export class SettingsManager {
|
|||||||
this.save();
|
this.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getEnableInstallTelemetry(): boolean {
|
||||||
|
return this.settings.enableInstallTelemetry ?? true;
|
||||||
|
}
|
||||||
|
|
||||||
|
setEnableInstallTelemetry(enabled: boolean): void {
|
||||||
|
this.globalSettings.enableInstallTelemetry = enabled;
|
||||||
|
this.markModified("enableInstallTelemetry");
|
||||||
|
this.save();
|
||||||
|
}
|
||||||
|
|
||||||
getPackages(): PackageSource[] {
|
getPackages(): PackageSource[] {
|
||||||
return [...(this.settings.packages ?? [])];
|
return [...(this.settings.packages ?? [])];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export interface SettingsConfig {
|
|||||||
availableThemes: string[];
|
availableThemes: string[];
|
||||||
hideThinkingBlock: boolean;
|
hideThinkingBlock: boolean;
|
||||||
collapseChangelog: boolean;
|
collapseChangelog: boolean;
|
||||||
|
enableInstallTelemetry: boolean;
|
||||||
doubleEscapeAction: "fork" | "tree" | "none";
|
doubleEscapeAction: "fork" | "tree" | "none";
|
||||||
treeFilterMode: "default" | "no-tools" | "user-only" | "labeled-only" | "all";
|
treeFilterMode: "default" | "no-tools" | "user-only" | "labeled-only" | "all";
|
||||||
showHardwareCursor: boolean;
|
showHardwareCursor: boolean;
|
||||||
@@ -66,6 +67,7 @@ export interface SettingsCallbacks {
|
|||||||
onThemePreview?: (theme: string) => void;
|
onThemePreview?: (theme: string) => void;
|
||||||
onHideThinkingBlockChange: (hidden: boolean) => void;
|
onHideThinkingBlockChange: (hidden: boolean) => void;
|
||||||
onCollapseChangelogChange: (collapsed: boolean) => void;
|
onCollapseChangelogChange: (collapsed: boolean) => void;
|
||||||
|
onEnableInstallTelemetryChange: (enabled: boolean) => void;
|
||||||
onDoubleEscapeActionChange: (action: "fork" | "tree" | "none") => void;
|
onDoubleEscapeActionChange: (action: "fork" | "tree" | "none") => void;
|
||||||
onTreeFilterModeChange: (mode: "default" | "no-tools" | "user-only" | "labeled-only" | "all") => void;
|
onTreeFilterModeChange: (mode: "default" | "no-tools" | "user-only" | "labeled-only" | "all") => void;
|
||||||
onShowHardwareCursorChange: (enabled: boolean) => void;
|
onShowHardwareCursorChange: (enabled: boolean) => void;
|
||||||
@@ -206,6 +208,13 @@ export class SettingsSelectorComponent extends Container {
|
|||||||
currentValue: config.quietStartup ? "true" : "false",
|
currentValue: config.quietStartup ? "true" : "false",
|
||||||
values: ["true", "false"],
|
values: ["true", "false"],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "install-telemetry",
|
||||||
|
label: "Install telemetry",
|
||||||
|
description: "Send an anonymous version/update ping after changelog-detected updates",
|
||||||
|
currentValue: config.enableInstallTelemetry ? "true" : "false",
|
||||||
|
values: ["true", "false"],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "double-escape-action",
|
id: "double-escape-action",
|
||||||
label: "Double-escape action",
|
label: "Double-escape action",
|
||||||
@@ -396,6 +405,9 @@ export class SettingsSelectorComponent extends Container {
|
|||||||
case "quiet-startup":
|
case "quiet-startup":
|
||||||
callbacks.onQuietStartupChange(newValue === "true");
|
callbacks.onQuietStartupChange(newValue === "true");
|
||||||
break;
|
break;
|
||||||
|
case "install-telemetry":
|
||||||
|
callbacks.onEnableInstallTelemetryChange(newValue === "true");
|
||||||
|
break;
|
||||||
case "double-escape-action":
|
case "double-escape-action":
|
||||||
callbacks.onDoubleEscapeActionChange(newValue as "fork" | "tree");
|
callbacks.onDoubleEscapeActionChange(newValue as "fork" | "tree");
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -136,6 +136,11 @@ function isAnthropicSubscriptionAuthKey(apiKey: string | undefined): boolean {
|
|||||||
return typeof apiKey === "string" && apiKey.startsWith("sk-ant-oat");
|
return typeof apiKey === "string" && apiKey.startsWith("sk-ant-oat");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isTruthyEnvFlag(value: string | undefined): boolean {
|
||||||
|
if (!value) return false;
|
||||||
|
return value === "1" || value.toLowerCase() === "true" || value.toLowerCase() === "yes";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options for InteractiveMode initialization.
|
* Options for InteractiveMode initialization.
|
||||||
*/
|
*/
|
||||||
@@ -767,20 +772,41 @@ export class InteractiveMode {
|
|||||||
const entries = parseChangelog(changelogPath);
|
const entries = parseChangelog(changelogPath);
|
||||||
|
|
||||||
if (!lastVersion) {
|
if (!lastVersion) {
|
||||||
// Fresh install - just record the version, don't show changelog
|
// Fresh install - record the version, send telemetry, don't show changelog
|
||||||
this.settingsManager.setLastChangelogVersion(VERSION);
|
this.settingsManager.setLastChangelogVersion(VERSION);
|
||||||
|
this.reportInstallTelemetry(VERSION);
|
||||||
return undefined;
|
return undefined;
|
||||||
} else {
|
}
|
||||||
const newEntries = getNewEntries(entries, lastVersion);
|
|
||||||
if (newEntries.length > 0) {
|
const newEntries = getNewEntries(entries, lastVersion);
|
||||||
this.settingsManager.setLastChangelogVersion(VERSION);
|
if (newEntries.length > 0) {
|
||||||
return newEntries.map((e) => e.content).join("\n\n");
|
this.settingsManager.setLastChangelogVersion(VERSION);
|
||||||
}
|
this.reportInstallTelemetry(VERSION);
|
||||||
|
return newEntries.map((e) => e.content).join("\n\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private reportInstallTelemetry(version: string): void {
|
||||||
|
if (process.env.PI_OFFLINE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const telemetryEnv = process.env.PI_TELEMETRY;
|
||||||
|
const telemetryEnabled =
|
||||||
|
telemetryEnv !== undefined ? isTruthyEnvFlag(telemetryEnv) : this.settingsManager.getEnableInstallTelemetry();
|
||||||
|
if (!telemetryEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fetch(`https://pi.dev/install?version=${encodeURIComponent(version)}`, {
|
||||||
|
signal: AbortSignal.timeout(5000),
|
||||||
|
})
|
||||||
|
.then(() => undefined)
|
||||||
|
.catch(() => undefined);
|
||||||
|
}
|
||||||
|
|
||||||
private getMarkdownThemeWithSettings(): MarkdownTheme {
|
private getMarkdownThemeWithSettings(): MarkdownTheme {
|
||||||
return {
|
return {
|
||||||
...getMarkdownTheme(),
|
...getMarkdownTheme(),
|
||||||
@@ -3352,6 +3378,7 @@ export class InteractiveMode {
|
|||||||
availableThemes: getAvailableThemes(),
|
availableThemes: getAvailableThemes(),
|
||||||
hideThinkingBlock: this.hideThinkingBlock,
|
hideThinkingBlock: this.hideThinkingBlock,
|
||||||
collapseChangelog: this.settingsManager.getCollapseChangelog(),
|
collapseChangelog: this.settingsManager.getCollapseChangelog(),
|
||||||
|
enableInstallTelemetry: this.settingsManager.getEnableInstallTelemetry(),
|
||||||
doubleEscapeAction: this.settingsManager.getDoubleEscapeAction(),
|
doubleEscapeAction: this.settingsManager.getDoubleEscapeAction(),
|
||||||
treeFilterMode: this.settingsManager.getTreeFilterMode(),
|
treeFilterMode: this.settingsManager.getTreeFilterMode(),
|
||||||
showHardwareCursor: this.settingsManager.getShowHardwareCursor(),
|
showHardwareCursor: this.settingsManager.getShowHardwareCursor(),
|
||||||
@@ -3427,6 +3454,9 @@ export class InteractiveMode {
|
|||||||
onCollapseChangelogChange: (collapsed) => {
|
onCollapseChangelogChange: (collapsed) => {
|
||||||
this.settingsManager.setCollapseChangelog(collapsed);
|
this.settingsManager.setCollapseChangelog(collapsed);
|
||||||
},
|
},
|
||||||
|
onEnableInstallTelemetryChange: (enabled) => {
|
||||||
|
this.settingsManager.setEnableInstallTelemetry(enabled);
|
||||||
|
},
|
||||||
onQuietStartupChange: (enabled) => {
|
onQuietStartupChange: (enabled) => {
|
||||||
this.settingsManager.setQuietStartup(enabled);
|
this.settingsManager.setQuietStartup(enabled);
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user