great-cto 2.5.7 → 2.5.8
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.
- package/dist/telemetry.js +94 -52
- package/package.json +1 -1
package/dist/telemetry.js
CHANGED
|
@@ -1,27 +1,42 @@
|
|
|
1
1
|
// Anonymous opt-in telemetry.
|
|
2
2
|
//
|
|
3
|
-
// What we send: random install_id (UUID
|
|
4
|
-
// platform, and timestamp. Nothing personal — no email,
|
|
5
|
-
// names. The install_id is generated once and stored in
|
|
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.
|
|
6
7
|
//
|
|
7
8
|
// What we DON'T send: project paths, code, file names, environment variables,
|
|
8
|
-
// shell history,
|
|
9
|
+
// shell history, CLI flag values, exact IP (PostHog discards by default; only
|
|
10
|
+
// country-level geo is retained at ingestion).
|
|
9
11
|
//
|
|
10
12
|
// Opt-out:
|
|
11
13
|
// - GREATCTO_NO_TELEMETRY=1 env var (highest priority)
|
|
14
|
+
// - DO_NOT_TRACK=1 env var (consoledonottrack.com standard)
|
|
12
15
|
// - --no-telemetry CLI flag
|
|
13
16
|
// - User declines the first-run prompt
|
|
14
17
|
// - Manually edit ~/.great_cto/config.json: { "telemetry": false }
|
|
15
18
|
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
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`.
|
|
18
27
|
import * as fs from "node:fs";
|
|
19
28
|
import * as path from "node:path";
|
|
20
29
|
import * as os from "node:os";
|
|
21
30
|
import * as crypto from "node:crypto";
|
|
22
31
|
import { dim, log } from "./ui.js";
|
|
23
|
-
|
|
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/";
|
|
24
36
|
const TELEMETRY_TIMEOUT_MS = 1500;
|
|
37
|
+
function telemetryDisabledByEnv() {
|
|
38
|
+
return process.env.GREATCTO_NO_TELEMETRY === "1" || process.env.DO_NOT_TRACK === "1";
|
|
39
|
+
}
|
|
25
40
|
function configPath() {
|
|
26
41
|
return path.join(os.homedir(), ".great_cto", "config.json");
|
|
27
42
|
}
|
|
@@ -58,7 +73,7 @@ function ensureInstallId(cfg) {
|
|
|
58
73
|
* 4. Default to enabled (true) if non-interactive (e.g. CI), else show notice
|
|
59
74
|
*/
|
|
60
75
|
export function resolveTelemetryConsent(cliFlag) {
|
|
61
|
-
if (
|
|
76
|
+
if (telemetryDisabledByEnv())
|
|
62
77
|
return false;
|
|
63
78
|
if (cliFlag)
|
|
64
79
|
return false;
|
|
@@ -69,11 +84,12 @@ export function resolveTelemetryConsent(cliFlag) {
|
|
|
69
84
|
// show a clear notice with how to disable. Kept short; full details in README.
|
|
70
85
|
log("");
|
|
71
86
|
log(dim("─ Anonymous telemetry ────────────────────────────────"));
|
|
72
|
-
log(dim(" great_cto sends one anonymous ping per install
|
|
73
|
-
log(dim("
|
|
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."));
|
|
74
90
|
log(dim(" No paths, no code, no PII. Disable any time:"));
|
|
75
|
-
log(dim(" great-cto --no-telemetry ·
|
|
76
|
-
log(dim("
|
|
91
|
+
log(dim(" great-cto --no-telemetry · GREATCTO_NO_TELEMETRY=1"));
|
|
92
|
+
log(dim(" DO_NOT_TRACK=1 · ~/.great_cto/config.json"));
|
|
77
93
|
log(dim("──────────────────────────────────────────────────────"));
|
|
78
94
|
log("");
|
|
79
95
|
cfg.telemetry = true;
|
|
@@ -83,38 +99,70 @@ export function resolveTelemetryConsent(cliFlag) {
|
|
|
83
99
|
return true;
|
|
84
100
|
}
|
|
85
101
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
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).
|
|
88
106
|
*/
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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(),
|
|
102
122
|
};
|
|
103
123
|
try {
|
|
104
124
|
const ctrl = new AbortController();
|
|
105
125
|
const timer = setTimeout(() => ctrl.abort(), TELEMETRY_TIMEOUT_MS);
|
|
106
|
-
await fetch(
|
|
126
|
+
await fetch(POSTHOG_ENDPOINT, {
|
|
107
127
|
method: "POST",
|
|
108
|
-
headers: {
|
|
109
|
-
|
|
128
|
+
headers: {
|
|
129
|
+
"Content-Type": "application/json",
|
|
130
|
+
"User-Agent": `great-cto-cli/${opts.cliVersion}`,
|
|
131
|
+
},
|
|
132
|
+
body: JSON.stringify(body),
|
|
110
133
|
signal: ctrl.signal,
|
|
111
134
|
}).catch(() => { });
|
|
112
135
|
clearTimeout(timer);
|
|
113
136
|
}
|
|
114
137
|
catch {
|
|
115
|
-
//
|
|
138
|
+
// best-effort
|
|
116
139
|
}
|
|
117
140
|
}
|
|
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
|
+
}
|
|
118
166
|
/**
|
|
119
167
|
* Subcommand-usage ping. Fire-and-forget. Used to track which v2.4+ commands
|
|
120
168
|
* (ci / mcp / adapt / serve / report / webhook) actually get used in the wild.
|
|
@@ -129,31 +177,25 @@ export async function sendInstallPing(opts) {
|
|
|
129
177
|
* Honours the same opt-out signals as install ping.
|
|
130
178
|
*/
|
|
131
179
|
export async function sendUsagePing(opts) {
|
|
132
|
-
if (
|
|
180
|
+
if (telemetryDisabledByEnv())
|
|
133
181
|
return;
|
|
134
182
|
const cfg = readConfig();
|
|
135
183
|
if (cfg.telemetry === false)
|
|
136
184
|
return;
|
|
137
185
|
if (!cfg.install_id)
|
|
138
186
|
return; // never ping without an established install_id
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}).catch(() => { });
|
|
154
|
-
clearTimeout(timer);
|
|
155
|
-
}
|
|
156
|
-
catch {
|
|
157
|
-
// best-effort
|
|
158
|
-
}
|
|
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
|
+
});
|
|
159
201
|
}
|
package/package.json
CHANGED