Skip to content

Commit 3ac60dd

Browse files
committed
feat(cli): add PostHog telemetry tracking
1 parent 8dcfba7 commit 3ac60dd

15 files changed

Lines changed: 950 additions & 2 deletions

File tree

docs/references/steel-cli.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,16 +73,29 @@ For generated flags and argument schemas, use [../cli-reference.md](../cli-refer
7373

7474
- Config directory: `~/.config/steel`
7575
- Main config: `~/.config/steel/config.json`
76+
- Telemetry state: `~/.config/steel/telemetry.json`
7677
- Browser session state: `~/.config/steel/browser-session-state.json`
7778
- Profile metadata: `~/.config/steel/profiles/<name>.json`
7879

80+
Telemetry can be disabled persistently in `config.json` with:
81+
82+
```json
83+
{
84+
"telemetry": {
85+
"disabled": true
86+
}
87+
}
88+
```
89+
7990
## Environment Variables (Common)
8091

8192
- `STEEL_API_KEY`: cloud auth for session API and browser runtime.
8293
- `STEEL_API_URL`: cloud API endpoint override.
8394
- `STEEL_BROWSER_API_URL`: canonical self-hosted local endpoint override.
8495
- `STEEL_LOCAL_API_URL`: backward-compatible self-hosted alias.
8596
- `STEEL_CONFIG_DIR`: override config directory root.
97+
- `STEEL_TELEMETRY_HOST`: override the PostHog ingest host.
98+
- `STEEL_TELEMETRY_DISABLED`: force telemetry off.
8699
- `STEEL_PROFILE`: default profile name for browser sessions.
87100

88101
## Key References

src/commands/browser/action.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,50 @@ pub enum ActionCommand {
161161
Close,
162162
}
163163

164+
impl ActionCommand {
165+
pub const fn telemetry_name(&self) -> &'static str {
166+
match self {
167+
Self::Navigate(_) => "navigate",
168+
Self::Back => "back",
169+
Self::Forward => "forward",
170+
Self::Reload => "reload",
171+
Self::Click(_) => "click",
172+
Self::DblClick(_) => "dblclick",
173+
Self::Fill(_) => "fill",
174+
Self::Type(_) => "type",
175+
Self::Press(_) => "press",
176+
Self::Hover(_) => "hover",
177+
Self::Focus(_) => "focus",
178+
Self::Check(_) => "check",
179+
Self::Uncheck(_) => "uncheck",
180+
Self::Select(_) => "select",
181+
Self::Clear(_) => "clear",
182+
Self::SelectAll(_) => "selectall",
183+
Self::Scroll(_) => "scroll",
184+
Self::ScrollIntoView(_) => "scrollintoview",
185+
Self::SetValue(_) => "setvalue",
186+
Self::Snapshot(_) => "snapshot",
187+
Self::Screenshot(_) => "screenshot",
188+
Self::Eval(_) => "eval",
189+
Self::Find(_) => "find",
190+
Self::Content => "content",
191+
Self::Get { .. } => "get",
192+
Self::Is { .. } => "is",
193+
Self::Wait(_) => "wait",
194+
Self::Tab { .. } => "tab",
195+
Self::Cookies { .. } => "cookies",
196+
Self::Storage { .. } => "storage",
197+
Self::Drag(_) => "drag",
198+
Self::Upload(_) => "upload",
199+
Self::Highlight(_) => "highlight",
200+
Self::Set { .. } => "set",
201+
Self::BringToFront => "bringtofront",
202+
Self::Diff { .. } => "diff",
203+
Self::Close => "close",
204+
}
205+
}
206+
}
207+
164208
// ── Get subcommands ─────────────────────────────────────────────────
165209

166210
#[derive(Subcommand)]

src/commands/browser/captcha.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@ pub enum Command {
1616
Status(StatusArgs),
1717
}
1818

19+
impl Command {
20+
pub const fn telemetry_name(&self) -> &'static str {
21+
match self {
22+
Self::Solve(_) => "solve",
23+
Self::Status(_) => "status",
24+
}
25+
}
26+
}
27+
1928
#[derive(Parser)]
2029
pub struct SolveArgs {
2130
/// Explicit session ID (overrides --session name lookup)

src/commands/browser/mod.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,20 @@ pub enum Command {
4646
Action(action::ActionCommand),
4747
}
4848

49+
impl Command {
50+
pub fn telemetry_name(&self) -> String {
51+
match self {
52+
Self::Start(_) => "start".to_string(),
53+
Self::Stop(_) => "stop".to_string(),
54+
Self::Sessions(_) => "sessions".to_string(),
55+
Self::Live(_) => "live".to_string(),
56+
Self::Captcha { command } => format!("captcha.{}", command.telemetry_name()),
57+
Self::Batch(_) => "batch".to_string(),
58+
Self::Action(action) => action.telemetry_name().to_string(),
59+
}
60+
}
61+
}
62+
4963
pub async fn run(args: BrowserArgs) -> anyhow::Result<()> {
5064
let session = args.session;
5165
if let Some(ref name) = session

src/commands/browser/start.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ pub async fn run(args: Args, session: Option<&str>) -> anyhow::Result<()> {
7373
}
7474

7575
let persist_profile = args.profile.is_some() && args.update_profile;
76+
let proxy_enabled = args.proxy.is_some();
77+
let namespace_set = args.namespace.is_some();
7678

7779
// If a daemon is already running for this session name, stop it first.
7880
// `start` always creates a fresh session — use `steel browser sessions`
@@ -122,6 +124,19 @@ pub async fn run(args: Args, session: Option<&str>) -> anyhow::Result<()> {
122124
)?;
123125
}
124126

127+
let mut properties = serde_json::Map::new();
128+
properties.insert("stealth".into(), json!(args.stealth));
129+
properties.insert("proxy_enabled".into(), json!(proxy_enabled));
130+
properties.insert("has_profile".into(), json!(args.profile.is_some()));
131+
properties.insert("persist_profile".into(), json!(persist_profile));
132+
properties.insert("credentials".into(), json!(args.credentials));
133+
properties.insert("namespace_set".into(), json!(namespace_set));
134+
properties.insert(
135+
"session_timeout_set".into(),
136+
json!(args.session_timeout.is_some()),
137+
);
138+
crate::telemetry::track_event("browser_session_started", properties);
139+
125140
display_session_info(&info);
126141

127142
Ok(())

src/commands/browser/stop.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pub async fn run(args: Args, session: Option<&str>) -> anyhow::Result<()> {
1616
anyhow::bail!("Cannot combine `--all` with `--session`.");
1717
}
1818

19+
let stopped_count;
1920
if args.all {
2021
let names = process::list_daemon_names();
2122
if names.is_empty() {
@@ -30,6 +31,7 @@ pub async fn run(args: Args, session: Option<&str>) -> anyhow::Result<()> {
3031
for name in &names {
3132
let _ = process::stop_daemon(name).await;
3233
}
34+
stopped_count = names.len() as u64;
3335

3436
if output::is_json() {
3537
output::success_data(json!({ "stoppedSessions": names }));
@@ -39,6 +41,7 @@ pub async fn run(args: Args, session: Option<&str>) -> anyhow::Result<()> {
3941
} else {
4042
let session_name = session.unwrap_or("default");
4143
process::stop_daemon(session_name).await?;
44+
stopped_count = 1;
4245

4346
if output::is_json() {
4447
output::success_data(json!({ "stoppedSessions": [session_name] }));
@@ -47,5 +50,10 @@ pub async fn run(args: Args, session: Option<&str>) -> anyhow::Result<()> {
4750
}
4851
}
4952

53+
let mut properties = serde_json::Map::new();
54+
properties.insert("all".into(), json!(args.all));
55+
properties.insert("stopped_count".into(), json!(stopped_count));
56+
crate::telemetry::track_event("browser_session_stopped", properties);
57+
5058
Ok(())
5159
}

src/commands/config.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,9 @@ pub async fn run(_args: Args) -> anyhow::Result<()> {
3232
println!("browser.apiUrl: {url}");
3333
}
3434

35+
if let Some(ref cfg) = config {
36+
println!("telemetry.disabled: {}", cfg.telemetry_disabled());
37+
}
38+
3539
Ok(())
3640
}

src/commands/credentials.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,17 @@ pub enum Command {
1919
Delete(DeleteArgs),
2020
}
2121

22+
impl Command {
23+
pub const fn telemetry_name(&self) -> &'static str {
24+
match self {
25+
Self::List(_) => "list",
26+
Self::Create(_) => "create",
27+
Self::Update(_) => "update",
28+
Self::Delete(_) => "delete",
29+
}
30+
}
31+
}
32+
2233
#[derive(Parser)]
2334
pub struct ListArgs {
2435
/// Filter by namespace

src/commands/dev.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ pub enum Command {
1717
Stop(StopArgs),
1818
}
1919

20+
impl Command {
21+
pub const fn telemetry_name(&self) -> &'static str {
22+
match self {
23+
Self::Install(_) => "install",
24+
Self::Start(_) => "start",
25+
Self::Stop(_) => "stop",
26+
}
27+
}
28+
}
29+
2030
#[derive(Parser)]
2131
pub struct InstallArgs {
2232
/// Git repository URL for local Steel Browser runtime

src/commands/login.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ pub async fn run(_args: Args) -> anyhow::Result<()> {
2727

2828
save_api_key(&api_key, &name)?;
2929

30+
crate::telemetry::track_event("login_completed", serde_json::Map::new());
31+
3032
status!("Authentication successful! Your API key has been saved.");
3133

3234
Ok(())

0 commit comments

Comments
 (0)