create-bestax 4.1.2 → 4.2.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.
package/README.md CHANGED
@@ -151,6 +151,18 @@ A scaffolder runs with write access to your filesystem and picks your starting d
151
151
 
152
152
  Full detail: [`SECURITY.md`](https://github.com/allxsmith/bestax/blob/main/SECURITY.md) · [Security guide](https://bestax.io/docs/guides/security)
153
153
 
154
+ ## Telemetry
155
+
156
+ `create-bestax` can send one **anonymous** usage event after a successful
157
+ scaffold — only if you opt in when asked (once, at the end of a scaffold). The
158
+ event is just the choices you made — template, Bulma flavor, icon library,
159
+ skills, package manager — plus the CLI version, Node major version, and OS
160
+ platform; never names, IPs, machine IDs, paths, or file contents, and no
161
+ identifier exists that could link two events together. Opt out any time with
162
+ `--no-telemetry`, `BESTAX_TELEMETRY=0`, or `DO_NOT_TRACK=1`.
163
+
164
+ Full disclosure of every field and control: [Telemetry guide](https://bestax.io/docs/guides/telemetry)
165
+
154
166
  ## Publishing
155
167
 
156
168
  This package uses semantic-release with scope-based rules. Only commits with `feat(create-bestax)` or `fix(create-bestax)` will trigger releases.
package/dist/cli.d.ts CHANGED
@@ -16,5 +16,5 @@ export declare function copyTemplate(templatePath: string, targetPath: string):
16
16
  export declare function createProject(projectDir?: string, options?: CLIOptions): Promise<void>;
17
17
  export declare function createCLI(): Command;
18
18
  export declare function isMainModule(importMetaUrl: string, argv1: string): boolean;
19
- export declare function runCLI(): void;
19
+ export declare function runCLI(): Promise<void>;
20
20
  //# sourceMappingURL=cli.d.ts.map
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,OAAO,EAAE,cAAc,EAAE,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAIvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC1E,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACtE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGzE,OAAO,EAAE,cAAc,EAAE,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACxB,cAAc,IAAI,cAAc,EAChC,iBAAiB,IAAI,iBAAiB,EACtC,iBAAiB,IAAI,iBAAiB,GACvC,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,aAAa,EACb,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAG1B,wBAAsB,cAAc,CAClC,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGxB;AAED,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAGlB;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGxD;AAED,wBAAsB,YAAY,CAChC,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAGf;AAED,wBAAsB,aAAa,CACjC,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,UAAU,GACnB,OAAO,CAAC,IAAI,CAAC,CAGf;AAcD,wBAAgB,SAAS,IAAI,OAAO,CA0BnC;AAED,wBAAgB,YAAY,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAE1E;AAED,wBAAgB,MAAM,IAAI,IAAI,CAG7B"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,OAAO,EAAE,cAAc,EAAE,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAIvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC1E,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACtE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGzE,OAAO,EAAE,cAAc,EAAE,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACxB,cAAc,IAAI,cAAc,EAChC,iBAAiB,IAAI,iBAAiB,EACtC,iBAAiB,IAAI,iBAAiB,GACvC,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,aAAa,EACb,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAG1B,wBAAsB,cAAc,CAClC,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGxB;AAED,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAGlB;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGxD;AAED,wBAAsB,YAAY,CAChC,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAGf;AAED,wBAAsB,aAAa,CACjC,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,UAAU,GACnB,OAAO,CAAC,IAAI,CAAC,CAGf;AAcD,wBAAgB,SAAS,IAAI,OAAO,CA+BnC;AAED,wBAAgB,YAAY,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAE1E;AAED,wBAAsB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAG5C"}
package/dist/cli.js CHANGED
@@ -59,21 +59,23 @@ export function createCLI() {
59
59
  .option('-i, --icon <library>', 'icon library (none, fontawesome, mdi, ionicons, material-icons, material-symbols)')
60
60
  .option('--skills', 'install the bestax AI skills into .claude/skills')
61
61
  .option('--no-skills', 'do not install the bestax AI skills')
62
+ .option('--telemetry', 'enable anonymous usage telemetry (https://bestax.io/docs/guides/telemetry)')
63
+ .option('--no-telemetry', 'disable anonymous usage telemetry')
62
64
  .option('-y, --yes', 'skip prompts and use defaults or provided options')
63
- .action((projectDir, options) => {
64
- projectCreator.create(projectDir, options);
65
+ .action(async (projectDir, options) => {
66
+ await projectCreator.create(projectDir, options);
65
67
  });
66
68
  return program;
67
69
  }
68
70
  export function isMainModule(importMetaUrl, argv1) {
69
71
  return importMetaUrl === `file://${argv1}`;
70
72
  }
71
- export function runCLI() {
73
+ export async function runCLI() {
72
74
  const program = createCLI();
73
- program.parse();
75
+ await program.parseAsync();
74
76
  }
75
77
  // Only run if this is the main module
76
78
  /* istanbul ignore next */
77
79
  if (isMainModule(import.meta.url, process.argv[1])) {
78
- runCLI();
80
+ await runCLI();
79
81
  }
@@ -23,6 +23,10 @@ export declare const MESSAGES: {
23
23
  readonly NEXT_STEPS: "Next steps:";
24
24
  readonly HAPPY_CODING: "Happy coding! 🎉";
25
25
  readonly SKILLS_ADDED: "✔ Installed bestax AI skills into .claude/skills/ (+ CLAUDE.md, .claude/launch.json)";
26
+ readonly TELEMETRY_NOTICE: string;
27
+ readonly TELEMETRY_ACK_ON: "Thanks! Opt out anytime with --no-telemetry.";
28
+ readonly TELEMETRY_ACK_OFF: "No problem — we won't ask again.";
29
+ readonly TELEMETRY_ACK_UNSAVED: "Couldn't save your choice (config dir not writable) — you may be asked again.";
26
30
  };
27
31
  export declare const LAUNCH_JSON: string;
28
32
  export declare const PROMPTS: {
@@ -31,6 +35,7 @@ export declare const PROMPTS: {
31
35
  readonly SELECT_ICON_LIBRARY: "Would you like to add an icon library?";
32
36
  readonly SELECT_BULMA_FLAVOR: "Which Bulma CSS flavor would you like to use?";
33
37
  readonly INSTALL_SKILLS: "Install the bestax AI skills (.claude/skills) for Claude Code and other agents?";
38
+ readonly TELEMETRY_CONSENT: "Share anonymous usage stats?";
34
39
  };
35
40
  export declare const CONFIG_PROVIDER_ICON_VALUES: Record<string, string>;
36
41
  export interface ClaudeMdOptions {
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,KAAK,CAAC,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,SAAS,EAAE,QAAQ,EAG/B,CAAC;AAEF,eAAO,MAAM,oBAAoB,kBAAkB,CAAC;AACpD,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAC3C,eAAO,MAAM,kBAAkB,QAAsB,CAAC;AAEtD,eAAO,MAAM,QAAQ;;;;;;;wCAaQ,MAAM;uCAEP,MAAM;sCACP,MAAM;4CAEA,MAAM;;;;;CAM7B,CAAC;AASX,eAAO,MAAM,WAAW,QAed,CAAC;AAEX,eAAO,MAAM,OAAO;;;;;;CAOV,CAAC;AAOX,eAAO,MAAM,2BAA2B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAK9D,CAAC;AAKF,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,SAAS,GACpB,aAAa,MAAM,EACnB,8BAA8B,eAAe,KAC5C,MAoIF,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,KAAK,CAAC,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IAIrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,cAAc,EAAE,WAAW,EA6CvC,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,OAAO,KAAK,CAAC,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,eAAO,MAAM,aAAa,EAAE,WAAW,EA4CtC,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,KAAK,CAAC,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,SAAS,EAAE,QAAQ,EAG/B,CAAC;AAEF,eAAO,MAAM,oBAAoB,kBAAkB,CAAC;AACpD,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAC3C,eAAO,MAAM,kBAAkB,QAAsB,CAAC;AAEtD,eAAO,MAAM,QAAQ;;;;;;;wCAaQ,MAAM;uCAEP,MAAM;sCACP,MAAM;4CAEA,MAAM;;;;;;;;;CAiB7B,CAAC;AASX,eAAO,MAAM,WAAW,QAed,CAAC;AAEX,eAAO,MAAM,OAAO;;;;;;;CAQV,CAAC;AAOX,eAAO,MAAM,2BAA2B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAK9D,CAAC;AAKF,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,SAAS,GACpB,aAAa,MAAM,EACnB,8BAA8B,eAAe,KAC5C,MAoIF,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,KAAK,CAAC,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IAIrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,cAAc,EAAE,WAAW,EA6CvC,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,OAAO,KAAK,CAAC,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,eAAO,MAAM,aAAa,EAAE,WAAW,EA4CtC,CAAC"}
package/dist/constants.js CHANGED
@@ -24,6 +24,15 @@ export const MESSAGES = {
24
24
  NEXT_STEPS: 'Next steps:',
25
25
  HAPPY_CODING: 'Happy coding! 🎉',
26
26
  SKILLS_ADDED: '✔ Installed bestax AI skills into .claude/skills/ (+ CLAUDE.md, .claude/launch.json)',
27
+ TELEMETRY_NOTICE: 'Help improve bestax — share anonymous usage stats?\n' +
28
+ 'Sends only the options you chose (template, Bulma flavor, icon library,\n' +
29
+ 'skills, package manager) plus CLI version, Node major, and OS name.\n' +
30
+ 'Never your name, paths, project names, or any personal data.\n' +
31
+ ' Details & opt-out: https://bestax.io/docs/guides/telemetry\n' +
32
+ ' Feedback welcome: https://github.com/allxsmith/bestax/issues',
33
+ TELEMETRY_ACK_ON: 'Thanks! Opt out anytime with --no-telemetry.',
34
+ TELEMETRY_ACK_OFF: "No problem — we won't ask again.",
35
+ TELEMETRY_ACK_UNSAVED: "Couldn't save your choice (config dir not writable) — you may be asked again.",
27
36
  };
28
37
  // Dev-server manifest written into .claude/ with the skills opt-in so Claude
29
38
  // Code's browser preview (`preview_start`) can boot the app by name instead of
@@ -49,6 +58,7 @@ export const PROMPTS = {
49
58
  SELECT_ICON_LIBRARY: 'Would you like to add an icon library?',
50
59
  SELECT_BULMA_FLAVOR: 'Which Bulma CSS flavor would you like to use?',
51
60
  INSTALL_SKILLS: 'Install the bestax AI skills (.claude/skills) for Claude Code and other agents?',
61
+ TELEMETRY_CONSENT: 'Share anonymous usage stats?',
52
62
  };
53
63
  // Icon libraries that require a `ConfigProvider iconLibrary` wrapper, mapped
54
64
  // to the exact prop value the scaffolder writes into main.tsx/jsx ('none' and
@@ -1 +1 @@
1
- {"version":3,"file":"display.d.ts","sourceRoot":"","sources":["../src/display.ts"],"names":[],"mappings":"AAKA,wBAAgB,aAAa,IAAI,IAAI,CAMpC;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CA2BtD;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAElD;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAEjD;AAED,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC"}
1
+ {"version":3,"file":"display.d.ts","sourceRoot":"","sources":["../src/display.ts"],"names":[],"mappings":"AAKA,wBAAgB,aAAa,IAAI,IAAI,CAMpC;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAiCtD;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAElD;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAEjD;AAED,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC"}
package/dist/display.js CHANGED
@@ -30,6 +30,8 @@ export function displaySuccess(targetDir) {
30
30
  console.log(chalk.yellow(`${figures.star} If you enjoy using bestax-bulma, please star us on GitHub!`));
31
31
  console.log(chalk.dim(' https://github.com/allxsmith/bestax'));
32
32
  console.log();
33
+ console.log(chalk.dim(' Ideas or problems? Open an issue: https://github.com/allxsmith/bestax/issues'));
34
+ console.log();
33
35
  }
34
36
  export function displayError(message) {
35
37
  console.error(chalk.red(`${figures.cross} ${message}`));
package/dist/index.js CHANGED
@@ -15,6 +15,5 @@ if (majorVersion < MINIMUM_NODE_VERSION) {
15
15
  process.exit(1);
16
16
  }
17
17
  const { createCLI } = await import('./cli.js');
18
- const program = createCLI();
19
- program.parse();
18
+ await createCLI().parseAsync(process.argv);
20
19
  export {};
@@ -1,4 +1,5 @@
1
1
  export type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun';
2
+ export declare const KNOWN_PACKAGE_MANAGERS: readonly ["npm", "pnpm", "yarn", "bun"];
2
3
  /** Detect the package manager that invoked the CLI from npm_config_user_agent
3
4
  * (e.g. "pnpm/9.12.0 npm/? node/v22 darwin arm64"). Falls back to npm. */
4
5
  export declare function detectPackageManager(userAgent?: string | undefined): PackageManager;
@@ -1 +1 @@
1
- {"version":3,"file":"package-manager.d.ts","sourceRoot":"","sources":["../src/package-manager.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;AAI7D;2EAC2E;AAC3E,wBAAgB,oBAAoB,CAClC,SAAS,GAAE,MAAM,GAAG,SAA6C,GAChE,cAAc,CAKhB"}
1
+ {"version":3,"file":"package-manager.d.ts","sourceRoot":"","sources":["../src/package-manager.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;AAI7D,eAAO,MAAM,sBAAsB,yCAA0C,CAAC;AAG9E;2EAC2E;AAC3E,wBAAgB,oBAAoB,CAClC,SAAS,GAAE,MAAM,GAAG,SAA6C,GAChE,cAAc,CAKhB"}
@@ -1,4 +1,7 @@
1
- const KNOWN = ['npm', 'pnpm', 'yarn', 'bun'];
1
+ // Exported so check:conformance can compare it against the telemetry
2
+ // worker's allowlist by importing it, rather than regex-scraping the source.
3
+ export const KNOWN_PACKAGE_MANAGERS = ['npm', 'pnpm', 'yarn', 'bun'];
4
+ const KNOWN = KNOWN_PACKAGE_MANAGERS;
2
5
  /** Detect the package manager that invoked the CLI from npm_config_user_agent
3
6
  * (e.g. "pnpm/9.12.0 npm/? node/v22 darwin arm64"). Falls back to npm. */
4
7
  export function detectPackageManager(userAgent = process.env.npm_config_user_agent) {
@@ -11,6 +11,7 @@ export interface CLIOptions {
11
11
  bulma?: string;
12
12
  icon?: string;
13
13
  skills?: boolean;
14
+ telemetry?: boolean;
14
15
  yes?: boolean;
15
16
  }
16
17
  export declare class ProjectCreator {
@@ -28,5 +29,15 @@ export declare class ProjectCreator {
28
29
  setupIconLibrary(targetPath: string, iconLibrary: string, template: string): Promise<void>;
29
30
  setupConfigProvider(targetPath: string, bulmaFlavor: string, iconLibrary: string, template: string): Promise<void>;
30
31
  create(projectDir?: string, options?: CLIOptions): Promise<void>;
32
+ /**
33
+ * Consent + beacon after a successful scaffold. Only success events are
34
+ * reported, the beacon is awaited (so no process.exit(1) site can kill an
35
+ * in-flight request), and nothing in here may throw or fail the scaffold.
36
+ * The prompt is skipped for -y, an explicit flag, DO_NOT_TRACK, or a
37
+ * missing TTY on EITHER end — with stdout redirected the question would
38
+ * render into the log while the CLI blocked on an invisible prompt
39
+ * (#192: the non-interactive path must never hang).
40
+ */
41
+ private handleTelemetry;
31
42
  }
32
43
  //# sourceMappingURL=project-creator.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"project-creator.d.ts","sourceRoot":"","sources":["../src/project-creator.ts"],"names":[],"mappings":"AA2BA,OAAO,EAOL,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AAWxB,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,YAAY,CAAS;gBAEjB,YAAY,CAAC,EAAE,MAAM;IAI3B,cAAc,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAa3D,sBAAsB,CAC1B,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC;IAkBnB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAInC,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMjE,oBAAoB,CACxB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC;IAgBV,WAAW,CACf,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,IAAI,CAAC;IAqBV,gBAAgB,CACpB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAmDhB,OAAO,CAAC,WAAW;IA2BnB,OAAO,CAAC,YAAY;IAYd,gBAAgB,CACpB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAgJV,mBAAmB,CACvB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAmEV,MAAM,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;CAyIvE"}
1
+ {"version":3,"file":"project-creator.d.ts","sourceRoot":"","sources":["../src/project-creator.ts"],"names":[],"mappings":"AA6BA,OAAO,EAOL,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AAWxB,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,YAAY,CAAS;gBAEjB,YAAY,CAAC,EAAE,MAAM;IAI3B,cAAc,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAa3D,sBAAsB,CAC1B,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC;IAkBnB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAInC,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMjE,oBAAoB,CACxB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC;IAgBV,WAAW,CACf,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,IAAI,CAAC;IAqBV,gBAAgB,CACpB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAmDhB,OAAO,CAAC,WAAW;IA2BnB,OAAO,CAAC,YAAY;IAYd,gBAAgB,CACpB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAgJV,mBAAmB,CACvB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAmEV,MAAM,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAgJtE;;;;;;;;OAQG;YACW,eAAe;CA2B9B"}
@@ -3,7 +3,8 @@ import { fileURLToPath } from 'url';
3
3
  import chalk from 'chalk';
4
4
  import fs from 'fs-extra';
5
5
  import { checkDirectoryExists, isDirectoryEmpty, emptyDirectory, ensureDirectory, copyDirectory, updatePackageJson, } from './file-system.js';
6
- import { promptProjectName, promptOverwriteDirectory, promptTemplate, promptIconLibrary, promptBulmaFlavor, promptInstallSkills, } from './prompts.js';
6
+ import { promptProjectName, promptOverwriteDirectory, promptTemplate, promptIconLibrary, promptBulmaFlavor, promptInstallSkills, promptTelemetryConsent, } from './prompts.js';
7
+ import { reportScaffold } from './telemetry.js';
7
8
  import { displayHeader, displaySuccess, displayError, displayCancelled, } from './display.js';
8
9
  import { validateProjectName } from './validators.js';
9
10
  import { MESSAGES, ICON_LIBRARIES, BULMA_FLAVORS, CLAUDE_MD, LAUNCH_JSON, CONFIG_PROVIDER_ICON_VALUES, } from './constants.js';
@@ -421,10 +422,51 @@ export class ProjectCreator {
421
422
  });
422
423
  }
423
424
  displaySuccess(targetDir);
425
+ await this.handleTelemetry(options, {
426
+ template,
427
+ bulmaFlavor,
428
+ iconLibrary,
429
+ skills: installSkills,
430
+ });
424
431
  }
425
432
  catch (error) {
426
433
  displayError(error.message);
427
434
  process.exit(1);
428
435
  }
429
436
  }
437
+ /**
438
+ * Consent + beacon after a successful scaffold. Only success events are
439
+ * reported, the beacon is awaited (so no process.exit(1) site can kill an
440
+ * in-flight request), and nothing in here may throw or fail the scaffold.
441
+ * The prompt is skipped for -y, an explicit flag, DO_NOT_TRACK, or a
442
+ * missing TTY on EITHER end — with stdout redirected the question would
443
+ * render into the log while the CLI blocked on an invisible prompt
444
+ * (#192: the non-interactive path must never hang).
445
+ */
446
+ async handleTelemetry(options, choices) {
447
+ try {
448
+ await reportScaffold(choices, options?.telemetry, {
449
+ interactive: process.stdin.isTTY === true &&
450
+ process.stdout.isTTY === true &&
451
+ options?.yes !== true,
452
+ promptConsent: promptTelemetryConsent,
453
+ onDecided: (enabled, persisted) => {
454
+ // Worded on whether the write actually stuck: promising "we won't
455
+ // ask again" after a swallowed write failure would be false.
456
+ if (!persisted) {
457
+ console.log(chalk.dim(MESSAGES.TELEMETRY_ACK_UNSAVED));
458
+ }
459
+ else if (enabled) {
460
+ console.log(chalk.dim(MESSAGES.TELEMETRY_ACK_ON));
461
+ }
462
+ else {
463
+ console.log(chalk.dim(MESSAGES.TELEMETRY_ACK_OFF));
464
+ }
465
+ },
466
+ });
467
+ }
468
+ catch {
469
+ // Telemetry must never affect the scaffold's outcome.
470
+ }
471
+ }
430
472
  }
package/dist/prompts.d.ts CHANGED
@@ -3,5 +3,13 @@ export declare function promptOverwriteDirectory(targetDir: string): Promise<boo
3
3
  export declare function promptInstallSkills(): Promise<boolean>;
4
4
  export declare function promptTemplate(): Promise<string | null>;
5
5
  export declare function promptIconLibrary(): Promise<string | null>;
6
+ /**
7
+ * Consent for anonymous usage telemetry. Deliberately does NOT call
8
+ * ensureInteractive(): consent is optional, so without a TTY the caller skips
9
+ * the question instead of failing the run (#192). Returns null when the user
10
+ * cancels (Ctrl-C) — a cancel is not an answer and nothing is persisted, but
11
+ * the interrupt is honored via exit code 130 rather than swallowed.
12
+ */
13
+ export declare function promptTelemetryConsent(): Promise<boolean | null>;
6
14
  export declare function promptBulmaFlavor(): Promise<string | null>;
7
15
  //# sourceMappingURL=prompts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAwBA,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAWhE;AAED,wBAAsB,wBAAwB,CAC5C,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAUlB;AAED,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,OAAO,CAAC,CAU5D;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAa7D;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAchE;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAgBhE"}
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAyBA,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAWhE;AAED,wBAAsB,wBAAwB,CAC5C,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAUlB;AAED,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,OAAO,CAAC,CAU5D;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAa7D;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAchE;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CA2BtE;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAgBhE"}
package/dist/prompts.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import prompts from 'prompts';
2
+ import chalk from 'chalk';
2
3
  import { TEMPLATES, DEFAULT_PROJECT_NAME, PROMPTS as PROMPT_MESSAGES, MESSAGES, ICON_LIBRARIES, BULMA_FLAVORS, } from './constants.js';
3
4
  import { validateProjectName } from './validators.js';
4
5
  import { displayError } from './display.js';
@@ -71,6 +72,37 @@ export async function promptIconLibrary() {
71
72
  });
72
73
  return response.iconLibrary || null;
73
74
  }
75
+ /**
76
+ * Consent for anonymous usage telemetry. Deliberately does NOT call
77
+ * ensureInteractive(): consent is optional, so without a TTY the caller skips
78
+ * the question instead of failing the run (#192). Returns null when the user
79
+ * cancels (Ctrl-C) — a cancel is not an answer and nothing is persisted, but
80
+ * the interrupt is honored via exit code 130 rather than swallowed.
81
+ */
82
+ export async function promptTelemetryConsent() {
83
+ console.log();
84
+ console.log(chalk.gray(MESSAGES.TELEMETRY_NOTICE));
85
+ let cancelled = false;
86
+ const response = await prompts({
87
+ type: 'confirm',
88
+ name: 'telemetry',
89
+ message: PROMPT_MESSAGES.TELEMETRY_CONSENT,
90
+ initial: false,
91
+ }, {
92
+ onCancel: () => {
93
+ cancelled = true;
94
+ },
95
+ });
96
+ if (cancelled) {
97
+ // A cancel is not an answer, but it IS an interrupt: without this, a ^C
98
+ // at the question would be swallowed and the run would exit 0 as if it
99
+ // had never been interrupted. 130 = 128 + SIGINT, the shell convention.
100
+ // The scaffold itself already succeeded, so no abrupt process.exit here.
101
+ process.exitCode = 130;
102
+ return null;
103
+ }
104
+ return typeof response.telemetry === 'boolean' ? response.telemetry : null;
105
+ }
74
106
  export async function promptBulmaFlavor() {
75
107
  ensureInteractive();
76
108
  const response = await prompts({
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Shared CLI telemetry kernel (disclosure: https://bestax.io/docs/guides/telemetry).
3
+ *
4
+ * Copied byte-for-byte into create-bestax and bestax-migrate — both publish
5
+ * standalone, so this cannot be a workspace package. `check:conformance
6
+ * --only=telemetry-core` fails if the copies diverge. Edit this file in
7
+ * create-bestax and copy it over the migrate one.
8
+ *
9
+ * No identifier of any kind is generated. Undecided means off. Every failure
10
+ * here is silent: telemetry must never affect the CLI's outcome.
11
+ */
12
+ export type TelemetryDecision = 'on' | 'off' | 'undecided';
13
+ export type TelemetrySource = 'flag' | 'dnt' | 'env' | 'config' | 'default';
14
+ export interface ResolvedTelemetry {
15
+ decision: TelemetryDecision;
16
+ source: TelemetrySource;
17
+ }
18
+ /** Shared across the bestax CLI family so consent is asked at most once. */
19
+ export declare function telemetryConfigPath(): string;
20
+ /**
21
+ * Precedence: flag (persisted by the caller) → DO_NOT_TRACK → BESTAX_TELEMETRY
22
+ * (per-run, never persisted) → config file → undecided (off).
23
+ */
24
+ export declare function resolveTelemetry(flag?: boolean): Promise<ResolvedTelemetry>;
25
+ /**
26
+ * Returns whether the decision was actually written: callers word their
27
+ * acknowledgement on it, because promising "we won't ask again" after a
28
+ * swallowed write failure would be false on every future run.
29
+ */
30
+ export declare function persistTelemetryDecision(enabled: boolean, decidedBy: string): Promise<boolean>;
31
+ export declare function sendTelemetry(payload: unknown): Promise<void>;
32
+ export declare function getToolVersion(): string;
33
+ export interface ReportRunOptions {
34
+ /** True only when a consent question could actually be answered. */
35
+ interactive: boolean;
36
+ /** Owns the consent UI up to the answer (notice, question); null = cancelled. */
37
+ promptConsent: () => Promise<boolean | null>;
38
+ /**
39
+ * Called after a prompted answer has been (attempted to be) saved, so the
40
+ * acknowledgement can tell the truth about whether it will stick.
41
+ */
42
+ onDecided?: (enabled: boolean, persisted: boolean) => void;
43
+ }
44
+ export declare function reportRun(flag: boolean | undefined, toolName: string, options: ReportRunOptions, payload: unknown): Promise<void>;
45
+ //# sourceMappingURL=telemetry-core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry-core.d.ts","sourceRoot":"","sources":["../src/telemetry-core.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;GAUG;AAEH,MAAM,MAAM,iBAAiB,GAAG,IAAI,GAAG,KAAK,GAAG,WAAW,CAAC;AAE3D,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE5E,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,MAAM,EAAE,eAAe,CAAC;CACzB;AAYD,4EAA4E;AAC5E,wBAAgB,mBAAmB,IAAI,MAAM,CAG5C;AA6BD;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,CAAC,EAAE,OAAO,GACb,OAAO,CAAC,iBAAiB,CAAC,CAyB5B;AAED;;;;GAIG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAgBlB;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAWnE;AAED,wBAAgB,cAAc,IAAI,MAAM,CAUvC;AAED,MAAM,WAAW,gBAAgB;IAC/B,oEAAoE;IACpE,WAAW,EAAE,OAAO,CAAC;IACrB,iFAAiF;IACjF,aAAa,EAAE,MAAM,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IAC7C;;;OAGG;IACH,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;CAC5D;AAED,wBAAsB,SAAS,CAC7B,IAAI,EAAE,OAAO,GAAG,SAAS,EACzB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,gBAAgB,EACzB,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,IAAI,CAAC,CAqCf"}
@@ -0,0 +1,141 @@
1
+ import { mkdir, readFile, writeFile } from 'fs/promises';
2
+ import { homedir } from 'os';
3
+ import { dirname, join } from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { readFileSync } from 'fs';
6
+ const DEFAULT_ENDPOINT = 'https://bestax.io/api/t';
7
+ const TIMEOUT_MS = 1500;
8
+ /** Shared across the bestax CLI family so consent is asked at most once. */
9
+ export function telemetryConfigPath() {
10
+ const base = process.env.XDG_CONFIG_HOME || join(homedir(), '.config');
11
+ return join(base, 'bestax', 'telemetry.json');
12
+ }
13
+ async function readConfig() {
14
+ try {
15
+ const parsed = JSON.parse(await readFile(telemetryConfigPath(), 'utf-8'));
16
+ // Consent fails closed: only a well-formed v1 record is trusted. A bare
17
+ // {"enabled":true}, an array, or an unknown future schema version reads
18
+ // as "never asked" (prompt again) — never as an opt-in.
19
+ if (parsed !== null &&
20
+ typeof parsed === 'object' &&
21
+ !Array.isArray(parsed) &&
22
+ parsed.version === 1) {
23
+ return parsed;
24
+ }
25
+ }
26
+ catch {
27
+ // Missing or corrupt config reads as "never asked".
28
+ }
29
+ return null;
30
+ }
31
+ function doNotTrack() {
32
+ const dnt = process.env.DO_NOT_TRACK;
33
+ return dnt !== undefined && dnt !== '' && dnt !== '0';
34
+ }
35
+ /**
36
+ * Precedence: flag (persisted by the caller) → DO_NOT_TRACK → BESTAX_TELEMETRY
37
+ * (per-run, never persisted) → config file → undecided (off).
38
+ */
39
+ export async function resolveTelemetry(flag) {
40
+ if (flag !== undefined) {
41
+ return {
42
+ decision: flag ? 'on' : 'off',
43
+ source: 'flag',
44
+ };
45
+ }
46
+ if (doNotTrack()) {
47
+ return { decision: 'off', source: 'dnt' };
48
+ }
49
+ const env = process.env.BESTAX_TELEMETRY;
50
+ if (env === '0' || env === '1') {
51
+ return {
52
+ decision: env === '1' ? 'on' : 'off',
53
+ source: 'env',
54
+ };
55
+ }
56
+ const config = await readConfig();
57
+ if (config && typeof config.enabled === 'boolean') {
58
+ return {
59
+ decision: config.enabled ? 'on' : 'off',
60
+ source: 'config',
61
+ };
62
+ }
63
+ return { decision: 'undecided', source: 'default' };
64
+ }
65
+ /**
66
+ * Returns whether the decision was actually written: callers word their
67
+ * acknowledgement on it, because promising "we won't ask again" after a
68
+ * swallowed write failure would be false on every future run.
69
+ */
70
+ export async function persistTelemetryDecision(enabled, decidedBy) {
71
+ try {
72
+ const config = {
73
+ version: 1,
74
+ enabled,
75
+ decidedAt: new Date().toISOString(),
76
+ decidedBy,
77
+ };
78
+ const filePath = telemetryConfigPath();
79
+ await mkdir(dirname(filePath), { recursive: true });
80
+ await writeFile(filePath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
81
+ return true;
82
+ }
83
+ catch {
84
+ // An unwritable config dir must not break the CLI.
85
+ return false;
86
+ }
87
+ }
88
+ export async function sendTelemetry(payload) {
89
+ try {
90
+ await fetch(process.env.BESTAX_TELEMETRY_ENDPOINT || DEFAULT_ENDPOINT, {
91
+ method: 'POST',
92
+ headers: { 'content-type': 'application/json' },
93
+ body: JSON.stringify(payload),
94
+ signal: AbortSignal.timeout(TIMEOUT_MS),
95
+ });
96
+ }
97
+ catch {
98
+ // Fire-and-forget: the response (or its absence) is never surfaced.
99
+ }
100
+ }
101
+ export function getToolVersion() {
102
+ try {
103
+ const dir = dirname(fileURLToPath(import.meta.url));
104
+ const packageJson = JSON.parse(readFileSync(join(dir, '..', 'package.json'), 'utf-8'));
105
+ return packageJson.version || '0.0.0';
106
+ }
107
+ catch {
108
+ return '0.0.0';
109
+ }
110
+ }
111
+ export async function reportRun(flag, toolName, options, payload) {
112
+ const resolved = await resolveTelemetry(flag);
113
+ let decision = resolved.decision;
114
+ if (resolved.source === 'flag') {
115
+ // Flags are the scripted equivalent of the prompt: decided once,
116
+ // remembered — EXCEPT under DO_NOT_TRACK, where the flag still applies to
117
+ // this one run (an explicit ask on the command line wins) but is never
118
+ // written down, so a copied command containing --telemetry cannot enable
119
+ // telemetry beyond the run it was typed for.
120
+ if (!doNotTrack()) {
121
+ const persisted = await persistTelemetryDecision(decision === 'on', `${toolName}@${getToolVersion()}`);
122
+ // A silently unsaved flag is worse than an unsaved answer: with an
123
+ // opposite value already in the shared config, --no-telemetry would
124
+ // apply to this run only and the next family run would re-enable.
125
+ if (!persisted) {
126
+ options.onDecided?.(decision === 'on', false);
127
+ }
128
+ }
129
+ }
130
+ else if (decision === 'undecided' && options.interactive) {
131
+ const answer = await options.promptConsent();
132
+ if (answer !== null) {
133
+ const persisted = await persistTelemetryDecision(answer, `${toolName}@${getToolVersion()}`);
134
+ decision = answer ? 'on' : 'off';
135
+ options.onDecided?.(answer, persisted);
136
+ }
137
+ }
138
+ if (decision === 'on') {
139
+ await sendTelemetry(payload);
140
+ }
141
+ }
@@ -0,0 +1,31 @@
1
+ import type { ReportRunOptions } from './telemetry-core.js';
2
+ /**
3
+ * create-bestax payload + reporter. The kernel lives in telemetry-core.ts
4
+ * (copied into bestax-migrate; see that file). Envelope and props are closed
5
+ * enums, a version string, or a bounded integer — never paths, project names,
6
+ * or free text.
7
+ */
8
+ export interface ScaffoldChoices {
9
+ template: string;
10
+ bulmaFlavor: string;
11
+ iconLibrary: string;
12
+ skills: boolean;
13
+ }
14
+ export interface ScaffoldPayload {
15
+ v: 1;
16
+ tool: 'create-bestax';
17
+ event: 'scaffold';
18
+ toolVersion: string;
19
+ nodeMajor: number;
20
+ platform: string;
21
+ props: {
22
+ template: string;
23
+ bulmaFlavor: string;
24
+ iconLibrary: string;
25
+ skills: boolean;
26
+ packageManager: string;
27
+ };
28
+ }
29
+ export declare function buildScaffoldPayload(choices: ScaffoldChoices): ScaffoldPayload;
30
+ export declare function reportScaffold(choices: ScaffoldChoices, flag: boolean | undefined, options: ReportRunOptions): Promise<void>;
31
+ //# sourceMappingURL=telemetry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D;;;;;GAKG;AAEH,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,CAAC,EAAE,CAAC,CAAC;IACL,IAAI,EAAE,eAAe,CAAC;IACtB,KAAK,EAAE,UAAU,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE;QACL,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,OAAO,CAAC;QAChB,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;CACH;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,eAAe,GACvB,eAAe,CAgBjB;AAED,wBAAsB,cAAc,CAClC,OAAO,EAAE,eAAe,EACxB,IAAI,EAAE,OAAO,GAAG,SAAS,EACzB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,IAAI,CAAC,CAOf"}
@@ -0,0 +1,22 @@
1
+ import { detectPackageManager } from './package-manager.js';
2
+ import { getToolVersion, reportRun } from './telemetry-core.js';
3
+ export function buildScaffoldPayload(choices) {
4
+ return {
5
+ v: 1,
6
+ tool: 'create-bestax',
7
+ event: 'scaffold',
8
+ toolVersion: getToolVersion(),
9
+ nodeMajor: Number(process.versions.node.split('.')[0]),
10
+ platform: process.platform,
11
+ props: {
12
+ template: choices.template,
13
+ bulmaFlavor: choices.bulmaFlavor,
14
+ iconLibrary: choices.iconLibrary,
15
+ skills: choices.skills,
16
+ packageManager: detectPackageManager(),
17
+ },
18
+ };
19
+ }
20
+ export async function reportScaffold(choices, flag, options) {
21
+ await reportRun(flag, 'create-bestax', options, buildScaffoldPayload(choices));
22
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-bestax",
3
- "version": "4.1.2",
3
+ "version": "4.2.0",
4
4
  "description": "Create a new bestax-bulma project",
5
5
  "type": "module",
6
6
  "bin": {
@@ -74,5 +74,5 @@
74
74
  "clean": "rimraf dist",
75
75
  "release": "npx semantic-release"
76
76
  },
77
- "readme": "# create-bestax\n\n[![npm version](https://img.shields.io/npm/v/create-bestax.svg)](https://www.npmjs.com/package/create-bestax)\n[![npm downloads](https://img.shields.io/npm/dm/create-bestax.svg)](https://www.npmjs.com/package/create-bestax)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Socket Badge](https://socket.dev/api/badge/npm/package/create-bestax)](https://socket.dev/npm/package/create-bestax/overview)\n[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/allxsmith/bestax/badge)](https://scorecard.dev/viewer/?uri=github.com/allxsmith/bestax)\n[![npm provenance](https://img.shields.io/badge/npm-provenance-3fb950.svg)](https://www.npmjs.com/package/create-bestax#provenance)\n[![Security policy](https://img.shields.io/badge/security-policy-blue.svg)](https://github.com/allxsmith/bestax/blob/main/SECURITY.md)\n\nThe scaffolder for [`@allxsmith/bestax-bulma`](https://www.npmjs.com/package/@allxsmith/bestax-bulma) — spin up a Vite app pre-wired for the **Bulma v1** React component library in one command. Picks your framework (JS or TypeScript), CSS flavor, and icon library, and can drop in the bestax **AI skills** so an agent like Claude Code knows the library from the first prompt.\n\nPart of the [bestax monorepo](https://github.com/allxsmith/bestax) — see also [`@allxsmith/bestax-bulma`](https://www.npmjs.com/package/@allxsmith/bestax-bulma) for the components themselves.\n\n## Requirements\n\n- **Node.js 22 or newer.** Node 18 and 20 are both past end-of-life. On an older runtime the\n CLI exits immediately with an explicit upgrade message.\n- npm 10 or newer (ships with Node 22), or yarn/pnpm.\n\n## Usage\n\n### Quick Start\n\n```bash\n# Using npm\nnpm create bestax@latest my-app\n\n# Using npx\nnpx create-bestax@latest my-app\n\n# Alternative naming\nnpm create bestax-bulma@latest my-app\n```\n\n### Interactive Mode\n\nRunning without arguments will prompt you for all options:\n\n```bash\nnpm create bestax@latest\n```\n\nYou'll be asked to:\n\n1. Enter a project name\n2. Select a framework (Vite or Vite + TypeScript)\n3. Choose a Bulma CSS flavor (Complete or Minimal)\n4. Select an icon library (Font Awesome, Material Icons, etc.)\n5. Choose whether to install the bestax AI skills into `.claude/skills/`\n\nEvery prompt has a flag equivalent, so the whole flow is scriptable and CI-friendly — see the options below.\n\n### Command Line Options\n\n```bash\nnpm create bestax@latest [project-directory] [options]\n```\n\n**Arguments:**\n\n- `[project-directory]` - Optional project directory name\n\n**Options:**\n\n- `-t, --template <template>` - Template to use: `vite` or `vite-ts` \n **Default:** `vite`\n- `-b, --bulma <flavor>` - Bulma CSS flavor:\n - `complete` - Full Bulma CSS with all components and helpers\n - `prefixed` - Prefixed version for compatibility\n - `no-helpers` - Without helper classes\n - `no-helpers-prefixed` - Prefixed without helpers\n - `no-dark-mode` - Without dark mode support \n **Default:** `complete`\n- `-i, --icon <library>` - Icon library:\n - `none` - No icon library\n - `fontawesome` - Font Awesome\n - `mdi` - Material Design Icons\n - `ionicons` - Ionicons\n - `material-icons` - Material Icons\n - `material-symbols` - Material Symbols \n **Default:** `none`\n- `--skills` / `--no-skills` - Install (or skip) the bestax AI skills into\n `.claude/skills/` (with a `CLAUDE.md`) so a Claude Code session picks them up\n automatically. When omitted you're prompted; with `-y` the default is to install.\n- `-y, --yes` - Skip prompts and use defaults or provided options. \n When used, the following defaults are selected unless overridden by flags:\n - Template: `vite`\n - Bulma flavor: `complete`\n - Icon library: `none`\n - AI skills: installed (pass `--no-skills` to opt out)\n\n**Example:**\n\n```bash\n# Create a TypeScript project with Font Awesome icons\nnpm create bestax@latest my-app -t vite-ts -i fontawesome -b complete\n\n# Use all defaults (skip prompts)\nnpm create bestax@latest my-app -y\n```\n\n## Templates\n\nAvailable templates:\n\n- `vite` - Vite + JavaScript\n- `vite-ts` - Vite + TypeScript\n\nEach template includes:\n\n- Pre-configured bestax-bulma integration (React 19)\n- The latest `@allxsmith/bestax-bulma`, which ships Bulma v1 automatically\n- Icon library support (Font Awesome, Material Design, etc.)\n- Sample components demonstrating library usage\n- Development and build scripts\n\n## For AI Tools\n\nScaffolding for an AI agent? Pass `--skills` (or accept the prompt) to drop the bestax\nAgent Skills and a `CLAUDE.md` into the new project so Claude Code, Cursor, or Copilot\nbuild the bestax way from the start. The library also ships LLM-optimized docs:\n\n- [LLMs guide](https://bestax.io/docs/guides/llms) — using bestax with Claude Code, Cursor, Copilot\n- [Agent Skills](https://bestax.io/docs/skills/intro) — teach your agent the bestax conventions\n- [llms.txt](https://bestax.io/llms.txt) (curated index) · [llms-full.txt](https://bestax.io/llms-full.txt) (full docs)\n\n## Development\n\n```bash\nnpm run build # Build the CLI\nnpm run dev # Watch mode\nnpm test # Run tests\nnpm run lint # Lint CLI source code\nnpm run typecheck # Type check CLI source code\n```\n\n**Note on Templates:** Template files in `templates/` are excluded from linting. They should be manually validated by scaffolding a test project and running lint/build there before releasing.\n\n## Hardened by default\n\nA scaffolder runs with write access to your filesystem and picks your starting dependencies, so how it is built and published matters:\n\n- **Signed provenance** — every release carries a sigstore attestation linking the tarball to the exact commit and CI run that built it. Check the **Provenance** section on the [npm page](https://www.npmjs.com/package/create-bestax#provenance), or run `npm audit signatures`.\n- **npm OIDC trusted publishing** — short-lived, per-run credentials; no long-lived `NPM_TOKEN` exists to be stolen. Release commits and tags are GPG-signed.\n- **Socket.dev scans every PR** for malware, install scripts, obfuscated code, and privilege escalation before it can reach `main`.\n- **Dependencies are a deliberate act** — install scripts are blocked unless individually allow-listed, freshly published versions are refused for 3 days, and CI installs only what the reviewed lockfile resolves.\n- **Every GitHub Action is pinned to a full commit SHA**, so a compromised action release can't roll silently into a build of this CLI.\n- **CodeQL, Dependency Review, and Dependabot** run continuously, alongside a high-severity `pnpm audit` gate.\n- **Layered AI review before merge** — [CodeRabbit](https://coderabbit.ai) plus an independent adversarial Claude review (a different model from the one writing AI-authored changes), on top of required green CI, an approving review, and a human merge.\n\nFull detail: [`SECURITY.md`](https://github.com/allxsmith/bestax/blob/main/SECURITY.md) · [Security guide](https://bestax.io/docs/guides/security)\n\n## Publishing\n\nThis package uses semantic-release with scope-based rules. Only commits with `feat(create-bestax)` or `fix(create-bestax)` will trigger releases.\n"
77
+ "readme": "# create-bestax\n\n[![npm version](https://img.shields.io/npm/v/create-bestax.svg)](https://www.npmjs.com/package/create-bestax)\n[![npm downloads](https://img.shields.io/npm/dm/create-bestax.svg)](https://www.npmjs.com/package/create-bestax)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Socket Badge](https://socket.dev/api/badge/npm/package/create-bestax)](https://socket.dev/npm/package/create-bestax/overview)\n[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/allxsmith/bestax/badge)](https://scorecard.dev/viewer/?uri=github.com/allxsmith/bestax)\n[![npm provenance](https://img.shields.io/badge/npm-provenance-3fb950.svg)](https://www.npmjs.com/package/create-bestax#provenance)\n[![Security policy](https://img.shields.io/badge/security-policy-blue.svg)](https://github.com/allxsmith/bestax/blob/main/SECURITY.md)\n\nThe scaffolder for [`@allxsmith/bestax-bulma`](https://www.npmjs.com/package/@allxsmith/bestax-bulma) — spin up a Vite app pre-wired for the **Bulma v1** React component library in one command. Picks your framework (JS or TypeScript), CSS flavor, and icon library, and can drop in the bestax **AI skills** so an agent like Claude Code knows the library from the first prompt.\n\nPart of the [bestax monorepo](https://github.com/allxsmith/bestax) — see also [`@allxsmith/bestax-bulma`](https://www.npmjs.com/package/@allxsmith/bestax-bulma) for the components themselves.\n\n## Requirements\n\n- **Node.js 22 or newer.** Node 18 and 20 are both past end-of-life. On an older runtime the\n CLI exits immediately with an explicit upgrade message.\n- npm 10 or newer (ships with Node 22), or yarn/pnpm.\n\n## Usage\n\n### Quick Start\n\n```bash\n# Using npm\nnpm create bestax@latest my-app\n\n# Using npx\nnpx create-bestax@latest my-app\n\n# Alternative naming\nnpm create bestax-bulma@latest my-app\n```\n\n### Interactive Mode\n\nRunning without arguments will prompt you for all options:\n\n```bash\nnpm create bestax@latest\n```\n\nYou'll be asked to:\n\n1. Enter a project name\n2. Select a framework (Vite or Vite + TypeScript)\n3. Choose a Bulma CSS flavor (Complete or Minimal)\n4. Select an icon library (Font Awesome, Material Icons, etc.)\n5. Choose whether to install the bestax AI skills into `.claude/skills/`\n\nEvery prompt has a flag equivalent, so the whole flow is scriptable and CI-friendly — see the options below.\n\n### Command Line Options\n\n```bash\nnpm create bestax@latest [project-directory] [options]\n```\n\n**Arguments:**\n\n- `[project-directory]` - Optional project directory name\n\n**Options:**\n\n- `-t, --template <template>` - Template to use: `vite` or `vite-ts` \n **Default:** `vite`\n- `-b, --bulma <flavor>` - Bulma CSS flavor:\n - `complete` - Full Bulma CSS with all components and helpers\n - `prefixed` - Prefixed version for compatibility\n - `no-helpers` - Without helper classes\n - `no-helpers-prefixed` - Prefixed without helpers\n - `no-dark-mode` - Without dark mode support \n **Default:** `complete`\n- `-i, --icon <library>` - Icon library:\n - `none` - No icon library\n - `fontawesome` - Font Awesome\n - `mdi` - Material Design Icons\n - `ionicons` - Ionicons\n - `material-icons` - Material Icons\n - `material-symbols` - Material Symbols \n **Default:** `none`\n- `--skills` / `--no-skills` - Install (or skip) the bestax AI skills into\n `.claude/skills/` (with a `CLAUDE.md`) so a Claude Code session picks them up\n automatically. When omitted you're prompted; with `-y` the default is to install.\n- `-y, --yes` - Skip prompts and use defaults or provided options. \n When used, the following defaults are selected unless overridden by flags:\n - Template: `vite`\n - Bulma flavor: `complete`\n - Icon library: `none`\n - AI skills: installed (pass `--no-skills` to opt out)\n\n**Example:**\n\n```bash\n# Create a TypeScript project with Font Awesome icons\nnpm create bestax@latest my-app -t vite-ts -i fontawesome -b complete\n\n# Use all defaults (skip prompts)\nnpm create bestax@latest my-app -y\n```\n\n## Templates\n\nAvailable templates:\n\n- `vite` - Vite + JavaScript\n- `vite-ts` - Vite + TypeScript\n\nEach template includes:\n\n- Pre-configured bestax-bulma integration (React 19)\n- The latest `@allxsmith/bestax-bulma`, which ships Bulma v1 automatically\n- Icon library support (Font Awesome, Material Design, etc.)\n- Sample components demonstrating library usage\n- Development and build scripts\n\n## For AI Tools\n\nScaffolding for an AI agent? Pass `--skills` (or accept the prompt) to drop the bestax\nAgent Skills and a `CLAUDE.md` into the new project so Claude Code, Cursor, or Copilot\nbuild the bestax way from the start. The library also ships LLM-optimized docs:\n\n- [LLMs guide](https://bestax.io/docs/guides/llms) — using bestax with Claude Code, Cursor, Copilot\n- [Agent Skills](https://bestax.io/docs/skills/intro) — teach your agent the bestax conventions\n- [llms.txt](https://bestax.io/llms.txt) (curated index) · [llms-full.txt](https://bestax.io/llms-full.txt) (full docs)\n\n## Development\n\n```bash\nnpm run build # Build the CLI\nnpm run dev # Watch mode\nnpm test # Run tests\nnpm run lint # Lint CLI source code\nnpm run typecheck # Type check CLI source code\n```\n\n**Note on Templates:** Template files in `templates/` are excluded from linting. They should be manually validated by scaffolding a test project and running lint/build there before releasing.\n\n## Hardened by default\n\nA scaffolder runs with write access to your filesystem and picks your starting dependencies, so how it is built and published matters:\n\n- **Signed provenance** — every release carries a sigstore attestation linking the tarball to the exact commit and CI run that built it. Check the **Provenance** section on the [npm page](https://www.npmjs.com/package/create-bestax#provenance), or run `npm audit signatures`.\n- **npm OIDC trusted publishing** — short-lived, per-run credentials; no long-lived `NPM_TOKEN` exists to be stolen. Release commits and tags are GPG-signed.\n- **Socket.dev scans every PR** for malware, install scripts, obfuscated code, and privilege escalation before it can reach `main`.\n- **Dependencies are a deliberate act** — install scripts are blocked unless individually allow-listed, freshly published versions are refused for 3 days, and CI installs only what the reviewed lockfile resolves.\n- **Every GitHub Action is pinned to a full commit SHA**, so a compromised action release can't roll silently into a build of this CLI.\n- **CodeQL, Dependency Review, and Dependabot** run continuously, alongside a high-severity `pnpm audit` gate.\n- **Layered AI review before merge** — [CodeRabbit](https://coderabbit.ai) plus an independent adversarial Claude review (a different model from the one writing AI-authored changes), on top of required green CI, an approving review, and a human merge.\n\nFull detail: [`SECURITY.md`](https://github.com/allxsmith/bestax/blob/main/SECURITY.md) · [Security guide](https://bestax.io/docs/guides/security)\n\n## Telemetry\n\n`create-bestax` can send one **anonymous** usage event after a successful\nscaffold — only if you opt in when asked (once, at the end of a scaffold). The\nevent is just the choices you made — template, Bulma flavor, icon library,\nskills, package manager — plus the CLI version, Node major version, and OS\nplatform; never names, IPs, machine IDs, paths, or file contents, and no\nidentifier exists that could link two events together. Opt out any time with\n`--no-telemetry`, `BESTAX_TELEMETRY=0`, or `DO_NOT_TRACK=1`.\n\nFull disclosure of every field and control: [Telemetry guide](https://bestax.io/docs/guides/telemetry)\n\n## Publishing\n\nThis package uses semantic-release with scope-based rules. Only commits with `feat(create-bestax)` or `fix(create-bestax)` will trigger releases.\n"
78
78
  }