great-cto 2.5.8 → 2.5.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/telemetry.js +52 -94
  2. package/package.json +1 -1
package/dist/telemetry.js CHANGED
@@ -1,42 +1,27 @@
1
1
  // Anonymous opt-in telemetry.
2
2
  //
3
- // What we send: random install_id (UUID, used as PostHog distinct_id), version,
4
- // archetype, node version, platform, and timestamp. Nothing personal — no email,
5
- // paths, code, or repo names. The install_id is generated once and stored in
6
- // ~/.great_cto/config.json.
3
+ // What we send: random install_id (UUID), version, archetype, node version,
4
+ // platform, and timestamp. Nothing personal — no email, paths, code, or repo
5
+ // names. The install_id is generated once and stored in ~/.great_cto/config.json.
7
6
  //
8
7
  // What we DON'T send: project paths, code, file names, environment variables,
9
- // shell history, CLI flag values, exact IP (PostHog discards by default; only
10
- // country-level geo is retained at ingestion).
8
+ // shell history, IP-derived geolocation (CF only logs country at the edge).
11
9
  //
12
10
  // Opt-out:
13
11
  // - GREATCTO_NO_TELEMETRY=1 env var (highest priority)
14
- // - DO_NOT_TRACK=1 env var (consoledonottrack.com standard)
15
12
  // - --no-telemetry CLI flag
16
13
  // - User declines the first-run prompt
17
14
  // - Manually edit ~/.great_cto/config.json: { "telemetry": false }
18
15
  //
19
- // Backend: PostHog Cloud EU (https://eu.i.posthog.com)
20
- // Project ID: 175527
21
- // Endpoint: POST /i/v0/e/ (single-event capture)
22
- // API key: phc_* — public, write-only ingestion token; safe in OSS code.
23
- //
24
- // We never run telemetry from an `npm postinstall` hook. The install ping fires
25
- // from the first interactive `great-cto init` run instead, so a failed network
26
- // call can never break `npm install` / `npm ci`.
16
+ // Endpoint: https://greatcto.systems/api/install (Cloudflare Worker → D1)
17
+ // Source: workers/telemetry/index.js
27
18
  import * as fs from "node:fs";
28
19
  import * as path from "node:path";
29
20
  import * as os from "node:os";
30
21
  import * as crypto from "node:crypto";
31
22
  import { dim, log } from "./ui.js";
32
- // Public, write-only PostHog ingestion key. Safe to ship in open-source code:
33
- // phc_* keys can only POST events, not read them.
34
- const POSTHOG_API_KEY = "phc_xk55Ce6CMp9ZgjZHVjdGoshxZJk7kSF4WGXKLRUHatGv";
35
- const POSTHOG_ENDPOINT = "https://eu.i.posthog.com/i/v0/e/";
23
+ const TELEMETRY_ENDPOINT = "https://greatcto.systems/api/install";
36
24
  const TELEMETRY_TIMEOUT_MS = 1500;
37
- function telemetryDisabledByEnv() {
38
- return process.env.GREATCTO_NO_TELEMETRY === "1" || process.env.DO_NOT_TRACK === "1";
39
- }
40
25
  function configPath() {
41
26
  return path.join(os.homedir(), ".great_cto", "config.json");
42
27
  }
@@ -73,7 +58,7 @@ function ensureInstallId(cfg) {
73
58
  * 4. Default to enabled (true) if non-interactive (e.g. CI), else show notice
74
59
  */
75
60
  export function resolveTelemetryConsent(cliFlag) {
76
- if (telemetryDisabledByEnv())
61
+ if (process.env.GREATCTO_NO_TELEMETRY === "1")
77
62
  return false;
78
63
  if (cliFlag)
79
64
  return false;
@@ -84,12 +69,11 @@ export function resolveTelemetryConsent(cliFlag) {
84
69
  // show a clear notice with how to disable. Kept short; full details in README.
85
70
  log("");
86
71
  log(dim("─ Anonymous telemetry ────────────────────────────────"));
87
- log(dim(" great_cto sends one anonymous ping per install + one"));
88
- log(dim(" per subcommand to PostHog Cloud EU (eu.posthog.com)."));
89
- log(dim(" Sent: install_id, version, archetype, Node, OS, exit code."));
72
+ log(dim(" great_cto sends one anonymous ping per install:"));
73
+ log(dim(" install_id, version, archetype, Node version, OS."));
90
74
  log(dim(" No paths, no code, no PII. Disable any time:"));
91
- log(dim(" great-cto --no-telemetry · GREATCTO_NO_TELEMETRY=1"));
92
- log(dim(" DO_NOT_TRACK=1 · ~/.great_cto/config.json"));
75
+ log(dim(" great-cto --no-telemetry · or set GREATCTO_NO_TELEMETRY=1"));
76
+ log(dim(" or edit ~/.great_cto/config.json: { \"telemetry\": false }"));
93
77
  log(dim("──────────────────────────────────────────────────────"));
94
78
  log("");
95
79
  cfg.telemetry = true;
@@ -99,70 +83,38 @@ export function resolveTelemetryConsent(cliFlag) {
99
83
  return true;
100
84
  }
101
85
  /**
102
- * POST a single event to PostHog `/i/v0/e/`. Fire-and-forget never throws,
103
- * never blocks longer than TELEMETRY_TIMEOUT_MS.
104
- *
105
- * `distinct_id` is the random install_id (no PII).
86
+ * Best-effort telemetry ping. Non-blocking, fire-and-forget. Never throws.
87
+ * Returns a promise that resolves once the request completes or times out.
106
88
  */
107
- async function postHogCapture(opts) {
108
- const body = {
109
- api_key: POSTHOG_API_KEY,
110
- event: opts.event,
111
- distinct_id: opts.distinct_id,
112
- properties: {
113
- ...opts.properties,
114
- // Standard PostHog meta (these all start with $) — safe, no PII.
115
- $lib: "great-cto-cli",
116
- $lib_version: opts.cliVersion,
117
- // Disable PostHog GeoIP enrichment beyond country (we don't want city-level).
118
- // PostHog respects this server-side.
119
- $ip: null,
120
- },
121
- timestamp: new Date().toISOString(),
89
+ export async function sendInstallPing(opts) {
90
+ if (!opts.consent)
91
+ return;
92
+ const cfg = readConfig();
93
+ const install_id = ensureInstallId(cfg);
94
+ const evt = {
95
+ install_id,
96
+ cli_version: opts.cliVersion,
97
+ archetype: opts.archetype,
98
+ node_version: process.version,
99
+ platform: process.platform,
100
+ arch: process.arch,
101
+ ts: new Date().toISOString(),
122
102
  };
123
103
  try {
124
104
  const ctrl = new AbortController();
125
105
  const timer = setTimeout(() => ctrl.abort(), TELEMETRY_TIMEOUT_MS);
126
- await fetch(POSTHOG_ENDPOINT, {
106
+ await fetch(TELEMETRY_ENDPOINT, {
127
107
  method: "POST",
128
- headers: {
129
- "Content-Type": "application/json",
130
- "User-Agent": `great-cto-cli/${opts.cliVersion}`,
131
- },
132
- body: JSON.stringify(body),
108
+ headers: { "Content-Type": "application/json", "User-Agent": `great-cto-cli/${opts.cliVersion}` },
109
+ body: JSON.stringify(evt),
133
110
  signal: ctrl.signal,
134
111
  }).catch(() => { });
135
112
  clearTimeout(timer);
136
113
  }
137
114
  catch {
138
- // best-effort
115
+ // never block install on telemetry failure
139
116
  }
140
117
  }
141
- /**
142
- * Install / first-run ping. Fired once per `great-cto init` invocation.
143
- * Non-blocking, fire-and-forget. Never throws.
144
- */
145
- export async function sendInstallPing(opts) {
146
- if (!opts.consent)
147
- return;
148
- if (telemetryDisabledByEnv())
149
- return;
150
- const cfg = readConfig();
151
- const install_id = ensureInstallId(cfg);
152
- await postHogCapture({
153
- event: "cli_install",
154
- distinct_id: install_id,
155
- cliVersion: opts.cliVersion,
156
- properties: {
157
- cli_version: opts.cliVersion,
158
- archetype: opts.archetype,
159
- node_version: process.version,
160
- platform: process.platform,
161
- arch: process.arch,
162
- ci: Boolean(process.env.CI),
163
- },
164
- });
165
- }
166
118
  /**
167
119
  * Subcommand-usage ping. Fire-and-forget. Used to track which v2.4+ commands
168
120
  * (ci / mcp / adapt / serve / report / webhook) actually get used in the wild.
@@ -177,25 +129,31 @@ export async function sendInstallPing(opts) {
177
129
  * Honours the same opt-out signals as install ping.
178
130
  */
179
131
  export async function sendUsagePing(opts) {
180
- if (telemetryDisabledByEnv())
132
+ if (process.env.GREATCTO_NO_TELEMETRY === "1")
181
133
  return;
182
134
  const cfg = readConfig();
183
135
  if (cfg.telemetry === false)
184
136
  return;
185
137
  if (!cfg.install_id)
186
138
  return; // never ping without an established install_id
187
- await postHogCapture({
188
- event: "cli_usage",
189
- distinct_id: cfg.install_id,
190
- cliVersion: opts.cliVersion,
191
- properties: {
192
- cli_version: opts.cliVersion,
193
- subcommand: opts.subcommand,
194
- exit_code: opts.exitCode,
195
- node_version: process.version,
196
- platform: process.platform,
197
- arch: process.arch,
198
- ci: Boolean(process.env.CI),
199
- },
200
- });
139
+ try {
140
+ const ctrl = new AbortController();
141
+ const timer = setTimeout(() => ctrl.abort(), TELEMETRY_TIMEOUT_MS);
142
+ await fetch(`${TELEMETRY_ENDPOINT.replace("/install", "/usage")}`, {
143
+ method: "POST",
144
+ headers: { "Content-Type": "application/json", "User-Agent": `great-cto-cli/${opts.cliVersion}` },
145
+ body: JSON.stringify({
146
+ install_id: cfg.install_id,
147
+ cli_version: opts.cliVersion,
148
+ subcommand: opts.subcommand,
149
+ exit_code: opts.exitCode,
150
+ ts: new Date().toISOString(),
151
+ }),
152
+ signal: ctrl.signal,
153
+ }).catch(() => { });
154
+ clearTimeout(timer);
155
+ }
156
+ catch {
157
+ // best-effort
158
+ }
201
159
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "great-cto",
3
- "version": "2.5.8",
3
+ "version": "2.5.9",
4
4
  "description": "One command install for the great_cto Claude Code plugin. Auto-detects your stack, picks the right archetype, bootstraps PROJECT.md.",
5
5
  "keywords": [
6
6
  "claude-code",