create-kubojs 0.4.1 → 0.5.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
@@ -104,27 +104,31 @@ For MCP project creation, prefer `install: false`. Long dependency installs can
104
104
 
105
105
  ## Telemetry
106
106
 
107
- This CLI collects anonymous usage data to help improve the tool. The data collected includes:
107
+ This CLI collects anonymous usage data to help improve the tool. Each `create` run (except `--dry-run`) sends one event with:
108
108
 
109
- - Configuration options selected
110
- - CLI version
111
- - Node.js version
112
- - Platform (OS)
109
+ - Selected stack options (only when the project is created)
110
+ - Outcome (`success`, `failure`, `cancelled`), duration and, on failure, the error class name (never the message)
111
+ - How the CLI was invoked (`cli`, `json`, `mcp`, `api`) and how the stack was chosen (`interactive`, `flags`, `yes`, `template`)
112
+ - CLI version, Node.js version, platform, whether it runs in CI, and the coding agent detected from environment markers (e.g. `claude-code`, `cursor`, `codex`)
113
+ - A random anonymous install ID stored in the kubojs config directory (`telemetry.json`), used to count unique installs
113
114
 
114
- **Telemetry is enabled by default in published versions** to help us understand usage patterns and improve the tool.
115
+ Project names, paths, file contents, error messages and environment variable values are never sent. See https://kubojs.dev/docs/analytics.
115
116
 
116
117
  ### Disabling Telemetry
117
118
 
118
- You can disable telemetry by setting the `BTS_TELEMETRY_DISABLED` environment variable:
119
-
120
119
  ```bash
121
120
  # Disable telemetry for a single run
122
- BTS_TELEMETRY_DISABLED=1 npx kubojs
121
+ KUBO_TELEMETRY_DISABLED=1 npx kubojs
122
+
123
+ # DO_NOT_TRACK is also honored
124
+ DO_NOT_TRACK=1 npx kubojs
123
125
 
124
126
  # Disable telemetry globally in your shell profile (.bashrc, .zshrc, etc.)
125
- export BTS_TELEMETRY_DISABLED=1
127
+ export KUBO_TELEMETRY_DISABLED=1
126
128
  ```
127
129
 
130
+ The legacy `BTS_TELEMETRY_DISABLED=1` variable still works.
131
+
128
132
  ## Examples
129
133
 
130
134
  Create a project with default configuration:
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { _ as getLatestCLIVersion, g as types_exports, i as SchemaNameSchema, l as create, m as getSchemaResult, s as add, u as createBtsCli } from "./src-L3WKWfns.mjs";
2
+ import { _ as types_exports, g as setTelemetrySource, i as SchemaNameSchema, l as create, m as getSchemaResult, s as add, u as createBtsCli, v as getLatestCLIVersion } from "./src-QAuuh4xm.mjs";
3
3
  import z from "zod";
4
4
  import { format } from "node:util";
5
5
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -353,7 +353,7 @@ function createBtsMcpServer() {
353
353
  if (input.install) return formatToolError(getMcpInstallTimeoutMessage(input.packageManager));
354
354
  const result = await create(input.projectName, {
355
355
  ...input,
356
- disableAnalytics: true
356
+ disableAnalytics: false
357
357
  });
358
358
  if (result.isErr()) return formatToolError(result.error);
359
359
  return formatToolSuccess(result.value);
@@ -408,6 +408,7 @@ function createBtsMcpServer() {
408
408
  return server;
409
409
  }
410
410
  async function startBtsMcpServer() {
411
+ setTelemetrySource("mcp");
411
412
  const server = createBtsMcpServer();
412
413
  const transport = new StdioServerTransport();
413
414
  await server.connect(transport);
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import { C as UserCancelledError, S as ProjectCreationError, a as TEMPLATE_COUNT, b as DatabaseSetupError, c as builder, d as createVirtual, f as docs, h as router, i as SchemaNameSchema, l as create, m as getSchemaResult, n as GeneratorError, o as VirtualFileSystem, p as generate, r as Result, s as add, t as EMBEDDED_TEMPLATES, u as createBtsCli, v as CLIError, w as ValidationError, x as DirectoryConflictError, y as CompatibilityError } from "./src-L3WKWfns.mjs";
2
+ import { C as ProjectCreationError, S as DirectoryConflictError, T as ValidationError, a as TEMPLATE_COUNT, b as CompatibilityError, c as builder, d as createVirtual, f as docs, h as router, i as SchemaNameSchema, l as create, m as getSchemaResult, n as GeneratorError, o as VirtualFileSystem, p as generate, r as Result, s as add, t as EMBEDDED_TEMPLATES, u as createBtsCli, w as UserCancelledError, x as DatabaseSetupError, y as CLIError } from "./src-QAuuh4xm.mjs";
3
3
  export { CLIError, CompatibilityError, DatabaseSetupError, DirectoryConflictError, EMBEDDED_TEMPLATES, GeneratorError, ProjectCreationError, Result, SchemaNameSchema, TEMPLATE_COUNT, UserCancelledError, ValidationError, VirtualFileSystem, add, builder, create, createBtsCli, createVirtual, docs, generate, getSchemaResult, router };
@@ -4022,33 +4022,40 @@ async function getDeploymentChoice(deployment, _runtime, backend, frontend = [],
4022
4022
  }
4023
4023
  //#endregion
4024
4024
  //#region src/prompts/config-prompts.ts
4025
- async function gatherConfig(flags, projectName, projectDir, relativePath) {
4026
- if (isSilent()) return {
4027
- projectName,
4028
- projectDir,
4029
- relativePath,
4030
- addonOptions: flags.addonOptions,
4031
- dbSetupOptions: flags.dbSetupOptions,
4032
- frontend: flags.frontend ?? [...DEFAULT_CONFIG.frontend],
4033
- backend: flags.backend ?? DEFAULT_CONFIG.backend,
4034
- runtime: flags.runtime ?? DEFAULT_CONFIG.runtime,
4035
- database: flags.database ?? DEFAULT_CONFIG.database,
4036
- orm: flags.orm ?? DEFAULT_CONFIG.orm,
4037
- auth: flags.auth ?? DEFAULT_CONFIG.auth,
4038
- payments: flags.payments ?? DEFAULT_CONFIG.payments,
4039
- observability: flags.observability ?? DEFAULT_CONFIG.observability,
4040
- communication: normalizeCommunication$2(flags.communication ?? DEFAULT_CONFIG.communication),
4041
- addons: flags.addons ?? [...DEFAULT_CONFIG.addons],
4042
- examples: flags.examples ?? [...DEFAULT_CONFIG.examples],
4043
- testing: flags.testing ?? [...DEFAULT_CONFIG.testing],
4044
- git: flags.git ?? DEFAULT_CONFIG.git,
4045
- packageManager: flags.packageManager ?? DEFAULT_CONFIG.packageManager,
4046
- install: flags.install ?? DEFAULT_CONFIG.install,
4047
- dbSetup: flags.dbSetup ?? DEFAULT_CONFIG.dbSetup,
4048
- api: flags.api ?? DEFAULT_CONFIG.api,
4049
- webDeploy: flags.webDeploy ?? DEFAULT_CONFIG.webDeploy,
4050
- serverDeploy: flags.serverDeploy ?? DEFAULT_CONFIG.serverDeploy
4051
- };
4025
+ /** Prompts need a TTY on stdin; without one (CI, pipes, agents) they never resolve. */
4026
+ function canPromptForInput(stdin = process.stdin) {
4027
+ return stdin.isTTY === true;
4028
+ }
4029
+ async function gatherConfig(flags, projectName, projectDir, relativePath, stdin = process.stdin) {
4030
+ if (isSilent() || !canPromptForInput(stdin)) {
4031
+ if (!isSilent()) log.info(pc.yellow("No interactive terminal detected; using defaults for options not passed as flags."));
4032
+ return {
4033
+ projectName,
4034
+ projectDir,
4035
+ relativePath,
4036
+ addonOptions: flags.addonOptions,
4037
+ dbSetupOptions: flags.dbSetupOptions,
4038
+ frontend: flags.frontend ?? [...DEFAULT_CONFIG.frontend],
4039
+ backend: flags.backend ?? DEFAULT_CONFIG.backend,
4040
+ runtime: flags.runtime ?? DEFAULT_CONFIG.runtime,
4041
+ database: flags.database ?? DEFAULT_CONFIG.database,
4042
+ orm: flags.orm ?? DEFAULT_CONFIG.orm,
4043
+ auth: flags.auth ?? DEFAULT_CONFIG.auth,
4044
+ payments: flags.payments ?? DEFAULT_CONFIG.payments,
4045
+ observability: flags.observability ?? DEFAULT_CONFIG.observability,
4046
+ communication: normalizeCommunication$2(flags.communication ?? DEFAULT_CONFIG.communication),
4047
+ addons: flags.addons ?? [...DEFAULT_CONFIG.addons],
4048
+ examples: flags.examples ?? [...DEFAULT_CONFIG.examples],
4049
+ testing: flags.testing ?? [...DEFAULT_CONFIG.testing],
4050
+ git: flags.git ?? DEFAULT_CONFIG.git,
4051
+ packageManager: flags.packageManager ?? DEFAULT_CONFIG.packageManager,
4052
+ install: flags.install ?? DEFAULT_CONFIG.install,
4053
+ dbSetup: flags.dbSetup ?? DEFAULT_CONFIG.dbSetup,
4054
+ api: flags.api ?? DEFAULT_CONFIG.api,
4055
+ webDeploy: flags.webDeploy ?? DEFAULT_CONFIG.webDeploy,
4056
+ serverDeploy: flags.serverDeploy ?? DEFAULT_CONFIG.serverDeploy
4057
+ };
4058
+ }
4052
4059
  const result = await navigableGroup({
4053
4060
  frontend: ({ previousAnswer }) => getFrontendChoice(flags.frontend, flags.backend, flags.auth, previousAnswer),
4054
4061
  backend: ({ results, previousAnswer }) => getBackendFrameworkChoice(flags.backend, results.frontend, previousAnswer),
@@ -4175,51 +4182,17 @@ async function getProjectName(initialName) {
4175
4182
  return projectPath;
4176
4183
  }
4177
4184
  //#endregion
4178
- //#region src/utils/telemetry.ts
4179
- /**
4180
- * Returns true if telemetry/analytics should be enabled, false otherwise.
4181
- *
4182
- * - If BTS_TELEMETRY_DISABLED is present and "1", disables analytics.
4183
- * - Otherwise, BTS_TELEMETRY: "0" disables, "1" enables (default: enabled).
4184
- */
4185
- function isTelemetryEnabled() {
4186
- const BTS_TELEMETRY_DISABLED = process.env.BTS_TELEMETRY_DISABLED;
4187
- const BTS_TELEMETRY = "0";
4188
- if (BTS_TELEMETRY_DISABLED !== void 0) return BTS_TELEMETRY_DISABLED !== "1";
4189
- if (BTS_TELEMETRY !== void 0) return BTS_TELEMETRY === "1";
4190
- return true;
4191
- }
4192
- //#endregion
4193
4185
  //#region src/utils/analytics.ts
4194
- async function sendConvexEvent(payload) {}
4195
- async function trackProjectCreation(config, disableAnalytics = false) {
4196
- if (!isTelemetryEnabled() || disableAnalytics) return;
4197
- const { database, orm, backend, runtime, frontend, addons, examples, auth, payments, git, packageManager, install, dbSetup, api, webDeploy, serverDeploy } = config;
4198
- await Result.tryPromise({
4199
- try: () => sendConvexEvent({
4200
- database,
4201
- orm,
4202
- backend,
4203
- runtime,
4204
- frontend,
4205
- addons,
4206
- examples,
4207
- auth,
4208
- payments,
4209
- git,
4210
- packageManager,
4211
- install,
4212
- dbSetup,
4213
- api,
4214
- webDeploy,
4215
- serverDeploy,
4216
- cli_version: getLatestCLIVersion(),
4217
- node_version: typeof process !== "undefined" ? process.version : "",
4218
- platform: typeof process !== "undefined" ? process.platform : ""
4219
- }),
4220
- catch: () => void 0
4221
- });
4186
+ let telemetrySource = "cli";
4187
+ /** Set once per process by entry points that are not the interactive terminal (e.g. MCP). */
4188
+ function setTelemetrySource(source) {
4189
+ telemetrySource = source;
4190
+ }
4191
+ /** The JS API (`create()`) reports as "api" unless an entry point (e.g. MCP) already set a source. */
4192
+ function markProgrammaticTelemetrySource() {
4193
+ if (telemetrySource === "cli") telemetrySource = "api";
4222
4194
  }
4195
+ async function trackCreateEvent(event, disableAnalytics = false) {}
4223
4196
  //#endregion
4224
4197
  //#region src/utils/display-config.ts
4225
4198
  function displayConfig(config) {
@@ -6583,10 +6556,26 @@ async function createProjectHandler(input, options = {}) {
6583
6556
  const startTime = Date.now();
6584
6557
  const timeScaffolded = (/* @__PURE__ */ new Date()).toISOString();
6585
6558
  const result = await createProjectHandlerInternal(input, startTime, timeScaffolded);
6586
- if (result.isOk()) return result.value;
6559
+ const mode = getTelemetryMode(input);
6560
+ if (result.isOk()) {
6561
+ if (!input.dryRun) await trackCreateEvent({
6562
+ outcome: "success",
6563
+ mode,
6564
+ durationMs: result.value.elapsedTimeMs,
6565
+ config: result.value.projectConfig
6566
+ }, input.disableAnalytics);
6567
+ return result.value;
6568
+ }
6587
6569
  const error = result.error;
6588
6570
  const elapsedTimeMs = Date.now() - startTime;
6589
- if (UserCancelledError.is(error)) {
6571
+ const cancelled = UserCancelledError.is(error);
6572
+ if (!input.dryRun) await trackCreateEvent({
6573
+ outcome: cancelled ? "cancelled" : "failure",
6574
+ mode,
6575
+ durationMs: elapsedTimeMs,
6576
+ errorCategory: cancelled ? void 0 : getErrorCategory(error)
6577
+ }, input.disableAnalytics);
6578
+ if (cancelled) {
6590
6579
  if (isSilent()) return createEmptyResult(timeScaffolded, elapsedTimeMs, error.message);
6591
6580
  return;
6592
6581
  }
@@ -6595,6 +6584,38 @@ async function createProjectHandler(input, options = {}) {
6595
6584
  process.exit(1);
6596
6585
  });
6597
6586
  }
6587
+ /** Stack-selecting flags; their presence means the user chose the stack via flags, not prompts. */
6588
+ const STACK_FLAG_KEYS = [
6589
+ "database",
6590
+ "orm",
6591
+ "backend",
6592
+ "runtime",
6593
+ "frontend",
6594
+ "api",
6595
+ "auth",
6596
+ "payments",
6597
+ "addons",
6598
+ "examples",
6599
+ "dbSetup",
6600
+ "webDeploy",
6601
+ "serverDeploy"
6602
+ ];
6603
+ function getTelemetryMode(input) {
6604
+ if (input.template && input.template !== "none") return "template";
6605
+ if (input.yes) return "yes";
6606
+ if (isSilent() || STACK_FLAG_KEYS.some((key) => input[key] !== void 0)) return "flags";
6607
+ return "interactive";
6608
+ }
6609
+ function getTag(value) {
6610
+ if (typeof value !== "object" || value === null || !("_tag" in value)) return void 0;
6611
+ return typeof value._tag === "string" ? value._tag : void 0;
6612
+ }
6613
+ /** Error class names only (e.g. `CLIError:ValidationError`), never messages or paths. */
6614
+ function getErrorCategory(error) {
6615
+ const tag = getTag(error) ?? "UnknownError";
6616
+ const causeTag = "cause" in error ? getTag(error.cause) : void 0;
6617
+ return causeTag ? `${tag}:${causeTag}` : tag;
6618
+ }
6598
6619
  async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
6599
6620
  return Result.gen(async function* () {
6600
6621
  if (!isSilent() && input.renderTitle !== false) renderTitle();
@@ -6760,7 +6781,6 @@ async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
6760
6781
  dbSetupOptions: effectiveDbSetupOptions
6761
6782
  }));
6762
6783
  if (!isSilent()) log.success(cliColors.bright(`You can reproduce this setup with the following command:\n${reproducibleCommand}`));
6763
- await trackProjectCreation(config, input.disableAnalytics);
6764
6784
  const historyResult = await addToHistory(config, reproducibleCommand);
6765
6785
  if (historyResult.isErr() && !isSilent()) log.warn(pc.yellow(historyResult.error.message));
6766
6786
  const elapsedTimeMs = Date.now() - startTime;
@@ -6952,6 +6972,7 @@ const router = t.router({
6952
6972
  description: "Create a project from a raw JSON payload (agent-friendly)",
6953
6973
  jsonInput: true
6954
6974
  }).input(types_exports.CreateInputSchema).mutation(async ({ input }) => {
6975
+ setTelemetrySource("json");
6955
6976
  const result = await createProjectHandler(input, { silent: true });
6956
6977
  if (!result) throw new UserCancelledError({ message: "Operation cancelled" });
6957
6978
  if (!result.success) throw new CLIError({ message: result.error || "Unknown error occurred" });
@@ -7019,6 +7040,7 @@ function createBtsCli() {
7019
7040
  * ```
7020
7041
  */
7021
7042
  async function create(projectName, options) {
7043
+ markProgrammaticTelemetrySource();
7022
7044
  const input = {
7023
7045
  ...options,
7024
7046
  projectName,
@@ -7154,4 +7176,4 @@ async function add(options = {}) {
7154
7176
  return addHandler(options, { silent: true });
7155
7177
  }
7156
7178
  //#endregion
7157
- export { UserCancelledError as C, ProjectCreationError as S, getLatestCLIVersion as _, TEMPLATE_COUNT as a, DatabaseSetupError as b, builder as c, createVirtual as d, docs as f, types_exports as g, router as h, SchemaNameSchema as i, create as l, getSchemaResult as m, GeneratorError$1 as n, VirtualFileSystem$1 as o, generate$1 as p, Result$1 as r, add as s, EMBEDDED_TEMPLATES$1 as t, createBtsCli as u, CLIError as v, ValidationError as w, DirectoryConflictError as x, CompatibilityError as y };
7179
+ export { ProjectCreationError as C, DirectoryConflictError as S, ValidationError as T, types_exports as _, TEMPLATE_COUNT as a, CompatibilityError as b, builder as c, createVirtual as d, docs as f, setTelemetrySource as g, router as h, SchemaNameSchema as i, create as l, getSchemaResult as m, GeneratorError$1 as n, VirtualFileSystem$1 as o, generate$1 as p, Result$1 as r, add as s, EMBEDDED_TEMPLATES$1 as t, createBtsCli as u, getLatestCLIVersion as v, UserCancelledError as w, DatabaseSetupError as x, CLIError as y };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-kubojs",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
5
5
  "keywords": [
6
6
  "better-auth",
@@ -73,8 +73,8 @@
73
73
  "dependencies": {
74
74
  "@clack/core": "^1.4.1",
75
75
  "@clack/prompts": "^1.5.1",
76
- "@kubojs/template-generator": "^0.4.1",
77
- "@kubojs/types": "^0.4.1",
76
+ "@kubojs/template-generator": "^0.5.0",
77
+ "@kubojs/types": "^0.5.0",
78
78
  "@modelcontextprotocol/sdk": "1.29.0",
79
79
  "@tanstack/router-generator": "1.167.28",
80
80
  "@trpc/server": "^11.17.0",
@@ -93,7 +93,7 @@
93
93
  "trpc-cli": "^0.14.1",
94
94
  "ts-morph": "^28.0.0",
95
95
  "yaml": "^2.9.0",
96
- "zod": "^4.4.3"
96
+ "zod": "~4.4.3"
97
97
  },
98
98
  "devDependencies": {
99
99
  "@types/bun": "^1.3.14",