-
Notifications
You must be signed in to change notification settings - Fork 251
feat(cli): add opt-out anonymous telemetry via PostHog #52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b7c75b8
6e530bc
c6e1243
dd586f4
6c88c56
e52a19b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,6 +14,7 @@ import { execSync, execFileSync, spawn } from "node:child_process"; | |
| import * as clack from "@clack/prompts"; | ||
| import { c } from "../ui/colors.js"; | ||
| import { TEMPLATES, type TemplateId } from "../templates/generators.js"; | ||
| import { trackInitTemplate } from "../telemetry/events.js"; | ||
|
|
||
| const ALL_TEMPLATE_IDS = TEMPLATES.map((t) => t.id); | ||
|
|
||
|
|
@@ -380,6 +381,7 @@ export default defineCommand({ | |
| } | ||
|
|
||
| scaffoldProject(destDir, basename(destDir), templateId, localVideoName); | ||
| trackInitTemplate(templateId); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Claude: |
||
|
|
||
| console.log(c.success(`\nCreated ${c.accent(name + "/")}`)); | ||
| for (const f of readdirSync(destDir)) { | ||
|
|
@@ -499,6 +501,7 @@ export default defineCommand({ | |
|
|
||
| // 4. Copy template and patch | ||
| scaffoldProject(destDir, name, templateId, localVideoName); | ||
| trackInitTemplate(templateId); | ||
|
|
||
| const files = readdirSync(destDir); | ||
| clack.note(files.map((f) => c.accent(f)).join("\n"), c.success(`Created ${name}/`)); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ import { loadProducer } from "../utils/producer.js"; | |
| import { c } from "../ui/colors.js"; | ||
| import { formatBytes, formatDuration, errorBox } from "../ui/format.js"; | ||
| import { renderProgress } from "../ui/progress.js"; | ||
| import { trackRenderComplete, trackRenderError } from "../telemetry/events.js"; | ||
|
|
||
| const VALID_FPS = new Set([24, 30, 60]); | ||
| const VALID_QUALITY = new Set(["draft", "standard", "high"]); | ||
|
|
@@ -158,12 +159,21 @@ async function renderDocker( | |
| }); | ||
| await producer.executeRenderJob(job, projectDir, outputPath); | ||
| } catch (error: unknown) { | ||
| trackRenderError({ fps: options.fps, quality: options.quality, docker: true }); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Claude: The error tracking fires before |
||
| const message = error instanceof Error ? error.message : String(error); | ||
| errorBox("Render failed", message, "Try --docker for containerized rendering"); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| const elapsed = Date.now() - startTime; | ||
| trackRenderComplete({ | ||
| durationMs: elapsed, | ||
| fps: options.fps, | ||
| quality: options.quality, | ||
| workers: options.workers ?? 4, | ||
| docker: true, | ||
| gpu: options.gpu, | ||
| }); | ||
| printRenderComplete(outputPath, elapsed, options.quiet); | ||
| } | ||
|
|
||
|
|
@@ -191,12 +201,21 @@ async function renderLocal( | |
| try { | ||
| await producer.executeRenderJob(job, projectDir, outputPath, onProgress); | ||
| } catch (error: unknown) { | ||
| trackRenderError({ fps: options.fps, quality: options.quality, docker: false }); | ||
| const message = error instanceof Error ? error.message : String(error); | ||
| errorBox("Render failed", message, "Try --docker for containerized rendering"); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| const elapsed = Date.now() - startTime; | ||
| trackRenderComplete({ | ||
| durationMs: elapsed, | ||
| fps: options.fps, | ||
| quality: options.quality, | ||
| workers: options.workers ?? 4, | ||
| docker: false, | ||
| gpu: options.gpu, | ||
| }); | ||
| printRenderComplete(outputPath, elapsed, options.quiet); | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| import { defineCommand } from "citty"; | ||
| import { c } from "../ui/colors.js"; | ||
| import { readConfig, writeConfig, CONFIG_PATH } from "../telemetry/config.js"; | ||
|
|
||
| function runEnable(): void { | ||
| const config = readConfig(); | ||
| config.telemetryEnabled = true; | ||
| writeConfig(config); | ||
| console.log(`\n ${c.success("\u2713")} Telemetry ${c.success("enabled")}\n`); | ||
| } | ||
|
|
||
| function runDisable(): void { | ||
| const config = readConfig(); | ||
| config.telemetryEnabled = false; | ||
| writeConfig(config); | ||
| console.log(`\n ${c.success("\u2713")} Telemetry ${c.bold("disabled")}\n`); | ||
| } | ||
|
|
||
| function runStatus(): void { | ||
| const config = readConfig(); | ||
| const status = config.telemetryEnabled ? c.success("enabled") : c.dim("disabled"); | ||
| console.log(); | ||
| console.log(` ${c.dim("Status:")} ${status}`); | ||
| console.log(` ${c.dim("Config:")} ${c.accent(CONFIG_PATH)}`); | ||
| console.log(` ${c.dim("Commands:")} ${c.bold(String(config.commandCount))}`); | ||
| console.log(); | ||
| console.log(` ${c.dim("Disable:")} ${c.accent("hyperframes telemetry disable")}`); | ||
| console.log(` ${c.dim("Env var:")} ${c.accent("HYPERFRAMES_NO_TELEMETRY=1")}`); | ||
| console.log(); | ||
| } | ||
|
|
||
| export default defineCommand({ | ||
| meta: { name: "telemetry", description: "Manage anonymous usage telemetry" }, | ||
| args: { | ||
| subcommand: { | ||
| type: "positional", | ||
| description: "Subcommand: enable, disable, status", | ||
| required: false, | ||
| }, | ||
| }, | ||
| async run({ args }) { | ||
| const subcommand = args.subcommand; | ||
|
|
||
| if (!subcommand || subcommand === "") { | ||
| console.log(` | ||
| ${c.bold("hyperframes telemetry")} ${c.dim("<subcommand>")} | ||
|
|
||
| Manage anonymous usage data collection. | ||
|
|
||
| ${c.bold("SUBCOMMANDS:")} | ||
| ${c.accent("status")} ${c.dim("Show current telemetry status")} | ||
| ${c.accent("enable")} ${c.dim("Enable anonymous telemetry")} | ||
| ${c.accent("disable")} ${c.dim("Disable anonymous telemetry")} | ||
|
|
||
| ${c.bold("WHAT WE COLLECT:")} | ||
| ${c.dim("\u2022")} Command names (init, render, dev, etc.) | ||
| ${c.dim("\u2022")} Render performance (duration, fps, quality) | ||
| ${c.dim("\u2022")} Template choices | ||
| ${c.dim("\u2022")} OS, architecture, Node.js version, CLI version | ||
|
|
||
| ${c.bold("WHAT WE DON'T COLLECT:")} | ||
| ${c.dim("\u2022")} File paths, project names, or video content | ||
| ${c.dim("\u2022")} IP addresses (discarded by our analytics provider) | ||
| ${c.dim("\u2022")} Any personally identifiable information | ||
|
|
||
| ${c.dim("You can also set")} ${c.accent("HYPERFRAMES_NO_TELEMETRY=1")} ${c.dim("to disable.")} | ||
| `); | ||
| return; | ||
| } | ||
|
|
||
| switch (subcommand) { | ||
| case "enable": | ||
| return runEnable(); | ||
| case "disable": | ||
| return runDisable(); | ||
| case "status": | ||
| return runStatus(); | ||
| default: | ||
| console.error( | ||
| `${c.error("Unknown subcommand:")} ${subcommand}\n\nRun ${c.accent("hyperframes telemetry --help")} for usage.`, | ||
| ); | ||
| process.exit(1); | ||
| } | ||
| }, | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Claude:
trackCommandfires before the command actually runs. If the command fails to import (e.g., missing dependency), we still track it as invoked. This inflates usage numbers. Consider moving the track call to after the command completes, or track it as "command_started" and add a separate "command_completed" event.