@extenshi/cli 0.12.0 → 0.14.0

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.
@@ -71,6 +71,10 @@ function renderFinding(f) {
71
71
  console.log(chalk.dim(` ${chalk.cyan('found:')} ${f.evidence}`));
72
72
  for (const line of wrap(f.recommendation, 84))
73
73
  console.log(` ${chalk.green('→')} ${chalk.green(line)}`);
74
+ // Naming the policy is what lets the developer check the call and argue it
75
+ // with a reviewer; the citation below is the page, this is the rule.
76
+ if (f.policy)
77
+ console.log(chalk.dim(` ${chalk.cyan('policy:')} ${f.policy.title} (${f.policy.id})`));
74
78
  console.log(chalk.dim(` ${f.citation}`));
75
79
  console.log();
76
80
  }
@@ -1 +1 @@
1
- {"version":3,"file":"review-render.js","sourceRoot":"","sources":["../src/review-render.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"review-render.js","sourceRoot":"","sources":["../src/review-render.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB,SAAS,KAAK,CAAC,CAAU;IACxB,QAAQ,CAAC,EAAE,CAAC;QACX,KAAK,UAAU;YACd,OAAO,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAA;QACpD,KAAK,WAAW;YACf,OAAO,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAA;QACtD,KAAK,MAAM;YACV,OAAO,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAA;IAC5D,CAAC;AACF,CAAC;AAED,SAAS,aAAa,CAAC,CAAU;IAChC,QAAQ,CAAC,EAAE,CAAC;QACX,KAAK,UAAU;YACd,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAA;QAC1C,KAAK,WAAW;YACf,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAA;QAC5C,KAAK,MAAM;YACV,OAAO,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAA;IAClD,CAAC;AACF,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,OAAsB;IACxD,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAA;IAE9C,OAAO,CAAC,GAAG,EAAE,CAAA;IACb,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,uDAAuD,CAAC,CAAC,CAAA;QACtF,OAAO,CAAC,GAAG,EAAE,CAAA;QACb,OAAM;IACP,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,IAAI,MAAM,CAAC,QAAQ;QAAE,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAA;IAC9F,IAAI,MAAM,CAAC,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,WAAW,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAAA;IACjG,IAAI,MAAM,CAAC,IAAI;QAAE,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;IAClF,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,oBAAoB,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IAC3E,OAAO,CAAC,GAAG,EAAE,CAAA;IAEb,IAAI,WAAW,GAAiB,EAAE,CAAA;IAClC,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QAC1B,IAAI,CAAC,CAAC,OAAO,KAAK,WAAW,EAAE,CAAC;YAC/B,WAAW,GAAG,CAAC,CAAC,OAAO,CAAA;YACvB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAA;QAC9B,CAAC;QACD,aAAa,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IAED,+DAA+D;IAC/D,IAAI,QAAQ,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CACV,KAAK,CAAC,GAAG,CAAC,IAAI,CACb,yFAAyF,CACzF,CACD,CAAA;IACF,CAAC;SAAM,IAAI,MAAM,CAAC,SAAS,GAAG,CAAC,EAAE,CAAC;QACjC,OAAO,CAAC,GAAG,CACV,KAAK,CAAC,OAAO,CAAC,qFAAqF,CAAC,CACpG,CAAA;IACF,CAAC;SAAM,CAAC;QACP,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,6DAA6D,CAAC,CAAC,CAAA;IACzF,CAAC;IACD,OAAO,CAAC,GAAG,EAAE,CAAA;AACd,CAAC;AAED,SAAS,aAAa,CAAC,CAAgB;IACtC,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;IACvC,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;QAAE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAA;IAC5E,IAAI,CAAC,CAAC,QAAQ;QAAE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAA;IACnF,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,cAAc,EAAE,EAAE,CAAC;QAAE,OAAO,CAAC,GAAG,CAAC,OAAO,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAC1G,2EAA2E;IAC3E,qEAAqE;IACrE,IAAI,CAAC,CAAC,MAAM;QAAE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,KAAK,CAAC,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC,CAAA;IACvG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAA;IAC3C,OAAO,CAAC,GAAG,EAAE,CAAA;AACd,CAAC;AAED,+DAA+D;AAC/D,SAAS,IAAI,CAAC,IAAY,EAAE,KAAa;IACxC,MAAM,GAAG,GAAa,EAAE,CAAA;IACxB,IAAI,IAAI,GAAG,EAAE,CAAA;IACb,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QACtC,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,GAAG,KAAK,EAAE,CAAC;YACnD,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACd,IAAI,GAAG,IAAI,CAAA;QACZ,CAAC;aAAM,CAAC;YACP,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;QACvC,CAAC;IACF,CAAC;IACD,IAAI,IAAI;QAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACxB,OAAO,GAAG,CAAA;AACX,CAAC"}
@@ -8,10 +8,17 @@
8
8
  * → [optional] fetch last-published manifest for --extension-id (review-catalog)
9
9
  * → run checks (review-checks) → render (review-render) or --json
10
10
  *
11
- * Network is only touched for the manifest diff, and only when --extension-id is
12
- * given. With no id, every other check still runs fully offline — no API key
13
- * required. Exit code is 1 only when something WILL BE REJECTED, so it gates a
14
- * publish step in CI without blocking on slow-review / attrition advisories.
11
+ * The build never leaves the machine — every check reads the artifact locally
12
+ * and no API key is required. Two things do go out, neither of them the build:
13
+ * the manifest diff (only with --extension-id, and it sends the id), and the
14
+ * anonymous telemetry every command emits via runCommand (cli.ts), which the
15
+ * first interactive run asks about and which reports nothing before an answer.
16
+ * "Fully offline" is therefore the wrong claim to make in user-facing copy —
17
+ * see docs/src/app/developers/cli/page.mdx "What leaves your machine" and the
18
+ * /cli FAQ, which are worded to match.
19
+ *
20
+ * Exit code is 1 only when something WILL BE REJECTED, so it gates a publish
21
+ * step in CI without blocking on slow-review / attrition advisories.
15
22
  */
16
23
  export interface ReviewRiskOptions {
17
24
  artifactPath: string;
@@ -23,6 +30,12 @@ export interface ReviewRiskOptions {
23
30
  apiKey?: string;
24
31
  /** Store to diff the manifest against (default CHROME — this tool is CWS-centric). */
25
32
  store?: 'CHROME' | 'FIREFOX' | 'EDGE';
33
+ /**
34
+ * Path to a listing draft (`listing.json`). Optional: the listing rules
35
+ * already run against the package's own name/description in every locale.
36
+ * This is what adds the FULL description, which lives only in the dashboard.
37
+ */
38
+ listingPath?: string;
26
39
  outputJson: boolean;
27
40
  }
28
41
  export declare function runReviewRisk(opts: ReviewRiskOptions): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"review-risk.d.ts","sourceRoot":"","sources":["../src/review-risk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAWH,MAAM,WAAW,iBAAiB;IACjC,YAAY,EAAE,MAAM,CAAA;IACpB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAA;IACd,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,sFAAsF;IACtF,KAAK,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAA;IACrC,UAAU,EAAE,OAAO,CAAA;CACnB;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkE1E"}
1
+ {"version":3,"file":"review-risk.d.ts","sourceRoot":"","sources":["../src/review-risk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAYH,MAAM,WAAW,iBAAiB;IACjC,YAAY,EAAE,MAAM,CAAA;IACpB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAA;IACd,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,sFAAsF;IACtF,KAAK,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAA;IACrC;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,OAAO,CAAA;CACnB;AAuDD,wBAAsB,aAAa,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6E1E"}
@@ -8,18 +8,77 @@
8
8
  * → [optional] fetch last-published manifest for --extension-id (review-catalog)
9
9
  * → run checks (review-checks) → render (review-render) or --json
10
10
  *
11
- * Network is only touched for the manifest diff, and only when --extension-id is
12
- * given. With no id, every other check still runs fully offline — no API key
13
- * required. Exit code is 1 only when something WILL BE REJECTED, so it gates a
14
- * publish step in CI without blocking on slow-review / attrition advisories.
11
+ * The build never leaves the machine — every check reads the artifact locally
12
+ * and no API key is required. Two things do go out, neither of them the build:
13
+ * the manifest diff (only with --extension-id, and it sends the id), and the
14
+ * anonymous telemetry every command emits via runCommand (cli.ts), which the
15
+ * first interactive run asks about and which reports nothing before an answer.
16
+ * "Fully offline" is therefore the wrong claim to make in user-facing copy —
17
+ * see docs/src/app/developers/cli/page.mdx "What leaves your machine" and the
18
+ * /cli FAQ, which are worded to match.
19
+ *
20
+ * Exit code is 1 only when something WILL BE REJECTED, so it gates a publish
21
+ * step in CI without blocking on slow-review / attrition advisories.
15
22
  */
16
23
  import fs from 'node:fs';
17
24
  import ora from 'ora';
25
+ import { listingFromArtifact, mergeListings, parseListingFile } from './listing.js';
18
26
  import { loadArtifact } from './review-archive.js';
19
27
  import { fetchPriorManifest } from './review-catalog.js';
20
28
  import { runChecks, summarize } from './review-checks.js';
21
29
  import { renderReviewReport } from './review-render.js';
22
30
  const MAX_SIZE = 50 * 1024 * 1024; // 50 MB, matches `scan`
31
+ /**
32
+ * Assemble the listing to check: the package's localized name/description,
33
+ * overlaid with the draft file when one was passed.
34
+ *
35
+ * A bad `--listing` file THROWS rather than resolving to "no listing": the user
36
+ * pointed at it explicitly, so silently checking nothing would look like the
37
+ * listing passed. A package with no listing text at all resolves to null and
38
+ * the listing rules simply do not run.
39
+ */
40
+ function assembleListing(manifest, files, listingPath) {
41
+ const fromArtifact = listingFromArtifact(manifest, files);
42
+ if (!listingPath)
43
+ return fromArtifact;
44
+ if (!fs.existsSync(listingPath)) {
45
+ throw new Error(`Listing file not found: ${listingPath}`);
46
+ }
47
+ let parsed;
48
+ try {
49
+ parsed = JSON.parse(fs.readFileSync(listingPath, 'utf8'));
50
+ }
51
+ catch (err) {
52
+ throw new Error(`Listing file is not valid JSON: ${err.message}`);
53
+ }
54
+ try {
55
+ return mergeListings(fromArtifact, parseListingFile(parsed));
56
+ }
57
+ catch (err) {
58
+ throw new Error(`${listingPath}: ${err.message}`);
59
+ }
60
+ }
61
+ /**
62
+ * What the listing rules covered, stated plainly.
63
+ *
64
+ * A clean listing section means nothing unless the reader knows WHICH text was
65
+ * read: the package carries the name and short description in every locale, but
66
+ * never the full dashboard description, and that is where keyword spam
67
+ * overwhelmingly lives. Saying so is the difference between "your listing is
68
+ * fine" and "the half of your listing we could see is fine".
69
+ */
70
+ function listingNotes(listing, listingPath) {
71
+ if (!listing) {
72
+ return ['no listing text found in the package — pass --listing <file> to check name and descriptions'];
73
+ }
74
+ const notes = [
75
+ `listing checked in ${listing.locales.length} locale(s): ${listing.locales.map((l) => l.locale).join(', ')}`,
76
+ ];
77
+ if (!listingPath && !listing.locales.some((l) => l.description)) {
78
+ notes.push('the full store description is not in the package — pass --listing <file> with it to check for keyword spam');
79
+ }
80
+ return notes;
81
+ }
23
82
  export async function runReviewRisk(opts) {
24
83
  if (!fs.existsSync(opts.artifactPath)) {
25
84
  throw new Error(`Artifact file not found: ${opts.artifactPath}`);
@@ -57,7 +116,8 @@ export async function runReviewRisk(opts) {
57
116
  diffNote =
58
117
  'pass --extension-id <numeric catalog id> to diff against your last published manifest (auto-disable check)';
59
118
  }
60
- const summary = summarize(runChecks({ manifest, files, priorManifest }));
119
+ const listing = assembleListing(manifest, files, opts.listingPath);
120
+ const summary = summarize(runChecks({ manifest, files, priorManifest, listing }));
61
121
  if (opts.outputJson) {
62
122
  process.stdout.write(`${JSON.stringify({
63
123
  artifact: opts.artifactPath,
@@ -67,6 +127,15 @@ export async function runReviewRisk(opts) {
67
127
  counts: summary.counts,
68
128
  blocking: summary.blocking,
69
129
  findings: summary.findings,
130
+ // What the listing rules actually saw, so a --json consumer can tell
131
+ // "no listing findings" from "no listing was checked".
132
+ listing: listing
133
+ ? {
134
+ source: listing.source,
135
+ defaultLocale: listing.defaultLocale,
136
+ locales: listing.locales.map((l) => l.locale),
137
+ }
138
+ : null,
70
139
  diffSkippedReason: priorManifest ? undefined : diffNote,
71
140
  archiveWarnings: warnings,
72
141
  }, null, 2)}\n`);
@@ -77,6 +146,8 @@ export async function runReviewRisk(opts) {
77
146
  renderReviewReport(summary);
78
147
  if (diffNote)
79
148
  process.stderr.write(`note: ${diffNote}\n`);
149
+ for (const note of listingNotes(listing, opts.listingPath))
150
+ process.stderr.write(`note: ${note}\n`);
80
151
  }
81
152
  // Block a publish pipeline only on a definite rejection.
82
153
  if (summary.blocking)
@@ -1 +1 @@
1
- {"version":3,"file":"review-risk.js","sourceRoot":"","sources":["../src/review-risk.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"review-risk.js","sourceRoot":"","sources":["../src/review-risk.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * First-run telemetry question.
3
+ *
4
+ * Telemetry itself lives in telemetry.ts, which is vendored byte-identical into
5
+ * @extenshi/mcp — the MCP server runs headless and can never prompt, so the
6
+ * asking lives here, in the CLI only, and the answer is persisted to the shared
7
+ * `~/.extenshi/config.json` (`telemetry: true|false`) that both packages read.
8
+ *
9
+ * It is asked ONCE, before the first event of the first interactive run, and
10
+ * never again: `readTelemetryPreference()` returning a boolean is the record of
11
+ * having asked. Everything else is a reason NOT to ask —
12
+ *
13
+ * - not a TTY on both stdin and stderr (piped, redirected, or a daemon):
14
+ * a prompt would hang a build forever;
15
+ * - CI is set: same, and the answer would belong to nobody;
16
+ * - DO_NOT_TRACK / EXTENSHI_TELEMETRY is set: the user has already answered
17
+ * with the environment, and re-asking would be nagging;
18
+ * - EXTENSHI_POSTHOG_KEY is set but empty: the build ships dark, so there is
19
+ * nothing to consent to.
20
+ *
21
+ * Declining is recorded, not just obeyed: a `false` in the config is what makes
22
+ * the decision stick across runs and machines-per-home-dir. When the config
23
+ * cannot be written, the answer is still enforced for the current run through
24
+ * the environment, and the question comes back next time.
25
+ *
26
+ * Fail-soft like the rest of the telemetry path — an unwritable home directory
27
+ * or a closed stdin must never break the command the user actually typed.
28
+ */
29
+ export declare const TELEMETRY_PROMPT_TEXT: string;
30
+ export interface ConsentDeps {
31
+ env: NodeJS.ProcessEnv;
32
+ /** True only when a human can actually see and answer the question. */
33
+ isInteractive: () => boolean;
34
+ readPreference: () => boolean | undefined;
35
+ savePreference: (enabled: boolean) => void;
36
+ /** Resolves to the raw answer, or null when nothing was answered in time. */
37
+ ask: (question: string) => Promise<string | null>;
38
+ log: (message: string) => void;
39
+ }
40
+ /**
41
+ * Which environment variable, if any, has already answered the question.
42
+ *
43
+ * Exported because `extenshi telemetry status` has to name the variable that is
44
+ * overriding the saved answer, and a second copy of this list there would drift
45
+ * from the one that actually suppresses the prompt. Any non-empty value counts
46
+ * as an answer — including `DO_NOT_TRACK=0`, which telemetry.ts reads as falsy
47
+ * and so leaves reporting on. Not asking is still right there: the user has
48
+ * said something about telemetry with the environment, and re-asking would
49
+ * override a deliberate setting with a prompt.
50
+ */
51
+ export declare function envAnswerSource(env: NodeJS.ProcessEnv): 'DO_NOT_TRACK' | 'EXTENSHI_TELEMETRY' | null;
52
+ /** Should the question be put to the user at all? */
53
+ export declare function shouldAskForConsent(deps: ConsentDeps): boolean;
54
+ /**
55
+ * Ask once, persist the answer, and return it. Returns `undefined` when the
56
+ * question was skipped or went unanswered — the caller carries on either way,
57
+ * and an unanswered question is simply asked again on the next interactive run.
58
+ */
59
+ export declare function maybeAskForTelemetryConsent(deps: ConsentDeps): Promise<boolean | undefined>;
60
+ /** The real dependencies: a TTY prompt on stderr so stdout stays pipeable. */
61
+ export declare function defaultConsentDeps(): ConsentDeps;
62
+ //# sourceMappingURL=telemetry-consent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry-consent.d.ts","sourceRoot":"","sources":["../src/telemetry-consent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAYH,eAAO,MAAM,qBAAqB,QAIiC,CAAA;AAEnE,MAAM,WAAW,WAAW;IAC3B,GAAG,EAAE,MAAM,CAAC,UAAU,CAAA;IACtB,uEAAuE;IACvE,aAAa,EAAE,MAAM,OAAO,CAAA;IAC5B,cAAc,EAAE,MAAM,OAAO,GAAG,SAAS,CAAA;IACzC,cAAc,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IAC1C,6EAA6E;IAC7E,GAAG,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACjD,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAC9B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,cAAc,GAAG,oBAAoB,GAAG,IAAI,CAIpG;AAED,qDAAqD;AACrD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAS9D;AAED;;;;GAIG;AACH,wBAAsB,2BAA2B,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CA2CjG;AAED,8EAA8E;AAC9E,wBAAgB,kBAAkB,IAAI,WAAW,CAoBhD"}
@@ -0,0 +1,144 @@
1
+ /**
2
+ * First-run telemetry question.
3
+ *
4
+ * Telemetry itself lives in telemetry.ts, which is vendored byte-identical into
5
+ * @extenshi/mcp — the MCP server runs headless and can never prompt, so the
6
+ * asking lives here, in the CLI only, and the answer is persisted to the shared
7
+ * `~/.extenshi/config.json` (`telemetry: true|false`) that both packages read.
8
+ *
9
+ * It is asked ONCE, before the first event of the first interactive run, and
10
+ * never again: `readTelemetryPreference()` returning a boolean is the record of
11
+ * having asked. Everything else is a reason NOT to ask —
12
+ *
13
+ * - not a TTY on both stdin and stderr (piped, redirected, or a daemon):
14
+ * a prompt would hang a build forever;
15
+ * - CI is set: same, and the answer would belong to nobody;
16
+ * - DO_NOT_TRACK / EXTENSHI_TELEMETRY is set: the user has already answered
17
+ * with the environment, and re-asking would be nagging;
18
+ * - EXTENSHI_POSTHOG_KEY is set but empty: the build ships dark, so there is
19
+ * nothing to consent to.
20
+ *
21
+ * Declining is recorded, not just obeyed: a `false` in the config is what makes
22
+ * the decision stick across runs and machines-per-home-dir. When the config
23
+ * cannot be written, the answer is still enforced for the current run through
24
+ * the environment, and the question comes back next time.
25
+ *
26
+ * Fail-soft like the rest of the telemetry path — an unwritable home directory
27
+ * or a closed stdin must never break the command the user actually typed.
28
+ */
29
+ import * as readline from 'node:readline/promises';
30
+ import { readTelemetryPreference, saveTelemetryPreference } from './config.js';
31
+ /**
32
+ * How long to wait for an answer before giving up and asking again next run.
33
+ * Kept short because this blocks the command the user actually typed: a TTY
34
+ * that nobody is watching (a pty in a script) should not look like a hang.
35
+ */
36
+ const ANSWER_TIMEOUT_MS = 30_000;
37
+ export const TELEMETRY_PROMPT_TEXT = 'Extenshi can report anonymous usage — which command ran, its flag names,\n' +
38
+ 'versions, and a coarse error kind. Never file paths, artifact contents,\n' +
39
+ 'manifest data, or flag values. It helps us fix what actually breaks.\n' +
40
+ 'You can change this any time with `extenshi telemetry on|off`.\n';
41
+ /**
42
+ * Which environment variable, if any, has already answered the question.
43
+ *
44
+ * Exported because `extenshi telemetry status` has to name the variable that is
45
+ * overriding the saved answer, and a second copy of this list there would drift
46
+ * from the one that actually suppresses the prompt. Any non-empty value counts
47
+ * as an answer — including `DO_NOT_TRACK=0`, which telemetry.ts reads as falsy
48
+ * and so leaves reporting on. Not asking is still right there: the user has
49
+ * said something about telemetry with the environment, and re-asking would
50
+ * override a deliberate setting with a prompt.
51
+ */
52
+ export function envAnswerSource(env) {
53
+ if ((env.DO_NOT_TRACK ?? '').trim() !== '')
54
+ return 'DO_NOT_TRACK';
55
+ if ((env.EXTENSHI_TELEMETRY ?? '').trim() !== '')
56
+ return 'EXTENSHI_TELEMETRY';
57
+ return null;
58
+ }
59
+ /** Should the question be put to the user at all? */
60
+ export function shouldAskForConsent(deps) {
61
+ if (deps.readPreference() !== undefined)
62
+ return false;
63
+ if (envAnswerSource(deps.env) !== null)
64
+ return false;
65
+ if ((deps.env.CI ?? '') !== '')
66
+ return false;
67
+ // Set-but-empty means the build ships dark; unset means the embedded key.
68
+ if (deps.env.EXTENSHI_POSTHOG_KEY !== undefined && deps.env.EXTENSHI_POSTHOG_KEY.trim() === '') {
69
+ return false;
70
+ }
71
+ return deps.isInteractive();
72
+ }
73
+ /**
74
+ * Ask once, persist the answer, and return it. Returns `undefined` when the
75
+ * question was skipped or went unanswered — the caller carries on either way,
76
+ * and an unanswered question is simply asked again on the next interactive run.
77
+ */
78
+ export async function maybeAskForTelemetryConsent(deps) {
79
+ try {
80
+ if (!shouldAskForConsent(deps))
81
+ return undefined;
82
+ deps.log(`\n${TELEMETRY_PROMPT_TEXT}`);
83
+ const answer = await deps.ask('Share anonymous usage data? [Y/n] ');
84
+ // No answer at all (timeout, closed stdin) is not consent, and is not a
85
+ // refusal either — record nothing and ask again next time.
86
+ if (answer === null)
87
+ return undefined;
88
+ const normalized = answer.trim().toLowerCase();
89
+ const enabled = !(normalized === 'n' || normalized === 'no');
90
+ // Apply the answer to THIS process before trying to persist it. A decline
91
+ // that cannot be written (read-only $HOME) must still hold for the run
92
+ // that made it — otherwise the user says no, the write fails silently, and
93
+ // the very next captureEvent reports anyway. telemetry.ts reads this
94
+ // variable on its first event, which has not happened yet: runCommand()
95
+ // asks before it captures anything. Accepting needs no variable, since
96
+ // reporting is what happens without one.
97
+ if (!enabled)
98
+ deps.env.EXTENSHI_TELEMETRY = '0';
99
+ let persisted = true;
100
+ try {
101
+ deps.savePreference(enabled);
102
+ }
103
+ catch {
104
+ persisted = false;
105
+ }
106
+ deps.log(enabled
107
+ ? '✓ Thanks — anonymous usage reporting is on. Turn it off any time with `extenshi telemetry off`.\n'
108
+ : '✓ Anonymous usage reporting is off. Nothing but your explicit commands leaves this machine.\n');
109
+ if (!persisted) {
110
+ deps.log('(The choice could not be saved, so this run honours it but the next one will ask again.)\n');
111
+ }
112
+ return enabled;
113
+ }
114
+ catch {
115
+ // Unwritable config, closed stdin, anything at all — never break the
116
+ // command the user actually ran.
117
+ return undefined;
118
+ }
119
+ }
120
+ /** The real dependencies: a TTY prompt on stderr so stdout stays pipeable. */
121
+ export function defaultConsentDeps() {
122
+ return {
123
+ env: process.env,
124
+ isInteractive: () => Boolean(process.stdin.isTTY && process.stderr.isTTY),
125
+ readPreference: readTelemetryPreference,
126
+ savePreference: (enabled) => {
127
+ saveTelemetryPreference(enabled);
128
+ },
129
+ ask: async (question) => {
130
+ const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
131
+ try {
132
+ return await rl.question(question, { signal: AbortSignal.timeout(ANSWER_TIMEOUT_MS) });
133
+ }
134
+ catch {
135
+ return null;
136
+ }
137
+ finally {
138
+ rl.close();
139
+ }
140
+ },
141
+ log: (message) => console.error(message),
142
+ };
143
+ }
144
+ //# sourceMappingURL=telemetry-consent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry-consent.js","sourceRoot":"","sources":["../src/telemetry-consent.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@extenshi/cli",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "Pre-publish security scanner for browser extensions",
5
5
  "type": "module",
6
6
  "publishConfig": {