@c15t/cli 2.0.0-rc.6 → 2.0.0-rc.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/145.mjs CHANGED
@@ -1,17 +1,18 @@
1
1
  import "dotenv/config";
2
2
  import open_0 from "open";
3
3
  import picocolors from "picocolors";
4
- import promises, { readdir, writeFile } from "node:fs/promises";
5
- import node_path, { extname, join as external_node_path_join } from "node:path";
4
+ import promises, { readFile, readdir, writeFile } from "node:fs/promises";
5
+ import node_path, { dirname, extname, join as external_node_path_join, relative, resolve as external_node_path_resolve } from "node:path";
6
6
  import { Node, Project, SyntaxKind } from "ts-morph";
7
- import { existsSync as external_node_fs_existsSync } from "node:fs";
7
+ import node_fs, { existsSync as external_node_fs_existsSync } from "node:fs";
8
8
  import { assign as external_xstate_assign, createActor, fromPromise, setup } from "xstate";
9
9
  import node_crypto from "node:crypto";
10
10
  import node_os from "node:os";
11
- import { PostHog } from "posthog-node";
11
+ import { createLogger, initLogger } from "evlog";
12
+ import { createDrainPipeline } from "evlog/pipeline";
12
13
  import { spawn as external_node_child_process_spawn } from "node:child_process";
13
14
  import { once } from "node:events";
14
- import { createLogger } from "@c15t/logger";
15
+ import { createLogger as logger_createLogger } from "@c15t/logger";
15
16
  import { migrator } from "@c15t/backend/db/migrator";
16
17
  import { DB } from "@c15t/backend/db/schema";
17
18
  import { loadConfig as external_c12_loadConfig } from "c12";
@@ -83,14 +84,17 @@ __webpack_require__.r(config_store_namespaceObject);
83
84
  __webpack_require__.d(config_store_namespaceObject, {
84
85
  getAccessToken: ()=>config_store_getAccessToken,
85
86
  IM: ()=>getAuthState,
87
+ qs: ()=>getSelectedInstanceId,
88
+ M3: ()=>isLoggedIn,
86
89
  _g: ()=>setSelectedInstanceId,
87
90
  Ex: ()=>storeTokens
88
91
  });
89
92
  function showHelpMenu(context, version, commands, flags) {
90
93
  const { logger } = context;
91
94
  logger.debug('Displaying help menu using command and flag structures.');
92
- const commandColumnWidth = Math.max(...commands.map((cmd)=>cmd.name.length), 10) + 2;
93
- const commandLines = commands.map((cmd)=>` ${cmd.name.padEnd(commandColumnWidth)}${cmd.description}`).join('\n');
95
+ const visibleCommands = commands.filter((cmd)=>!cmd.hidden);
96
+ const commandColumnWidth = Math.max(...visibleCommands.map((cmd)=>cmd.name.length), 10) + 2;
97
+ const commandLines = visibleCommands.map((cmd)=>` ${cmd.name.padEnd(commandColumnWidth)}${cmd.description}`).join('\n');
94
98
  const flagDisplays = flags.map((flag)=>{
95
99
  const names = flag.names.join(', ');
96
100
  const valuePlaceholder = flag.expectsValue ? ' <value>' : '';
@@ -351,6 +355,191 @@ async function runActiveUiApiCodemod(options) {
351
355
  errors
352
356
  };
353
357
  }
358
+ const CSS_ENTRYPOINT_CANDIDATES = [
359
+ 'app/globals.css',
360
+ 'src/app/globals.css',
361
+ 'app/global.css',
362
+ 'src/app/global.css',
363
+ 'styles/globals.css',
364
+ 'src/styles/globals.css',
365
+ 'styles/global.css',
366
+ 'src/styles/global.css',
367
+ 'src/index.css',
368
+ 'src/styles.css',
369
+ 'src/style.css',
370
+ 'styles.css',
371
+ 'app.css',
372
+ 'src/App.css'
373
+ ];
374
+ const LOCAL_CSS_IMPORT_RE = /^\s*import(?:\s+[^'"]+\s+from\s+)?['"]([^'"]+\.css)['"];\s*$/gm;
375
+ const TAILWIND_V4_IMPORT_RE = /^\s*@import\s+['"]tailwindcss['"];\s*$/;
376
+ const TAILWIND_COMPONENTS_RE = /^\s*@tailwind\s+components\s*;\s*$/;
377
+ const TAILWIND_UTILITIES_RE = /^\s*@tailwind\s+utilities\s*;\s*$/;
378
+ function normalizePath(projectRoot, filePath) {
379
+ if (filePath.startsWith(projectRoot)) return filePath;
380
+ return external_node_path_resolve(projectRoot, filePath);
381
+ }
382
+ function dedupePaths(paths) {
383
+ return [
384
+ ...new Set(paths)
385
+ ];
386
+ }
387
+ function isNonModuleLocalCssImport(moduleSpecifier) {
388
+ return moduleSpecifier.startsWith('.') && moduleSpecifier.endsWith('.css') && !moduleSpecifier.endsWith('.module.css');
389
+ }
390
+ function getManagedPackages(packageName) {
391
+ if ('@c15t/react' === packageName || '@c15t/nextjs' === packageName) return [
392
+ '@c15t/react',
393
+ '@c15t/nextjs'
394
+ ];
395
+ return [
396
+ '@c15t/ui'
397
+ ];
398
+ }
399
+ function getImportVariants(packageName, kind) {
400
+ if ('base' === kind) return [
401
+ `${packageName}/styles.css`,
402
+ `${packageName}/styles.tw3.css`
403
+ ];
404
+ return [
405
+ `${packageName}/iab/styles.css`,
406
+ `${packageName}/iab/styles.tw3.css`
407
+ ];
408
+ }
409
+ function getDesiredImportPath(packageName, kind, tailwindVersion) {
410
+ const suffix = isTailwindV3(tailwindVersion) ? 'styles.tw3.css' : 'styles.css';
411
+ return 'base' === kind ? `${packageName}/${suffix}` : `${packageName}/iab/${suffix}`;
412
+ }
413
+ function getDesiredImports(packageName, tailwindVersion, includeBase, includeIab) {
414
+ const imports = [];
415
+ if (includeBase) imports.push(getDesiredImportPath(packageName, 'base', tailwindVersion));
416
+ if (includeIab) imports.push(getDesiredImportPath(packageName, 'iab', tailwindVersion));
417
+ return imports;
418
+ }
419
+ function getFrameworkImportRegex(packageNames) {
420
+ const escapedPackages = packageNames.map((packageName)=>packageName.replace('/', '\\/')).join('|');
421
+ return new RegExp(`^\\s*@import\\s+['"](?:${escapedPackages})(?:\\/iab)?\\/styles(?:\\.tw3)?\\.css['"];\\s*$`);
422
+ }
423
+ function findTopInsertionLineIndex(lines) {
424
+ let index = 0;
425
+ if (lines[index]?.trim().startsWith('/*')) {
426
+ while(index < lines.length){
427
+ const line = lines[index];
428
+ index += 1;
429
+ if (line?.includes('*/')) break;
430
+ }
431
+ while(index < lines.length && lines[index]?.trim() === '')index += 1;
432
+ }
433
+ return index;
434
+ }
435
+ function insertImportsIntoCssContent(content, desiredImports, tailwindVersion, managedPackages) {
436
+ const normalizedContent = content.replace(/\r\n/g, '\n');
437
+ const hadTrailingNewline = normalizedContent.endsWith('\n');
438
+ const body = hadTrailingNewline ? normalizedContent.slice(0, -1) : normalizedContent;
439
+ const lines = body.length > 0 ? body.split('\n') : [];
440
+ const importRegex = getFrameworkImportRegex(managedPackages);
441
+ const filteredLines = lines.filter((line)=>!importRegex.test(line));
442
+ const importLines = desiredImports.map((importPath)=>`@import "${importPath}";`);
443
+ let insertionIndex = findTopInsertionLineIndex(filteredLines);
444
+ if (isTailwindV3(tailwindVersion)) {
445
+ const componentsIndex = filteredLines.findIndex((line)=>TAILWIND_COMPONENTS_RE.test(line));
446
+ if (componentsIndex >= 0) insertionIndex = componentsIndex + 1;
447
+ else {
448
+ const utilitiesIndex = filteredLines.findIndex((line)=>TAILWIND_UTILITIES_RE.test(line));
449
+ if (utilitiesIndex >= 0) insertionIndex = utilitiesIndex;
450
+ }
451
+ } else {
452
+ const tailwindImportIndex = filteredLines.findIndex((line)=>TAILWIND_V4_IMPORT_RE.test(line));
453
+ if (tailwindImportIndex >= 0) insertionIndex = tailwindImportIndex + 1;
454
+ }
455
+ const nextLines = [
456
+ ...filteredLines.slice(0, insertionIndex),
457
+ ...importLines,
458
+ ...filteredLines.slice(insertionIndex)
459
+ ];
460
+ let nextContent = nextLines.join('\n');
461
+ if (hadTrailingNewline) nextContent += '\n';
462
+ if (content.includes('\r\n')) nextContent = nextContent.replace(/\n/g, '\r\n');
463
+ return nextContent;
464
+ }
465
+ function describeImportChange(content, packageName, desiredImportPath) {
466
+ const kind = desiredImportPath.includes('/iab/') ? 'iab' : 'base';
467
+ const variants = getImportVariants(packageName, kind);
468
+ const alternateVariant = variants.find((variant)=>variant !== desiredImportPath && content.includes(variant));
469
+ if (alternateVariant) return `replaced @import "${alternateVariant}"; with @import "${desiredImportPath}";`;
470
+ if (content.includes(desiredImportPath)) return `normalized @import "${desiredImportPath}";`;
471
+ return `added @import "${desiredImportPath}";`;
472
+ }
473
+ async function resolveCssEntrypoint({ projectRoot, entrypointPath }) {
474
+ const searchedPaths = [];
475
+ if (entrypointPath) {
476
+ const resolvedEntrypointPath = normalizePath(projectRoot, entrypointPath);
477
+ if (external_node_fs_existsSync(resolvedEntrypointPath)) {
478
+ const entrypointContent = await readFile(resolvedEntrypointPath, 'utf-8');
479
+ for (const match of entrypointContent.matchAll(LOCAL_CSS_IMPORT_RE)){
480
+ const moduleSpecifier = match[1];
481
+ if (!moduleSpecifier || !isNonModuleLocalCssImport(moduleSpecifier)) continue;
482
+ const candidatePath = external_node_path_resolve(dirname(resolvedEntrypointPath), moduleSpecifier);
483
+ searchedPaths.push(candidatePath);
484
+ if (external_node_fs_existsSync(candidatePath)) return {
485
+ filePath: candidatePath,
486
+ searchedPaths: dedupePaths(searchedPaths)
487
+ };
488
+ }
489
+ }
490
+ }
491
+ for (const candidate of CSS_ENTRYPOINT_CANDIDATES){
492
+ const candidatePath = external_node_path_join(projectRoot, candidate);
493
+ searchedPaths.push(candidatePath);
494
+ if (external_node_fs_existsSync(candidatePath)) return {
495
+ filePath: candidatePath,
496
+ searchedPaths: dedupePaths(searchedPaths)
497
+ };
498
+ }
499
+ return {
500
+ filePath: null,
501
+ searchedPaths: dedupePaths(searchedPaths)
502
+ };
503
+ }
504
+ function formatSearchedCssPaths(projectRoot, searchedPaths) {
505
+ return searchedPaths.map((filePath)=>relative(projectRoot, filePath) || '.').join(', ');
506
+ }
507
+ function isTailwindV3(version) {
508
+ return null != version && /^(?:\^|~)?3/.test(version);
509
+ }
510
+ async function ensureGlobalCssStylesheetImports(options) {
511
+ const desiredImports = getDesiredImports(options.packageName, options.tailwindVersion, options.includeBase, options.includeIab);
512
+ if (0 === desiredImports.length) return {
513
+ updated: false,
514
+ filePath: null,
515
+ searchedPaths: [],
516
+ changes: []
517
+ };
518
+ const { filePath, searchedPaths } = await resolveCssEntrypoint(options);
519
+ if (!filePath) return {
520
+ updated: false,
521
+ filePath: null,
522
+ searchedPaths,
523
+ changes: []
524
+ };
525
+ const content = await readFile(filePath, 'utf-8');
526
+ const managedPackages = getManagedPackages(options.packageName);
527
+ const nextContent = insertImportsIntoCssContent(content, desiredImports, options.tailwindVersion, managedPackages);
528
+ if (nextContent === content) return {
529
+ updated: false,
530
+ filePath,
531
+ searchedPaths,
532
+ changes: []
533
+ };
534
+ if (!options.dryRun) await writeFile(filePath, nextContent, 'utf-8');
535
+ const changes = desiredImports.map((importPath)=>describeImportChange(content, options.packageName, importPath));
536
+ return {
537
+ updated: true,
538
+ filePath,
539
+ searchedPaths,
540
+ changes
541
+ };
542
+ }
354
543
  const add_stylesheet_imports_SUPPORTED_EXTENSIONS = new Set([
355
544
  '.ts',
356
545
  '.tsx',
@@ -400,6 +589,17 @@ const NEXTJS_ENTRYPOINTS = [
400
589
  'pages/_app.tsx',
401
590
  'pages/_app.jsx'
402
591
  ];
592
+ async function detectTailwindVersion(projectRoot) {
593
+ const packageJsonPath = external_node_path_join(projectRoot, 'package.json');
594
+ if (!external_node_fs_existsSync(packageJsonPath)) return null;
595
+ try {
596
+ const content = await readFile(packageJsonPath, 'utf-8');
597
+ const parsed = JSON.parse(content);
598
+ return parsed.dependencies?.tailwindcss ?? parsed.devDependencies?.tailwindcss ?? null;
599
+ } catch {
600
+ return null;
601
+ }
602
+ }
403
603
  async function add_stylesheet_imports_collectSourceFiles(rootDir) {
404
604
  const files = [];
405
605
  async function walk(currentDir) {
@@ -492,23 +692,19 @@ function findEntrypoint(projectRoot, framework) {
492
692
  }
493
693
  return null;
494
694
  }
495
- function hasCssImport(project, filePath, cssImportPath) {
496
- const sourceFile = project.addSourceFileAtPathIfExists(filePath);
497
- if (!sourceFile) return false;
498
- const importDeclarations = sourceFile.getImportDeclarations();
499
- return importDeclarations.some((importDecl)=>importDecl.getModuleSpecifierValue() === cssImportPath);
500
- }
501
- function addCssImport(project, filePath, cssImportPath) {
695
+ const FRAMEWORK_STYLESHEET_IMPORT_RE = /^@c15t\/(?:react|nextjs)(?:\/iab)?\/styles(?:\.tw3)?\.css$/;
696
+ function removeFrameworkStylesheetImports(project, filePath) {
502
697
  const sourceFile = project.addSourceFileAtPathIfExists(filePath);
503
- if (!sourceFile) return false;
504
- const importDeclarations = sourceFile.getImportDeclarations();
505
- const alreadyExists = importDeclarations.some((importDecl)=>importDecl.getModuleSpecifierValue() === cssImportPath);
506
- if (alreadyExists) return false;
507
- if (importDeclarations.length > 0) {
508
- const lastImport = importDeclarations[importDeclarations.length - 1];
509
- if (lastImport) sourceFile.insertStatements(lastImport.getChildIndex() + 1, `import '${cssImportPath}';`);
510
- } else sourceFile.insertStatements(0, `import '${cssImportPath}';`);
511
- return true;
698
+ if (!sourceFile) return [];
699
+ const removedImports = [];
700
+ for (const importDeclaration of sourceFile.getImportDeclarations()){
701
+ const moduleSpecifier = importDeclaration.getModuleSpecifierValue();
702
+ if (FRAMEWORK_STYLESHEET_IMPORT_RE.test(moduleSpecifier)) {
703
+ removedImports.push(moduleSpecifier);
704
+ importDeclaration.remove();
705
+ }
706
+ }
707
+ return removedImports;
512
708
  }
513
709
  async function runAddStylesheetImportsCodemod(options) {
514
710
  const project = new Project({
@@ -550,40 +746,42 @@ async function runAddStylesheetImportsCodemod(options) {
550
746
  };
551
747
  }
552
748
  const pkg = 'nextjs' === detection.framework ? '@c15t/nextjs' : '@c15t/react';
553
- const operations = [];
554
- const summaries = [];
749
+ const tailwindVersion = await detectTailwindVersion(options.projectRoot);
555
750
  try {
556
- if (detection.usesStyledUi) {
557
- const baseCss = `${pkg}/styles.css`;
558
- if (!hasCssImport(project, entrypoint, baseCss)) {
559
- const added = addCssImport(project, entrypoint, baseCss);
560
- if (added) {
561
- operations.push(1);
562
- summaries.push(`added import '${baseCss}'`);
563
- }
564
- }
565
- }
566
- if (detection.usesIabUi) {
567
- const iabCss = `${pkg}/iab/styles.css`;
568
- if (!hasCssImport(project, entrypoint, iabCss)) {
569
- const added = addCssImport(project, entrypoint, iabCss);
570
- if (added) {
571
- operations.push(1);
572
- summaries.push(`added import '${iabCss}'`);
573
- }
574
- }
751
+ const stylesheetResult = await ensureGlobalCssStylesheetImports({
752
+ projectRoot: options.projectRoot,
753
+ packageName: pkg,
754
+ tailwindVersion,
755
+ entrypointPath: entrypoint,
756
+ includeBase: detection.usesStyledUi || detection.usesIabUi,
757
+ includeIab: detection.usesIabUi,
758
+ dryRun: options.dryRun
759
+ });
760
+ if (!stylesheetResult.filePath) {
761
+ errors.push({
762
+ filePath: options.projectRoot,
763
+ error: `No suitable global CSS entrypoint found. Checked: ${formatSearchedCssPaths(options.projectRoot, stylesheetResult.searchedPaths)}`
764
+ });
765
+ return {
766
+ totalFiles: filePaths.length,
767
+ changedFiles,
768
+ errors
769
+ };
575
770
  }
576
- if (operations.length > 0) {
577
- changedFiles.push({
578
- filePath: entrypoint,
579
- operations: operations.length,
580
- summaries
771
+ if (stylesheetResult.updated) changedFiles.push({
772
+ filePath: stylesheetResult.filePath,
773
+ operations: stylesheetResult.changes.length,
774
+ summaries: stylesheetResult.changes
775
+ });
776
+ for (const filePath of filePaths){
777
+ const removedImports = removeFrameworkStylesheetImports(project, filePath);
778
+ if (0 !== removedImports.length) changedFiles.push({
779
+ filePath,
780
+ operations: removedImports.length,
781
+ summaries: removedImports.map((importPath)=>`removed JS import '${importPath}'`)
581
782
  });
582
- if (!options.dryRun) {
583
- const sourceFile = project.getSourceFile(entrypoint);
584
- if (sourceFile) await sourceFile.save();
585
- }
586
783
  }
784
+ if (!options.dryRun) await project.save();
587
785
  } catch (error) {
588
786
  errors.push({
589
787
  filePath: entrypoint,
@@ -2113,8 +2311,8 @@ const codemods = [
2113
2311
  },
2114
2312
  {
2115
2313
  id: 'add-stylesheet-imports',
2116
- label: 'add stylesheet imports for prebuilt UI',
2117
- hint: 'Adds @c15t/react/styles.css or @c15t/nextjs/styles.css imports for styled components.',
2314
+ label: 'configure global CSS for prebuilt UI',
2315
+ hint: 'Moves styled c15t imports into the app CSS entrypoint, including Tailwind 3 and IAB variants when needed.',
2118
2316
  run: async (context, dryRun)=>{
2119
2317
  const { projectRoot } = context;
2120
2318
  const result = await runAddStylesheetImportsCodemod({
@@ -2173,7 +2371,95 @@ const codemodsCommand = {
2173
2371
  description: 'Run project codemods (for example translations -> i18n migration).',
2174
2372
  action: runCodemods
2175
2373
  };
2176
- const TELEMETRY_DISABLED_ENV = 'C15T_TELEMETRY_DISABLED';
2374
+ const constants_URLS = {
2375
+ CONSENT_IO: 'https://consent.io',
2376
+ TELEMETRY: 'https://telemetry.c15t.com/c15t/v1/logs',
2377
+ DOCS: 'https://v2.c15t.com/docs',
2378
+ GITHUB: 'https://github.com/c15t/c15t',
2379
+ DISCORD: 'https://v2.c15t.com/discord',
2380
+ API_DOCS: 'https://v2.c15t.com/docs/api',
2381
+ CLI_DOCS: 'https://v2.c15t.com/docs/cli',
2382
+ CHANGELOG: 'https://v2.c15t.com/changelog'
2383
+ };
2384
+ const PATHS = {
2385
+ CONFIG_DIR: '.c15t',
2386
+ CONFIG_FILE: 'config.json',
2387
+ TELEMETRY_STATE_FILE: 'telemetry.json',
2388
+ TELEMETRY_QUEUE_FILE: 'telemetry-queue.json',
2389
+ PROJECT_CONFIG: 'c15t.config.ts',
2390
+ PROJECT_CONFIG_JS: 'c15t.config.js',
2391
+ ENV_FILE: '.env',
2392
+ ENV_LOCAL: '.env.local'
2393
+ };
2394
+ const constants_REGEX = {
2395
+ URL: /^https?:\/\/.+/,
2396
+ C15T_URL: /^https:\/\/[\w-]+\.c15t\.dev$/,
2397
+ DYNAMIC_SEGMENT: /\[[\w-]+\]/,
2398
+ SEMVER: /^\d+\.\d+\.\d+(-[\w.]+)?$/,
2399
+ PACKAGE_NAME: /^(@[\w-]+\/)?[\w-]+$/
2400
+ };
2401
+ const CLI_INFO = {
2402
+ NAME: 'c15t',
2403
+ BIN: 'c15t',
2404
+ CONTROL_PLANE_CLIENT_NAME: 'c15t-cli',
2405
+ VERSION: '2.0.0'
2406
+ };
2407
+ const TIMEOUTS = {
2408
+ DEVICE_FLOW_POLL_INTERVAL: 5,
2409
+ DEVICE_FLOW_EXPIRY: 900,
2410
+ HTTP_REQUEST: 10000,
2411
+ CONTROL_PLANE_CONNECTION: 30000
2412
+ };
2413
+ const ENV_VARS = {
2414
+ V2: 'V2',
2415
+ TELEMETRY_DISABLED: 'C15T_TELEMETRY_DISABLED',
2416
+ TELEMETRY_ENDPOINT: 'C15T_TELEMETRY_ENDPOINT',
2417
+ TELEMETRY_WRITE_KEY: 'C15T_TELEMETRY_WRITE_KEY',
2418
+ TELEMETRY_ORG_ID: 'C15T_TELEMETRY_ORG_ID',
2419
+ CONSENT_URL: 'CONSENT_URL',
2420
+ BACKEND_URL: 'C15T_URL',
2421
+ API_KEY: 'C15T_API_KEY',
2422
+ DEBUG: 'C15T_DEBUG'
2423
+ };
2424
+ const STORAGE_MODES = {
2425
+ HOSTED: 'hosted',
2426
+ C15T: 'c15t',
2427
+ OFFLINE: 'offline',
2428
+ SELF_HOSTED: 'self-hosted',
2429
+ CUSTOM: 'custom'
2430
+ };
2431
+ const LAYOUT_PATTERNS = [
2432
+ 'app/layout.tsx',
2433
+ 'app/layout.ts',
2434
+ 'app/layout.jsx',
2435
+ 'app/layout.js',
2436
+ 'src/app/layout.tsx',
2437
+ 'src/app/layout.ts',
2438
+ 'src/app/layout.jsx',
2439
+ 'src/app/layout.js',
2440
+ 'app/*/layout.tsx',
2441
+ 'app/*/layout.ts',
2442
+ 'app/*/layout.jsx',
2443
+ 'app/*/layout.js',
2444
+ 'src/app/*/layout.tsx',
2445
+ 'src/app/*/layout.ts',
2446
+ 'src/app/*/layout.jsx',
2447
+ 'src/app/*/layout.js',
2448
+ 'app/*/*/layout.tsx',
2449
+ 'app/*/*/layout.ts',
2450
+ 'src/app/*/*/layout.tsx',
2451
+ 'src/app/*/*/layout.ts'
2452
+ ];
2453
+ const PAGES_APP_PATTERNS = [
2454
+ 'pages/_app.tsx',
2455
+ 'pages/_app.ts',
2456
+ 'pages/_app.jsx',
2457
+ 'pages/_app.js',
2458
+ 'src/pages/_app.tsx',
2459
+ 'src/pages/_app.ts',
2460
+ 'src/pages/_app.jsx',
2461
+ 'src/pages/_app.js'
2462
+ ];
2177
2463
  const TelemetryEventName = {
2178
2464
  CLI_INVOKED: 'cli.invoked',
2179
2465
  CLI_COMPLETED: 'cli.completed',
@@ -2191,6 +2477,7 @@ const TelemetryEventName = {
2191
2477
  HELP_DISPLAYED: 'help.displayed',
2192
2478
  VERSION_DISPLAYED: 'version.displayed',
2193
2479
  ONBOARDING_STARTED: 'onboarding.started',
2480
+ ONBOARDING_STAGE: 'onboarding.stage',
2194
2481
  ONBOARDING_COMPLETED: 'onboarding.completed',
2195
2482
  ONBOARDING_EXITED: 'onboarding.exited',
2196
2483
  ONBOARDING_STORAGE_MODE_SELECTED: 'onboarding.storage_mode_selected',
@@ -2201,6 +2488,13 @@ const TelemetryEventName = {
2201
2488
  ONBOARDING_DEPENDENCIES_CHOICE: 'onboarding.dependencies_choice',
2202
2489
  ONBOARDING_DEPENDENCIES_INSTALLED: 'onboarding.dependencies_installed',
2203
2490
  ONBOARDING_GITHUB_STAR: 'onboarding.github_star',
2491
+ AUTH_LOGIN_STARTED: 'auth.login.started',
2492
+ AUTH_LOGIN_SUCCEEDED: 'auth.login.succeeded',
2493
+ AUTH_LOGIN_FAILED: 'auth.login.failed',
2494
+ AUTH_LOGOUT: 'auth.logout',
2495
+ PROJECTS_LISTED: 'projects.listed',
2496
+ PROJECT_SELECTED: 'project.selected',
2497
+ PROJECT_CREATED: 'project.created',
2204
2498
  ERROR_OCCURRED: 'error.occurred',
2205
2499
  MIGRATION_STARTED: 'migration.started',
2206
2500
  MIGRATION_PLANNED: 'migration.planned',
@@ -2218,178 +2512,408 @@ const TelemetryEventName = {
2218
2512
  CLI_STATE_CANCELLED: 'cli.state.cancelled',
2219
2513
  CLI_STATE_COMPLETE: 'cli.state.complete'
2220
2514
  };
2515
+ const DEFAULT_QUEUE_LIMIT = 250;
2516
+ const DEFAULT_TIMEOUT_MS = 3000;
2517
+ const DEFAULT_BATCH_SIZE = 20;
2518
+ const DEFAULT_BATCH_INTERVAL_MS = 1000;
2519
+ const DEFAULT_MAX_BUFFER_SIZE = 250;
2520
+ const MAX_DEPTH = 5;
2521
+ const MAX_ARRAY_LENGTH = 20;
2522
+ const MAX_OBJECT_KEYS = 50;
2523
+ const MAX_STRING_LENGTH = 500;
2524
+ const RESERVED_TOP_LEVEL_KEYS = new Set([
2525
+ 'event',
2526
+ 'installId',
2527
+ 'sessionId',
2528
+ 'commandRunId',
2529
+ 'sequence',
2530
+ 'source'
2531
+ ]);
2532
+ const SENSITIVE_KEY_PATTERN = /(^|[-_])(token|secret|password|authorization|cookie|api[-_]?key|access[-_]?token|refresh[-_]?token|config)([-_]|$)/i;
2533
+ const SECRET_VALUE_PATTERN = /^(Bearer\s+[A-Za-z0-9._-]+|[A-Za-z0-9+/=_-]{80,})$/;
2221
2534
  class Telemetry {
2222
- client = null;
2223
- disabled;
2535
+ endpoint;
2536
+ fetchImpl;
2537
+ queuePath;
2538
+ statePath;
2539
+ headers;
2224
2540
  defaultProperties;
2225
- distinctId;
2226
- apiKey = 'phc_ViY5LtTmh4kqoumXZB2olPFoTz4AbbDfrogNgFi1MH3';
2227
- debug;
2541
+ sessionId = node_crypto.randomUUID();
2542
+ installId;
2543
+ isFirstRun;
2544
+ drain;
2545
+ storageDir;
2228
2546
  logger;
2547
+ disabled;
2548
+ debug;
2549
+ sequence = 0;
2550
+ activeCommandName;
2551
+ activeCommandRunId;
2552
+ flushPromise = null;
2553
+ queueReplayPromise = Promise.resolve();
2554
+ queueWritePromise = Promise.resolve();
2229
2555
  constructor(options){
2230
- const envDisabled = '1' === process.env[TELEMETRY_DISABLED_ENV] || process.env[TELEMETRY_DISABLED_ENV]?.toLowerCase() === 'true';
2231
- const hasValidApiKey = !!(this.apiKey && '' !== this.apiKey.trim());
2232
- this.disabled = options?.disabled ?? envDisabled ?? !hasValidApiKey;
2233
- this.defaultProperties = options?.defaultProperties ?? {};
2234
- this.logger = options?.logger;
2556
+ const envDisabled = '1' === process.env[ENV_VARS.TELEMETRY_DISABLED] || process.env[ENV_VARS.TELEMETRY_DISABLED]?.toLowerCase() === 'true';
2557
+ this.disabled = options?.disabled ?? envDisabled ?? false;
2235
2558
  this.debug = options?.debug ?? false;
2236
- this.distinctId = this.generateAnonymousId();
2237
- if (this.disabled) {
2238
- if (!hasValidApiKey) this.logDebug('Telemetry disabled: No API key provided');
2239
- } else try {
2240
- this.initClient(options?.client);
2241
- } catch (error) {
2242
- this.disabled = true;
2243
- this.logDebug('Telemetry disabled due to initialization error:', error);
2244
- }
2559
+ this.logger = options?.logger;
2560
+ this.defaultProperties = this.sanitizeProperties(options?.defaultProperties ?? {});
2561
+ this.endpoint = options?.endpoint ?? process.env[ENV_VARS.TELEMETRY_ENDPOINT] ?? constants_URLS.TELEMETRY;
2562
+ this.fetchImpl = options?.fetch ?? fetch;
2563
+ this.storageDir = options?.storageDir ?? node_path.join(node_os.homedir(), PATHS.CONFIG_DIR);
2564
+ this.statePath = node_path.join(this.storageDir, PATHS.TELEMETRY_STATE_FILE);
2565
+ this.queuePath = node_path.join(this.storageDir, PATHS.TELEMETRY_QUEUE_FILE);
2566
+ this.headers = this.buildHeaders(options?.headers);
2567
+ const identity = this.loadOrCreateInstallIdentity();
2568
+ this.installId = identity.installId;
2569
+ this.isFirstRun = identity.isFirstRun;
2570
+ const userDrainOptions = options?.drainOptions;
2571
+ const onDropped = userDrainOptions?.onDropped;
2572
+ const pipeline = createDrainPipeline({
2573
+ batch: {
2574
+ size: userDrainOptions?.batch?.size ?? DEFAULT_BATCH_SIZE,
2575
+ intervalMs: userDrainOptions?.batch?.intervalMs ?? DEFAULT_BATCH_INTERVAL_MS
2576
+ },
2577
+ retry: {
2578
+ maxAttempts: userDrainOptions?.retry?.maxAttempts ?? 2,
2579
+ backoff: userDrainOptions?.retry?.backoff ?? 'fixed',
2580
+ initialDelayMs: userDrainOptions?.retry?.initialDelayMs ?? 250,
2581
+ maxDelayMs: userDrainOptions?.retry?.maxDelayMs ?? 1000
2582
+ },
2583
+ maxBufferSize: userDrainOptions?.maxBufferSize ?? DEFAULT_MAX_BUFFER_SIZE,
2584
+ onDropped: (events, error)=>{
2585
+ onDropped?.(events, error);
2586
+ this.persistDroppedEvents(events, error);
2587
+ }
2588
+ });
2589
+ this.drain = pipeline(async (batch)=>{
2590
+ await this.sendBatch(batch.map((item)=>item.event));
2591
+ });
2592
+ this.applyLoggerConfig();
2593
+ this.queueReplayPromise = this.flushQueuedEvents();
2245
2594
  }
2246
2595
  trackEvent(eventName, properties = {}) {
2247
- if (this.disabled || !this.client) {
2248
- if (this.debug) this.logDebug(`Telemetry event skipped (${eventName}): Telemetry disabled or client not initialized`);
2249
- return;
2250
- }
2251
- try {
2252
- const safeProperties = {};
2253
- for (const [key, value] of Object.entries(properties))if ('config' !== key && void 0 !== value) safeProperties[key] = value;
2254
- if (this.debug) this.logDebug(`Sending telemetry event: ${eventName}`);
2255
- this.client.capture({
2256
- distinctId: this.distinctId,
2257
- event: eventName,
2258
- properties: {
2259
- ...this.defaultProperties,
2260
- ...safeProperties,
2261
- timestamp: new Date().toISOString()
2262
- }
2263
- });
2264
- this.client.flush();
2265
- } catch (error) {
2266
- if (this.debug) this.logDebug(`Error sending telemetry event ${eventName}:`, error);
2267
- }
2268
- }
2269
- trackEventSync(eventName, properties = {}) {
2270
- if (this.disabled || !this.client) {
2271
- if (this.debug) this.logDebug('Telemetry disabled or client not initialized');
2596
+ if (this.disabled) {
2597
+ if (this.debug) this.logDebug(`Telemetry event skipped (${eventName}): telemetry disabled`);
2272
2598
  return;
2273
2599
  }
2274
- const safeProperties = {};
2275
- for (const [key, value] of Object.entries(properties))if ('config' !== key && void 0 !== value) safeProperties[key] = value;
2276
- if (this.debug) this.logDebug(`Sending telemetry event: ${eventName}`);
2277
2600
  try {
2278
- this.client.capture({
2279
- distinctId: this.distinctId,
2280
- event: eventName,
2281
- properties: {
2282
- ...this.defaultProperties,
2283
- ...safeProperties,
2284
- timestamp: new Date().toISOString()
2285
- }
2286
- });
2287
- this.client.flush();
2288
- if (this.debug) this.logDebug(`Flushed telemetry event: ${eventName}`);
2601
+ const log = createLogger(this.buildBaseContext(eventName));
2602
+ log.set(this.sanitizeProperties(properties));
2603
+ log.emit();
2604
+ if (this.debug) this.logDebug(`Queued telemetry event: ${eventName}`);
2289
2605
  } catch (error) {
2290
- if (this.debug) this.logDebug(`Error sending telemetry: ${error}`);
2606
+ if (this.debug) this.logDebug(`Failed to queue telemetry event ${eventName}:`, error);
2291
2607
  }
2292
2608
  }
2293
2609
  trackCommand(command, args = [], flags = {}) {
2294
- if (this.disabled || !this.client) return;
2295
- const safeFlags = {};
2296
- for (const [key, value] of Object.entries(flags))if ('config' !== key && void 0 !== value) safeFlags[key] = value;
2610
+ this.activeCommandName = command;
2611
+ this.activeCommandRunId = node_crypto.randomUUID();
2612
+ const safeFlags = this.sanitizeProperties(flags);
2613
+ const safeArgs = this.sanitizeValue(args);
2297
2614
  this.trackEvent(TelemetryEventName.COMMAND_EXECUTED, {
2298
2615
  command,
2299
- args: args.join(' '),
2300
- flagsData: JSON.stringify(safeFlags)
2616
+ commandRunId: this.activeCommandRunId,
2617
+ args: safeArgs,
2618
+ argsCount: args.length,
2619
+ flags: safeFlags,
2620
+ flagCount: Object.keys(safeFlags).length,
2621
+ flagNames: Object.keys(safeFlags).sort(),
2622
+ subcommand: 'string' == typeof safeArgs[0] ? safeArgs[0] : void 0
2301
2623
  });
2302
2624
  }
2303
2625
  trackError(error, command) {
2304
- if (this.disabled || !this.client) return;
2305
- this.trackEvent(TelemetryEventName.ERROR_OCCURRED, {
2306
- command,
2307
- error: error.message,
2308
- errorName: error.name,
2309
- stack: 'development' === process.env.NODE_ENV ? error.stack : void 0
2310
- });
2626
+ if (this.disabled) return;
2627
+ const safeCommand = command ?? this.activeCommandName;
2628
+ const safeError = this.sanitizeError(error);
2629
+ const errorMetadata = this.buildErrorMetadata(error);
2630
+ try {
2631
+ const log = createLogger(this.buildBaseContext(TelemetryEventName.ERROR_OCCURRED));
2632
+ log.error(safeError, {
2633
+ command: safeCommand,
2634
+ commandRunId: this.activeCommandRunId,
2635
+ failure: errorMetadata
2636
+ });
2637
+ log.emit();
2638
+ if (this.debug) this.logDebug(`Queued telemetry error event: ${safeCommand ?? 'unknown-command'}`);
2639
+ } catch (trackingError) {
2640
+ if (this.debug) this.logDebug('Failed to queue telemetry error event:', trackingError);
2641
+ }
2311
2642
  }
2312
- disable() {
2313
- this.disabled = true;
2643
+ flushSync() {
2644
+ if (this.disabled) return;
2645
+ this.flushPromise = this.flushAll();
2314
2646
  }
2315
- enable() {
2316
- this.disabled = false;
2317
- if (!this.client) this.initClient();
2647
+ async shutdown() {
2648
+ if (this.disabled) return;
2649
+ await (this.flushPromise ?? this.flushAll());
2318
2650
  }
2319
2651
  isDisabled() {
2320
2652
  return this.disabled;
2321
2653
  }
2322
- async shutdown() {
2323
- if (this.client) {
2324
- await this.client.shutdown();
2325
- this.client = null;
2326
- }
2654
+ disable() {
2655
+ this.disabled = true;
2656
+ this.applyLoggerConfig();
2657
+ }
2658
+ enable() {
2659
+ this.disabled = false;
2660
+ this.applyLoggerConfig();
2661
+ this.queueReplayPromise = this.flushQueuedEvents();
2327
2662
  }
2328
2663
  setLogger(logger) {
2329
2664
  this.logger = logger;
2330
2665
  }
2331
- logDebug(message, ...args) {
2332
- if (this.logger) this.logger.debug(message, ...args);
2333
- else console.debug(message, ...args);
2666
+ applyLoggerConfig() {
2667
+ const cliVersion = this.readString(this.defaultProperties.cliVersion);
2668
+ initLogger({
2669
+ enabled: !this.disabled,
2670
+ silent: true,
2671
+ pretty: false,
2672
+ stringify: false,
2673
+ _suppressDrainWarning: this.disabled,
2674
+ env: {
2675
+ service: 'c15t-cli',
2676
+ environment: this.getEnvironmentName(),
2677
+ version: cliVersion
2678
+ },
2679
+ drain: this.disabled ? void 0 : this.drain
2680
+ });
2334
2681
  }
2335
- initClient(customClient) {
2336
- if (customClient) {
2337
- this.client = customClient;
2338
- if (this.debug) this.logDebug('Using custom PostHog client');
2339
- } else {
2340
- if (!this.apiKey || '' === this.apiKey.trim()) {
2341
- this.disabled = true;
2342
- this.logDebug('Telemetry disabled: No API key provided');
2343
- return;
2344
- }
2345
- const startTime = Date.now();
2682
+ buildBaseContext(eventName) {
2683
+ this.sequence += 1;
2684
+ return {
2685
+ event: eventName,
2686
+ source: 'c15t-cli',
2687
+ installId: this.installId,
2688
+ sessionId: this.sessionId,
2689
+ commandRunId: this.activeCommandRunId,
2690
+ command: this.activeCommandName,
2691
+ sequence: this.sequence,
2692
+ firstRun: this.isFirstRun,
2693
+ interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY),
2694
+ tty: Boolean(process.stdout.isTTY),
2695
+ ci: this.isCi(),
2696
+ platform: process.platform,
2697
+ arch: process.arch,
2698
+ nodeVersion: process.version,
2699
+ ...this.defaultProperties
2700
+ };
2701
+ }
2702
+ buildHeaders(overrides) {
2703
+ const writeKey = process.env[ENV_VARS.TELEMETRY_WRITE_KEY];
2704
+ const orgId = process.env[ENV_VARS.TELEMETRY_ORG_ID];
2705
+ return {
2706
+ 'Content-Type': 'application/json',
2707
+ ...writeKey ? {
2708
+ Authorization: `Bearer ${writeKey}`
2709
+ } : {},
2710
+ ...orgId ? {
2711
+ 'X-Axiom-Org-Id': orgId
2712
+ } : {},
2713
+ ...overrides
2714
+ };
2715
+ }
2716
+ async flushAll() {
2717
+ try {
2718
+ await this.queueReplayPromise;
2719
+ await this.drain.flush();
2720
+ await this.queueWritePromise;
2721
+ await this.flushQueuedEvents();
2722
+ if (this.debug) this.logDebug('Flushed telemetry events');
2723
+ } catch (error) {
2724
+ if (this.debug) this.logDebug('Telemetry flush failed:', error);
2725
+ } finally{
2726
+ this.flushPromise = null;
2727
+ }
2728
+ }
2729
+ async sendBatch(events) {
2730
+ if (0 === events.length) return;
2731
+ const controller = new AbortController();
2732
+ const timeout = setTimeout(()=>controller.abort(), DEFAULT_TIMEOUT_MS);
2733
+ try {
2734
+ const response = await this.fetchImpl(this.endpoint, {
2735
+ method: 'POST',
2736
+ headers: this.headers,
2737
+ body: JSON.stringify(events),
2738
+ signal: controller.signal
2739
+ });
2740
+ if (!response.ok) throw new Error(`Telemetry ingest failed with status ${response.status}`);
2741
+ } finally{
2742
+ clearTimeout(timeout);
2743
+ }
2744
+ }
2745
+ async persistDroppedEvents(events, error) {
2746
+ this.queueWritePromise = this.queueWritePromise.then(async ()=>{
2346
2747
  try {
2347
- const clientConfig = {
2348
- host: 'https://eu.i.posthog.com',
2349
- flushInterval: 0,
2350
- flushAt: 1,
2351
- requestTimeout: 3000
2352
- };
2353
- if (this.debug) this.logDebug('Initializing PostHog client with config:', JSON.stringify(clientConfig));
2354
- this.client = new PostHog(this.apiKey, clientConfig);
2355
- const initTime = Date.now() - startTime;
2356
- if (this.debug) this.logDebug('PostHog client initialized in', initTime, 'ms');
2357
- } catch (error) {
2358
- this.disabled = true;
2359
- const errorDetails = error instanceof Error ? {
2360
- message: error.message,
2361
- name: error.name,
2362
- stack: error.stack
2363
- } : {
2364
- rawError: String(error)
2365
- };
2366
- if (this.debug) this.logDebug('Telemetry disabled due to initialization error:', JSON.stringify(errorDetails, null, 2));
2367
- try {
2368
- if (this.debug) this.logDebug('Attempting fallback PostHog initialization');
2369
- this.client = new PostHog(this.apiKey);
2370
- this.disabled = false;
2371
- if (this.debug) this.logDebug('PostHog client initialized using fallback method');
2372
- } catch (fallbackError) {
2373
- this.logDebug('Fallback initialization also failed:', fallbackError instanceof Error ? fallbackError.message : String(fallbackError));
2374
- }
2748
+ const existing = await this.readQueuedEvents();
2749
+ const next = [
2750
+ ...existing,
2751
+ ...events.map((item)=>item.event)
2752
+ ].slice(-DEFAULT_QUEUE_LIMIT);
2753
+ await this.writeQueuedEvents(next);
2754
+ if (this.debug) this.logDebug(`Persisted ${events.length} dropped telemetry event(s) to disk`, error);
2755
+ } catch (queueError) {
2756
+ if (this.debug) this.logDebug('Failed to persist dropped telemetry events:', queueError);
2375
2757
  }
2758
+ });
2759
+ await this.queueWritePromise;
2760
+ }
2761
+ async flushQueuedEvents() {
2762
+ if (this.disabled) return;
2763
+ try {
2764
+ const queuedEvents = await this.readQueuedEvents();
2765
+ if (0 === queuedEvents.length) return;
2766
+ await this.sendBatch(queuedEvents);
2767
+ await promises.unlink(this.queuePath).catch(()=>void 0);
2768
+ if (this.debug) this.logDebug(`Replayed ${queuedEvents.length} queued telemetry event(s)`);
2769
+ } catch (error) {
2770
+ if (this.debug) this.logDebug('Failed to replay queued telemetry events:', error);
2376
2771
  }
2377
2772
  }
2378
- generateAnonymousId() {
2379
- const machineId = node_crypto.createHash('sha256').update(node_os.hostname() + node_os.platform() + node_os.arch() + node_os.totalmem()).digest('hex');
2380
- return machineId;
2773
+ async readQueuedEvents() {
2774
+ try {
2775
+ const content = await promises.readFile(this.queuePath, 'utf-8');
2776
+ const parsed = JSON.parse(content);
2777
+ if (!Array.isArray(parsed)) return [];
2778
+ return parsed.filter((item)=>'object' == typeof item && null !== item);
2779
+ } catch {
2780
+ return [];
2781
+ }
2381
2782
  }
2382
- flushSync() {
2383
- if (this.disabled || !this.client) return;
2783
+ async writeQueuedEvents(events) {
2784
+ await promises.mkdir(this.storageDir, {
2785
+ recursive: true
2786
+ });
2787
+ await promises.writeFile(this.queuePath, JSON.stringify(events, null, 2), {
2788
+ mode: 384
2789
+ });
2790
+ }
2791
+ loadOrCreateInstallIdentity() {
2384
2792
  try {
2385
- this.client.flush();
2386
- if (this.debug) this.logDebug('Manually flushed telemetry events');
2793
+ node_fs.mkdirSync(this.storageDir, {
2794
+ recursive: true
2795
+ });
2796
+ if (node_fs.existsSync(this.statePath)) {
2797
+ const content = node_fs.readFileSync(this.statePath, 'utf-8');
2798
+ const parsed = JSON.parse(content);
2799
+ if ('string' == typeof parsed.installId && parsed.installId.length > 0) return {
2800
+ installId: parsed.installId,
2801
+ isFirstRun: false
2802
+ };
2803
+ }
2387
2804
  } catch (error) {
2388
- if (this.debug) this.logDebug(`Error flushing telemetry: ${error}`);
2805
+ if (this.debug) this.logDebug('Failed to read telemetry state file:', error);
2806
+ }
2807
+ const installId = node_crypto.randomUUID();
2808
+ try {
2809
+ node_fs.writeFileSync(this.statePath, JSON.stringify({
2810
+ installId,
2811
+ createdAt: new Date().toISOString()
2812
+ }, null, 2), {
2813
+ mode: 384
2814
+ });
2815
+ } catch (error) {
2816
+ if (this.debug) this.logDebug('Failed to persist telemetry install ID:', error);
2817
+ }
2818
+ return {
2819
+ installId,
2820
+ isFirstRun: true
2821
+ };
2822
+ }
2823
+ buildErrorMetadata(error) {
2824
+ const eventError = error;
2825
+ return this.sanitizeProperties({
2826
+ name: eventError.name,
2827
+ message: eventError.message,
2828
+ code: 'string' == typeof eventError.code || 'number' == typeof eventError.code ? eventError.code : void 0,
2829
+ status: 'number' == typeof eventError.status ? eventError.status : void 0,
2830
+ statusCode: 'number' == typeof eventError.statusCode ? eventError.statusCode : void 0,
2831
+ cause: eventError.cause instanceof Error ? eventError.cause.message : 'string' == typeof eventError.cause ? eventError.cause : void 0
2832
+ });
2833
+ }
2834
+ sanitizeError(error) {
2835
+ const safeError = new Error(error.message);
2836
+ safeError.name = error.name;
2837
+ if ('development' === process.env.NODE_ENV) safeError.stack = error.stack;
2838
+ else delete safeError.stack;
2839
+ const sourceError = error;
2840
+ const targetError = safeError;
2841
+ if ('number' == typeof sourceError.status) targetError.status = sourceError.status;
2842
+ if ('number' == typeof sourceError.statusCode) targetError.statusCode = sourceError.statusCode;
2843
+ if ('string' == typeof sourceError.statusText) targetError.statusText = sourceError.statusText;
2844
+ if ('string' == typeof sourceError.statusMessage) targetError.statusMessage = sourceError.statusMessage;
2845
+ if (sourceError.cause instanceof Error) targetError.cause = sourceError.cause.message;
2846
+ else if ('string' == typeof sourceError.cause) targetError.cause = sourceError.cause;
2847
+ return safeError;
2848
+ }
2849
+ sanitizeProperties(properties) {
2850
+ const sanitized = {};
2851
+ for (const [key, value] of Object.entries(properties))if (!RESERVED_TOP_LEVEL_KEYS.has(key)) {
2852
+ if (void 0 !== value) {
2853
+ if (SENSITIVE_KEY_PATTERN.test(key)) {
2854
+ sanitized[key] = '[redacted]';
2855
+ continue;
2856
+ }
2857
+ sanitized[key] = this.sanitizeValue(value, 0, key);
2858
+ }
2859
+ }
2860
+ return sanitized;
2861
+ }
2862
+ sanitizeValue(value, depth = 0, keyHint) {
2863
+ if (depth >= MAX_DEPTH) return '[truncated]';
2864
+ if (null === value) return null;
2865
+ if ('string' == typeof value || 'number' == typeof value || 'boolean' == typeof value) return this.sanitizePrimitive(value, keyHint);
2866
+ if (value instanceof Date) return value.toISOString();
2867
+ if (value instanceof Error) return this.sanitizeProperties(this.buildErrorMetadata(value));
2868
+ if (Array.isArray(value)) return value.slice(0, MAX_ARRAY_LENGTH).map((item)=>this.sanitizeValue(item, depth + 1));
2869
+ if ('object' == typeof value) {
2870
+ const objectValue = value;
2871
+ const sanitizedObject = {};
2872
+ for (const key of Object.keys(objectValue).slice(0, MAX_OBJECT_KEYS)){
2873
+ const nextValue = objectValue[key];
2874
+ if (void 0 !== nextValue) {
2875
+ if (SENSITIVE_KEY_PATTERN.test(key)) {
2876
+ sanitizedObject[key] = '[redacted]';
2877
+ continue;
2878
+ }
2879
+ sanitizedObject[key] = this.sanitizeValue(nextValue, depth + 1, key);
2880
+ }
2881
+ }
2882
+ return sanitizedObject;
2389
2883
  }
2884
+ return String(value);
2885
+ }
2886
+ sanitizePrimitive(value, keyHint) {
2887
+ if ('string' != typeof value) return value;
2888
+ if (keyHint && SENSITIVE_KEY_PATTERN.test(keyHint)) return '[redacted]';
2889
+ if (SECRET_VALUE_PATTERN.test(value)) return '[redacted]';
2890
+ if (node_path.isAbsolute(value)) return '[absolute-path]';
2891
+ if (value.startsWith('http://') || value.startsWith('https://')) try {
2892
+ const parsed = new URL(value);
2893
+ parsed.username = '';
2894
+ parsed.password = '';
2895
+ parsed.search = '';
2896
+ parsed.hash = '';
2897
+ value = parsed.toString();
2898
+ } catch {}
2899
+ if (value.length > MAX_STRING_LENGTH) return `${value.slice(0, MAX_STRING_LENGTH)}...`;
2900
+ return value;
2901
+ }
2902
+ getEnvironmentName() {
2903
+ return process.env.NODE_ENV ?? (this.isCi() ? 'production' : 'development');
2904
+ }
2905
+ isCi() {
2906
+ return Boolean(process.env.CI || process.env.GITHUB_ACTIONS || process.env.BUILDKITE || process.env.VERCEL);
2907
+ }
2908
+ readString(value) {
2909
+ return 'string' == typeof value ? value : void 0;
2910
+ }
2911
+ logDebug(message, ...args) {
2912
+ if (this.logger) this.logger.debug(message, ...args);
2913
+ else console.debug(message, ...args);
2390
2914
  }
2391
2915
  }
2392
- function createTelemetry(options) {
2916
+ function telemetry_createTelemetry(options) {
2393
2917
  return new Telemetry(options);
2394
2918
  }
2395
2919
  const DEFAULT_PERSIST_FILENAME = '.c15t-state.json';
@@ -2484,6 +3008,86 @@ function createPersistenceSubscriber(machineId, persistPath, options = {}) {
2484
3008
  });
2485
3009
  };
2486
3010
  }
3011
+ const GENERATE_STAGE_NAMES = {
3012
+ preflight: 'preflight',
3013
+ preflightError: 'preflight',
3014
+ modeSelection: 'mode_selection',
3015
+ hostedMode: 'hosted_mode',
3016
+ offlineMode: 'offline_mode',
3017
+ customMode: 'custom_mode',
3018
+ backendOptions: 'backend_options',
3019
+ frontendOptions: 'frontend_options',
3020
+ scriptsOptions: "scripts_options",
3021
+ fileGeneration: 'file_generation',
3022
+ dependencyCheck: 'dependency_check',
3023
+ dependencyConfirm: 'dependency_confirm',
3024
+ dependencyInstall: 'dependency_install',
3025
+ summary: 'summary',
3026
+ skillsInstall: 'skills_install',
3027
+ githubStar: 'github_star',
3028
+ cancelling: 'cancelling',
3029
+ cleanup: 'cleanup',
3030
+ complete: 'complete',
3031
+ error: 'error',
3032
+ exited: 'exited',
3033
+ cancelled: 'cancelled'
3034
+ };
3035
+ function normalizeGenerateStageName(state) {
3036
+ return GENERATE_STAGE_NAMES[state] ?? state.replace(/[A-Z]/g, (char)=>`_${char.toLowerCase()}`);
3037
+ }
3038
+ function getGenerateContext(snapshot) {
3039
+ return snapshot.context;
3040
+ }
3041
+ function normalizeCancelReason(reason) {
3042
+ if (!reason) return 'user_cancelled';
3043
+ const normalized = reason.toLowerCase();
3044
+ if (normalized.includes('signal')) return 'signal_interrupted';
3045
+ if (normalized.includes('mode selection')) return 'mode_selection_cancelled';
3046
+ if (normalized.includes('hosted setup')) return 'hosted_setup_cancelled';
3047
+ if (normalized.includes('backend options')) return 'backend_options_cancelled';
3048
+ if (normalized.includes('frontend options')) return 'frontend_options_cancelled';
3049
+ if (normalized.includes("scripts option")) return "scripts_options_cancelled";
3050
+ if (normalized.includes('dependency')) return 'dependency_install_cancelled';
3051
+ if (normalized.includes('prompt cancelled at stage:')) return normalized.replace('prompt cancelled at stage:', '').trim().replace(/\s+/g, '_').concat('_cancelled');
3052
+ return 'user_cancelled';
3053
+ }
3054
+ function getStageReason(fromState, toState, context) {
3055
+ if ('preflightError' === toState) return 'preflight_failed';
3056
+ if ('cancelling' === toState || 'cancelled' === toState || 'exited' === toState) return normalizeCancelReason(context?.cancelReason);
3057
+ if ('dependencyInstall' === fromState && context?.installSucceeded === false) return 'dependency_install_failed';
3058
+ if ('error' === toState) {
3059
+ const lastError = context?.errors?.[context.errors.length - 1];
3060
+ if (lastError?.state === 'fileGeneration') return 'file_generation_failed';
3061
+ if (lastError?.error?.name === 'PromptCancelledError') return normalizeCancelReason(lastError.error.message);
3062
+ if (lastError?.state) return `${normalizeGenerateStageName(lastError.state)}_failed`;
3063
+ return 'machine_error';
3064
+ }
3065
+ }
3066
+ function getStageResult(fromState, toState, context) {
3067
+ if ('preflightError' === toState || 'error' === toState) return 'failed';
3068
+ if ('cancelling' === toState || 'cancelled' === toState || 'exited' === toState) return 'cancelled';
3069
+ if ('dependencyInstall' === fromState && context?.installSucceeded === false) return 'failed';
3070
+ return 'completed';
3071
+ }
3072
+ function buildGenerateStageTelemetry(fromState, toState, durationMs, snapshot) {
3073
+ const context = getGenerateContext(snapshot);
3074
+ return {
3075
+ stage: normalizeGenerateStageName(fromState),
3076
+ nextStage: normalizeGenerateStageName(toState),
3077
+ durationMs,
3078
+ result: getStageResult(fromState, toState, context),
3079
+ reason: getStageReason(fromState, toState, context),
3080
+ selectedMode: context?.selectedMode ?? void 0,
3081
+ hostedProvider: context?.hostedProvider ?? void 0,
3082
+ dependencyCount: context?.dependenciesToAdd?.length ?? 0,
3083
+ filesCreatedCount: context?.filesCreated?.length ?? 0,
3084
+ filesModifiedCount: context?.filesModified?.length ?? 0,
3085
+ installConfirmed: context?.installConfirmed ?? void 0,
3086
+ installAttempted: context?.installAttempted ?? void 0,
3087
+ installSucceeded: context?.installSucceeded ?? void 0,
3088
+ errorsCount: context?.errors?.length ?? 0
3089
+ };
3090
+ }
2487
3091
  function createTelemetrySubscriber(config) {
2488
3092
  const { telemetry, machineId, skipStates = [] } = config;
2489
3093
  let lastState = null;
@@ -2502,6 +3106,7 @@ function createTelemetrySubscriber(config) {
2502
3106
  toState: currentState,
2503
3107
  duration
2504
3108
  });
3109
+ if ('generate' === machineId) telemetry.trackEvent(TelemetryEventName.ONBOARDING_STAGE, buildGenerateStageTelemetry(lastState, currentState, duration, snapshot));
2505
3110
  }
2506
3111
  stateHistory.push({
2507
3112
  state: currentState,
@@ -2556,89 +3161,6 @@ function createDebugSubscriber(machineId, logger) {
2556
3161
  }
2557
3162
  };
2558
3163
  }
2559
- const constants_URLS = {
2560
- CONSENT_IO: 'https://consent.io',
2561
- DOCS: 'https://v2.c15t.com/docs',
2562
- GITHUB: 'https://github.com/c15t/c15t',
2563
- DISCORD: 'https://v2.c15t.com/discord',
2564
- API_DOCS: 'https://v2.c15t.com/docs/api',
2565
- CLI_DOCS: 'https://v2.c15t.com/docs/cli',
2566
- CHANGELOG: 'https://v2.c15t.com/changelog'
2567
- };
2568
- const PATHS = {
2569
- CONFIG_DIR: '.c15t',
2570
- CONFIG_FILE: 'config.json',
2571
- PROJECT_CONFIG: 'c15t.config.ts',
2572
- PROJECT_CONFIG_JS: 'c15t.config.js',
2573
- ENV_FILE: '.env',
2574
- ENV_LOCAL: '.env.local'
2575
- };
2576
- const constants_REGEX = {
2577
- URL: /^https?:\/\/.+/,
2578
- C15T_URL: /^https:\/\/[\w-]+\.c15t\.dev$/,
2579
- DYNAMIC_SEGMENT: /\[[\w-]+\]/,
2580
- SEMVER: /^\d+\.\d+\.\d+(-[\w.]+)?$/,
2581
- PACKAGE_NAME: /^(@[\w-]+\/)?[\w-]+$/
2582
- };
2583
- const CLI_INFO = {
2584
- NAME: 'c15t',
2585
- BIN: 'c15t',
2586
- CONTROL_PLANE_CLIENT_NAME: 'c15t-cli',
2587
- VERSION: '2.0.0'
2588
- };
2589
- const TIMEOUTS = {
2590
- DEVICE_FLOW_POLL_INTERVAL: 5,
2591
- DEVICE_FLOW_EXPIRY: 900,
2592
- HTTP_REQUEST: 10000,
2593
- CONTROL_PLANE_CONNECTION: 30000
2594
- };
2595
- const ENV_VARS = {
2596
- V2: 'V2',
2597
- TELEMETRY_DISABLED: 'C15T_TELEMETRY_DISABLED',
2598
- CONSENT_URL: 'CONSENT_URL',
2599
- BACKEND_URL: 'C15T_URL',
2600
- API_KEY: 'C15T_API_KEY',
2601
- DEBUG: 'C15T_DEBUG'
2602
- };
2603
- const STORAGE_MODES = {
2604
- HOSTED: 'hosted',
2605
- C15T: 'c15t',
2606
- OFFLINE: 'offline',
2607
- SELF_HOSTED: 'self-hosted',
2608
- CUSTOM: 'custom'
2609
- };
2610
- const LAYOUT_PATTERNS = [
2611
- 'app/layout.tsx',
2612
- 'app/layout.ts',
2613
- 'app/layout.jsx',
2614
- 'app/layout.js',
2615
- 'src/app/layout.tsx',
2616
- 'src/app/layout.ts',
2617
- 'src/app/layout.jsx',
2618
- 'src/app/layout.js',
2619
- 'app/*/layout.tsx',
2620
- 'app/*/layout.ts',
2621
- 'app/*/layout.jsx',
2622
- 'app/*/layout.js',
2623
- 'src/app/*/layout.tsx',
2624
- 'src/app/*/layout.ts',
2625
- 'src/app/*/layout.jsx',
2626
- 'src/app/*/layout.js',
2627
- 'app/*/*/layout.tsx',
2628
- 'app/*/*/layout.ts',
2629
- 'src/app/*/*/layout.tsx',
2630
- 'src/app/*/*/layout.ts'
2631
- ];
2632
- const PAGES_APP_PATTERNS = [
2633
- 'pages/_app.tsx',
2634
- 'pages/_app.ts',
2635
- 'pages/_app.jsx',
2636
- 'pages/_app.js',
2637
- 'src/pages/_app.tsx',
2638
- 'src/pages/_app.ts',
2639
- 'src/pages/_app.jsx',
2640
- 'src/pages/_app.js'
2641
- ];
2642
3164
  const ERROR_CATALOG = {
2643
3165
  AUTH_FAILED: {
2644
3166
  code: 'AUTH_FAILED',
@@ -2726,17 +3248,17 @@ const ERROR_CATALOG = {
2726
3248
  URL_INVALID: {
2727
3249
  code: 'URL_INVALID',
2728
3250
  message: 'Invalid URL format',
2729
- hint: 'Expected format: https://your-instance.c15t.dev'
3251
+ hint: 'Expected format: https://your-project.c15t.dev'
2730
3252
  },
2731
3253
  INSTANCE_NOT_FOUND: {
2732
3254
  code: 'INSTANCE_NOT_FOUND',
2733
- message: 'Instance not found',
2734
- hint: 'Run `c15t instances list` to see available instances'
3255
+ message: 'Project not found',
3256
+ hint: 'Run `c15t projects list` to see available projects'
2735
3257
  },
2736
3258
  INSTANCE_NAME_INVALID: {
2737
3259
  code: 'INSTANCE_NAME_INVALID',
2738
- message: 'Invalid instance name',
2739
- hint: 'Instance names must be alphanumeric with hyphens'
3260
+ message: 'Invalid project slug',
3261
+ hint: 'Project slugs must be alphanumeric with hyphens'
2740
3262
  },
2741
3263
  FILE_NOT_FOUND: {
2742
3264
  code: 'FILE_NOT_FOUND',
@@ -3255,11 +3777,19 @@ async function getAuthState() {
3255
3777
  isExpired: isTokenExpired(config)
3256
3778
  };
3257
3779
  }
3780
+ async function isLoggedIn() {
3781
+ const state = await getAuthState();
3782
+ return state.isLoggedIn && !state.isExpired;
3783
+ }
3258
3784
  async function config_store_getAccessToken() {
3259
3785
  const config = await loadConfig();
3260
3786
  if (!config || isTokenExpired(config)) return null;
3261
3787
  return config.accessToken;
3262
3788
  }
3789
+ async function getSelectedInstanceId() {
3790
+ const config = await loadConfig();
3791
+ return config?.selectedInstanceId || null;
3792
+ }
3263
3793
  async function setSelectedInstanceId(instanceId) {
3264
3794
  await updateConfig({
3265
3795
  selectedInstanceId: instanceId
@@ -3668,7 +4198,7 @@ class ControlPlaneClient {
3668
4198
  const instances = await this.listInstances();
3669
4199
  const instance = instances.find((item)=>item.id === id);
3670
4200
  if (!instance) throw new CliError('INSTANCE_NOT_FOUND', {
3671
- details: `Instance not found: ${id}`
4201
+ details: `Project not found: ${id}`
3672
4202
  });
3673
4203
  return instance;
3674
4204
  }
@@ -3763,7 +4293,7 @@ function createValidator(validate, errorMessage) {
3763
4293
  }
3764
4294
  createValidator(isValidUrl, 'Please enter a valid URL (e.g., https://example.com)');
3765
4295
  createValidator(isValidC15tUrl, 'Please enter a valid c15t URL (e.g., https://my-app.c15t.dev)');
3766
- const validateInstanceName = createValidator(isValidInstanceName, 'Instance name must be 3-63 lowercase alphanumeric characters with hyphens');
4296
+ const validateInstanceName = createValidator(isValidInstanceName, 'Project slug must be 3-63 lowercase alphanumeric characters with hyphens');
3767
4297
  createValidator((value)=>value.trim().length > 0, 'This field is required');
3768
4298
  function isCancel(value) {
3769
4299
  return __rspack_external__clack_prompts_3cae1695.isCancel(value);
@@ -3913,7 +4443,7 @@ async function createInstanceInteractively(client, cliContext) {
3913
4443
  })),
3914
4444
  initialValue: organizations[0]?.organizationSlug
3915
4445
  });
3916
- if (isCancel(orgSelection)) throw new PromptCancelledError('instance_create_org_slug');
4446
+ if (isCancel(orgSelection)) throw new PromptCancelledError('project_create_org_slug');
3917
4447
  const v2Regions = regions.filter((region)=>'v2' === region.family);
3918
4448
  if (0 === v2Regions.length) throw new CliError('API_ERROR', {
3919
4449
  details: 'No v2 provisioning regions available'
@@ -3927,15 +4457,15 @@ async function createInstanceInteractively(client, cliContext) {
3927
4457
  })),
3928
4458
  initialValue: v2Regions.find((region)=>'us-east-1' === region.id)?.id
3929
4459
  });
3930
- if (isCancel(regionSelection)) throw new PromptCancelledError('instance_create_region');
4460
+ if (isCancel(regionSelection)) throw new PromptCancelledError('project_create_region');
3931
4461
  const slugInput = await __rspack_external__clack_prompts_3cae1695.text({
3932
- message: 'New instance slug:',
4462
+ message: 'New project slug:',
3933
4463
  placeholder: 'my-app',
3934
4464
  validate: (value)=>validateInstanceName(value?.trim() ?? '')
3935
4465
  });
3936
- if (isCancel(slugInput)) throw new PromptCancelledError('instance_create_name');
4466
+ if (isCancel(slugInput)) throw new PromptCancelledError('project_create_name');
3937
4467
  const slug = slugInput.trim();
3938
- const createSpinner = createTaskSpinner(`Creating instance "${slug}"...`);
4468
+ const createSpinner = createTaskSpinner(`Creating project "${slug}"...`);
3939
4469
  createSpinner.start();
3940
4470
  try {
3941
4471
  const instance = await client.createInstance({
@@ -3945,17 +4475,17 @@ async function createInstanceInteractively(client, cliContext) {
3945
4475
  region: regionSelection
3946
4476
  }
3947
4477
  });
3948
- createSpinner.success('Instance created');
3949
- cliContext.logger.info('Created as a v2 development instance. Enable production mode in the dashboard when you are ready.');
4478
+ createSpinner.success('Project created');
4479
+ cliContext.logger.info('Created as a v2 development project. Enable production mode in the dashboard when you are ready.');
3950
4480
  return instance;
3951
4481
  } catch (error) {
3952
- createSpinner.error('Failed to create instance');
4482
+ createSpinner.error('Failed to create project');
3953
4483
  throw error;
3954
4484
  }
3955
4485
  }
3956
4486
  async function selectOrCreateInstance(cliContext) {
3957
4487
  const baseUrl = getControlPlaneBaseUrl();
3958
- const listSpinner = createTaskSpinner('Fetching your consent.io instances...');
4488
+ const listSpinner = createTaskSpinner('Fetching your consent.io projects...');
3959
4489
  listSpinner.start();
3960
4490
  const client = await createControlPlaneClientFromConfig(baseUrl);
3961
4491
  if (!client) {
@@ -3966,11 +4496,11 @@ async function selectOrCreateInstance(cliContext) {
3966
4496
  const instances = await client.listInstances();
3967
4497
  listSpinner.stop();
3968
4498
  if (0 === instances.length) {
3969
- cliContext.logger.info('No instances found. Creating a new instance for this project.');
4499
+ cliContext.logger.info('No projects found. Creating a new project for this local project.');
3970
4500
  return await createInstanceInteractively(client, cliContext);
3971
4501
  }
3972
4502
  const selectedId = await __rspack_external__clack_prompts_3cae1695.select({
3973
- message: 'Select an instance to use:',
4503
+ message: 'Select a project to use:',
3974
4504
  options: [
3975
4505
  ...instances.map((instance)=>({
3976
4506
  value: instance.id,
@@ -3979,12 +4509,12 @@ async function selectOrCreateInstance(cliContext) {
3979
4509
  })),
3980
4510
  {
3981
4511
  value: '__create__',
3982
- label: 'Create new instance',
3983
- hint: 'Provision a new consent.io instance now'
4512
+ label: 'Create new project',
4513
+ hint: 'Provision a new consent.io project now'
3984
4514
  }
3985
4515
  ]
3986
4516
  });
3987
- if (isCancel(selectedId)) throw new PromptCancelledError('instance_select');
4517
+ if (isCancel(selectedId)) throw new PromptCancelledError('project_select');
3988
4518
  if ('__create__' === selectedId) return await createInstanceInteractively(client, cliContext);
3989
4519
  const selected = instances.find((instance)=>instance.id === selectedId);
3990
4520
  if (!selected) throw new CliError('INSTANCE_NOT_FOUND');
@@ -4032,10 +4562,10 @@ const hostedModeActor = fromPromise(async ({ input })=>{
4032
4562
  };
4033
4563
  }
4034
4564
  if (!isV2ModeEnabled()) {
4035
- cliContext.logger.info('consent.io sign-in is currently disabled. Set V2=1 to enable sign-in and instance selection.');
4565
+ cliContext.logger.info('consent.io sign-in is currently disabled. Set V2=1 to enable sign-in and project selection.');
4036
4566
  const url = await promptBackendURL({
4037
- message: 'Enter your consent.io instance URL:',
4038
- placeholder: 'https://your-instance.c15t.dev',
4567
+ message: 'Enter your consent.io project URL:',
4568
+ placeholder: 'https://your-project.c15t.dev',
4039
4569
  initialURL,
4040
4570
  stage: 'consent_manual_url'
4041
4571
  });
@@ -4049,12 +4579,12 @@ const hostedModeActor = fromPromise(async ({ input })=>{
4049
4579
  options: [
4050
4580
  {
4051
4581
  value: 'sign-in',
4052
- label: 'Sign in and pick an instance',
4053
- hint: 'List existing instances or create a new one'
4582
+ label: 'Sign in and pick a project',
4583
+ hint: 'List existing projects or create a new one'
4054
4584
  },
4055
4585
  {
4056
4586
  value: 'manual-url',
4057
- label: 'Enter instance URL manually',
4587
+ label: 'Enter project URL manually',
4058
4588
  hint: 'Use an existing backend URL'
4059
4589
  }
4060
4590
  ],
@@ -4063,8 +4593,8 @@ const hostedModeActor = fromPromise(async ({ input })=>{
4063
4593
  if (isCancel(setupMethod)) throw new PromptCancelledError('consent_setup_method');
4064
4594
  if ('manual-url' === setupMethod) {
4065
4595
  const url = await promptBackendURL({
4066
- message: 'Enter your consent.io instance URL:',
4067
- placeholder: 'https://your-instance.c15t.dev',
4596
+ message: 'Enter your consent.io project URL:',
4597
+ placeholder: 'https://your-project.c15t.dev',
4068
4598
  initialURL,
4069
4599
  stage: 'consent_manual_url'
4070
4600
  });
@@ -4076,7 +4606,7 @@ const hostedModeActor = fromPromise(async ({ input })=>{
4076
4606
  await runConsentLogin(cliContext);
4077
4607
  const instance = await selectOrCreateInstance(cliContext);
4078
4608
  await setSelectedInstanceId(instance.id);
4079
- cliContext.logger.info(`Using instance ${picocolors.cyan(instance.name)} (${picocolors.dim(instance.id)})`);
4609
+ cliContext.logger.info(`Using project ${picocolors.cyan(instance.name)} (${picocolors.dim(instance.id)})`);
4080
4610
  return {
4081
4611
  url: instance.url,
4082
4612
  provider: 'consent.io'
@@ -4093,7 +4623,7 @@ const backendOptionsActor = fromPromise(async ({ input })=>{
4093
4623
  if ('@c15t/nextjs' === cliContext.framework.pkg) {
4094
4624
  cliContext.logger.info('Learn more about Next.js Rewrites: https://nextjs.org/docs/app/api-reference/config/next-config-js/rewrites');
4095
4625
  const proxyResult = await __rspack_external__clack_prompts_3cae1695.confirm({
4096
- message: 'Proxy requests to your instance with Next.js Rewrites? (Recommended)',
4626
+ message: 'Proxy requests to your project with Next.js Rewrites? (Recommended)',
4097
4627
  initialValue: true
4098
4628
  });
4099
4629
  if (isCancel(proxyResult)) throw new PromptCancelledError('proxy_nextjs');
@@ -5033,6 +5563,29 @@ const generateMachine = setup({
5033
5563
  }
5034
5564
  }
5035
5565
  });
5566
+ function getSetupTrigger(modeArg, resumed) {
5567
+ if (resumed) return 'resume';
5568
+ if (modeArg) return 'arg';
5569
+ return 'interactive';
5570
+ }
5571
+ function normalizeSetupReason(finalState, finalContext) {
5572
+ if ('complete' === finalState) return;
5573
+ if ('preflightError' === finalState || !finalContext.preflightPassed && finalContext.preflightChecks.some((check)=>'fail' === check.status)) return 'preflight_failed';
5574
+ if ('exited' === finalState || 'cancelled' === finalState) {
5575
+ const reason = finalContext.cancelReason?.toLowerCase();
5576
+ if (!reason) return 'user_cancelled';
5577
+ if (reason.includes('signal')) return 'signal_interrupted';
5578
+ if (reason.includes('mode selection')) return 'mode_selection_cancelled';
5579
+ if (reason.includes('hosted setup')) return 'hosted_setup_cancelled';
5580
+ if (reason.includes('backend options')) return 'backend_options_cancelled';
5581
+ if (reason.includes('frontend options')) return 'frontend_options_cancelled';
5582
+ if (reason.includes("scripts option")) return "scripts_options_cancelled";
5583
+ return 'user_cancelled';
5584
+ }
5585
+ const lastError = finalContext.errors[finalContext.errors.length - 1];
5586
+ if (lastError?.state) return `${lastError.state}_failed`;
5587
+ return 'machine_error';
5588
+ }
5036
5589
  async function runGenerateMachine(options) {
5037
5590
  const { context: cliContext, modeArg, resume = false, debug = false, persist = true } = options;
5038
5591
  const { logger, telemetry } = cliContext;
@@ -5069,7 +5622,9 @@ async function runGenerateMachine(options) {
5069
5622
  const combinedSubscriber = combineSubscribers(...subscribers);
5070
5623
  actor.subscribe((snapshot)=>combinedSubscriber(snapshot));
5071
5624
  telemetry.trackEvent(TelemetryEventName.ONBOARDING_STARTED, {
5072
- resumed: resume && void 0 !== snapshot
5625
+ resumed: resume && void 0 !== snapshot,
5626
+ trigger: getSetupTrigger(modeArg, resume && void 0 !== snapshot),
5627
+ requestedMode: modeArg ?? void 0
5073
5628
  });
5074
5629
  telemetry.flushSync();
5075
5630
  actor.start();
@@ -5085,11 +5640,28 @@ async function runGenerateMachine(options) {
5085
5640
  const duration = Date.now() - startTime;
5086
5641
  clearSnapshot(persistPath).catch(()=>{});
5087
5642
  const success = 'complete' === finalState;
5643
+ const durationMs = duration;
5644
+ const reason = normalizeSetupReason(finalState, finalContext);
5645
+ const result = success ? 'success' : 'exited' === finalState ? 'cancelled' : 'failed';
5088
5646
  telemetry.trackEvent(TelemetryEventName.ONBOARDING_COMPLETED, {
5089
5647
  success,
5648
+ result,
5649
+ reason,
5650
+ trigger: getSetupTrigger(modeArg, resume && void 0 !== snapshot),
5651
+ resumed: resume && void 0 !== snapshot,
5090
5652
  selectedMode: finalContext.selectedMode ?? void 0,
5653
+ hostedProvider: finalContext.hostedProvider ?? void 0,
5091
5654
  installDependencies: finalContext.installSucceeded,
5655
+ installConfirmed: finalContext.installConfirmed,
5656
+ installAttempted: finalContext.installAttempted,
5657
+ installSucceeded: finalContext.installSucceeded,
5658
+ dependencyCount: finalContext.dependenciesToAdd.length,
5659
+ filesCreatedCount: finalContext.filesCreated.length,
5660
+ filesModifiedCount: finalContext.filesModified.length,
5661
+ errorsCount: finalContext.errors.length,
5662
+ cancelReason: finalContext.cancelReason ?? void 0,
5092
5663
  duration,
5664
+ durationMs,
5093
5665
  finalState
5094
5666
  });
5095
5667
  resolve({
@@ -5139,6 +5711,294 @@ async function generate(context, mode) {
5139
5711
  ];
5140
5712
  return generateAction(context);
5141
5713
  }
5714
+ function instances_formatInstanceLabel(instance) {
5715
+ if (instance.organizationSlug) return `${instance.organizationSlug}/${instance.name}`;
5716
+ return instance.name;
5717
+ }
5718
+ function instances_formatInstanceRegion(instance) {
5719
+ return `(${instance.region ?? 'unknown'})`;
5720
+ }
5721
+ async function requireAuth(context) {
5722
+ if (!await isLoggedIn()) {
5723
+ context.logger.error('You must be logged in to manage projects');
5724
+ context.logger.message(`Run ${picocolors.cyan('c15t login')} to authenticate`);
5725
+ throw new CliError('AUTH_NOT_LOGGED_IN');
5726
+ }
5727
+ }
5728
+ async function listAction(context) {
5729
+ const { logger, telemetry } = context;
5730
+ const baseUrl = getControlPlaneBaseUrl();
5731
+ await requireAuth(context);
5732
+ const spinner = createTaskSpinner('Fetching projects...');
5733
+ spinner.start();
5734
+ try {
5735
+ const client = await createControlPlaneClientFromConfig(baseUrl);
5736
+ if (!client) {
5737
+ spinner.stop();
5738
+ throw new CliError('AUTH_NOT_LOGGED_IN');
5739
+ }
5740
+ const instances = await client.listInstances();
5741
+ await client.close();
5742
+ spinner.stop();
5743
+ telemetry.trackEvent(TelemetryEventName.PROJECTS_LISTED, {
5744
+ count: instances.length
5745
+ });
5746
+ if (0 === instances.length) {
5747
+ logger.message('');
5748
+ logger.message('No projects found.');
5749
+ logger.message('');
5750
+ logger.message(`Run ${picocolors.cyan('c15t projects create')} to create one`);
5751
+ return;
5752
+ }
5753
+ const selectedId = await getSelectedInstanceId();
5754
+ logger.message('');
5755
+ logger.message(picocolors.bold('Your projects:'));
5756
+ logger.message('');
5757
+ for (const instance of instances){
5758
+ const isSelected = instance.id === selectedId;
5759
+ const status = getStatusColor(instance.status);
5760
+ const marker = isSelected ? picocolors.green('▸ ') : ' ';
5761
+ const label = instances_formatInstanceLabel(instance);
5762
+ logger.message(`${marker}${picocolors.bold(label)} ${picocolors.dim(`(${instance.id})`)}`);
5763
+ logger.message(` Region: ${picocolors.cyan(instances_formatInstanceRegion(instance))}`);
5764
+ logger.message(` Status: ${status}`);
5765
+ logger.message('');
5766
+ }
5767
+ if (selectedId) logger.message(picocolors.dim('▸ indicates the currently selected project'));
5768
+ } catch (error) {
5769
+ spinner.stop();
5770
+ throw error;
5771
+ }
5772
+ }
5773
+ async function selectAction(context) {
5774
+ const { logger, telemetry, commandArgs } = context;
5775
+ const baseUrl = getControlPlaneBaseUrl();
5776
+ await requireAuth(context);
5777
+ const spinner = createTaskSpinner('Fetching projects...');
5778
+ spinner.start();
5779
+ try {
5780
+ const client = await createControlPlaneClientFromConfig(baseUrl);
5781
+ if (!client) {
5782
+ spinner.stop();
5783
+ throw new CliError('AUTH_NOT_LOGGED_IN');
5784
+ }
5785
+ const instances = await client.listInstances();
5786
+ await client.close();
5787
+ spinner.stop();
5788
+ if (0 === instances.length) {
5789
+ logger.message('No projects found.');
5790
+ logger.message(`Run ${picocolors.cyan('c15t projects create')} to create one`);
5791
+ return;
5792
+ }
5793
+ let selectedInstance;
5794
+ if (commandArgs.length > 0) {
5795
+ const query = commandArgs[0];
5796
+ const found = instances.find((i)=>i.id === query || i.name === query || instances_formatInstanceLabel(i) === query);
5797
+ if (!found) throw new CliError('INSTANCE_NOT_FOUND', {
5798
+ details: `No project found with ID, name, or org/name: ${query}`
5799
+ });
5800
+ selectedInstance = found;
5801
+ } else {
5802
+ const currentId = await getSelectedInstanceId();
5803
+ const result = await __rspack_external__clack_prompts_3cae1695.select({
5804
+ message: 'Select a project:',
5805
+ options: instances.map((instance)=>({
5806
+ value: instance.id,
5807
+ label: instances_formatInstanceLabel(instance),
5808
+ hint: instance.id === currentId ? `(currently selected) • ${instances_formatInstanceRegion(instance)}` : instances_formatInstanceRegion(instance)
5809
+ }))
5810
+ });
5811
+ if (__rspack_external__clack_prompts_3cae1695.isCancel(result)) return void logger.info('Selection cancelled');
5812
+ selectedInstance = instances.find((i)=>i.id === result);
5813
+ }
5814
+ await setSelectedInstanceId(selectedInstance.id);
5815
+ telemetry.trackEvent(TelemetryEventName.PROJECT_SELECTED, {
5816
+ projectId: selectedInstance.id
5817
+ });
5818
+ logger.success(`Selected project: ${picocolors.cyan(instances_formatInstanceLabel(selectedInstance))}`);
5819
+ logger.message(`Region: ${instances_formatInstanceRegion(selectedInstance)}`);
5820
+ } catch (error) {
5821
+ spinner.stop();
5822
+ throw error;
5823
+ }
5824
+ }
5825
+ async function createAction(context) {
5826
+ const { logger, telemetry, commandArgs } = context;
5827
+ const baseUrl = getControlPlaneBaseUrl();
5828
+ await requireAuth(context);
5829
+ const client = await createControlPlaneClientFromConfig(baseUrl);
5830
+ if (!client) throw new CliError('AUTH_NOT_LOGGED_IN');
5831
+ try {
5832
+ const preloadSpinner = createTaskSpinner('Loading organizations and regions...');
5833
+ preloadSpinner.start();
5834
+ let organizations;
5835
+ let regions;
5836
+ try {
5837
+ [organizations, regions] = await Promise.all([
5838
+ client.listOrganizations(),
5839
+ client.listRegions()
5840
+ ]);
5841
+ } finally{
5842
+ preloadSpinner.stop();
5843
+ }
5844
+ if (0 === organizations.length) throw new CliError('API_ERROR', {
5845
+ details: 'No organizations available for this account'
5846
+ });
5847
+ if (0 === regions.length) throw new CliError('API_ERROR', {
5848
+ details: 'No provisioning regions available'
5849
+ });
5850
+ let name;
5851
+ if (commandArgs.length > 0) {
5852
+ const providedName = commandArgs[0];
5853
+ if (!providedName) throw new CliError('INSTANCE_NAME_INVALID', {
5854
+ details: 'Project slug is required'
5855
+ });
5856
+ const error = validateInstanceName(providedName);
5857
+ if (error) throw new CliError('INSTANCE_NAME_INVALID', {
5858
+ details: error
5859
+ });
5860
+ name = providedName;
5861
+ } else {
5862
+ const result = await __rspack_external__clack_prompts_3cae1695.text({
5863
+ message: 'Project slug:',
5864
+ placeholder: 'my-app',
5865
+ validate: (value)=>validateInstanceName(value?.trim() ?? '')
5866
+ });
5867
+ if (__rspack_external__clack_prompts_3cae1695.isCancel(result)) return void logger.info('Creation cancelled');
5868
+ name = result;
5869
+ }
5870
+ name = name.trim();
5871
+ const nameValidationError = validateInstanceName(name);
5872
+ if (nameValidationError) throw new CliError('INSTANCE_NAME_INVALID', {
5873
+ details: nameValidationError
5874
+ });
5875
+ const orgSelection = await __rspack_external__clack_prompts_3cae1695.select({
5876
+ message: 'Select organization:',
5877
+ options: organizations.map((org)=>({
5878
+ value: org.organizationSlug,
5879
+ label: org.organizationName,
5880
+ hint: `${org.organizationSlug} • ${org.role}`
5881
+ })),
5882
+ initialValue: organizations[0]?.organizationSlug
5883
+ });
5884
+ if (__rspack_external__clack_prompts_3cae1695.isCancel(orgSelection)) return void logger.info('Creation cancelled');
5885
+ const v2Regions = regions.filter((region)=>'v2' === region.family);
5886
+ if (0 === v2Regions.length) throw new CliError('API_ERROR', {
5887
+ details: 'No v2 provisioning regions available'
5888
+ });
5889
+ const regionSelection = await __rspack_external__clack_prompts_3cae1695.select({
5890
+ message: 'Select V2 region:',
5891
+ options: v2Regions.map((region)=>({
5892
+ value: region.id,
5893
+ label: region.id,
5894
+ hint: region.label
5895
+ })),
5896
+ initialValue: v2Regions.find((region)=>'us-east-1' === region.id)?.id
5897
+ });
5898
+ if (__rspack_external__clack_prompts_3cae1695.isCancel(regionSelection)) return void logger.info('Creation cancelled');
5899
+ const spinner = createTaskSpinner(`Creating project "${name}"...`);
5900
+ spinner.start();
5901
+ let instance;
5902
+ try {
5903
+ instance = await client.createInstance({
5904
+ name,
5905
+ config: {
5906
+ organizationSlug: orgSelection,
5907
+ region: regionSelection
5908
+ }
5909
+ });
5910
+ spinner.success('Project created');
5911
+ } catch (error) {
5912
+ spinner.error('Failed to create project');
5913
+ throw error;
5914
+ }
5915
+ telemetry.trackEvent(TelemetryEventName.PROJECT_CREATED, {
5916
+ projectId: instance.id
5917
+ });
5918
+ logger.message('');
5919
+ logger.message(`Name: ${picocolors.bold(instance.name)}`);
5920
+ logger.message(`ID: ${picocolors.dim(instance.id)}`);
5921
+ logger.message(`URL: ${picocolors.cyan(instance.url)}`);
5922
+ logger.message('');
5923
+ logger.info('Created as a v2 development project. Enable production mode in the dashboard when you are ready.');
5924
+ const shouldSelect = await __rspack_external__clack_prompts_3cae1695.confirm({
5925
+ message: 'Would you like to use this project for your project?',
5926
+ initialValue: true
5927
+ });
5928
+ if (shouldSelect && !__rspack_external__clack_prompts_3cae1695.isCancel(shouldSelect)) {
5929
+ await setSelectedInstanceId(instance.id);
5930
+ logger.info('Project selected');
5931
+ }
5932
+ } finally{
5933
+ await client.close();
5934
+ }
5935
+ }
5936
+ function getStatusColor(status) {
5937
+ switch(status){
5938
+ case 'active':
5939
+ return picocolors.green('active');
5940
+ case 'inactive':
5941
+ return picocolors.yellow('inactive');
5942
+ case 'pending':
5943
+ return picocolors.blue('pending');
5944
+ default:
5945
+ return status;
5946
+ }
5947
+ }
5948
+ async function projectsAction(context) {
5949
+ const { commandArgs } = context;
5950
+ const subcommand = commandArgs[0];
5951
+ switch(subcommand){
5952
+ case 'list':
5953
+ context.commandArgs = commandArgs.slice(1);
5954
+ return listAction(context);
5955
+ case 'select':
5956
+ context.commandArgs = commandArgs.slice(1);
5957
+ return selectAction(context);
5958
+ case 'create':
5959
+ context.commandArgs = commandArgs.slice(1);
5960
+ return createAction(context);
5961
+ default:
5962
+ return listAction(context);
5963
+ }
5964
+ }
5965
+ const projectsCommand = {
5966
+ name: 'projects',
5967
+ label: 'Projects',
5968
+ hint: 'Manage your c15t projects',
5969
+ description: 'List, select, and create c15t projects',
5970
+ action: projectsAction,
5971
+ subcommands: [
5972
+ {
5973
+ name: 'list',
5974
+ label: 'List',
5975
+ hint: 'List all projects',
5976
+ description: 'List all c15t projects for your account',
5977
+ action: listAction
5978
+ },
5979
+ {
5980
+ name: 'select',
5981
+ label: 'Select',
5982
+ hint: 'Select a project',
5983
+ description: 'Select a project for your local project',
5984
+ action: selectAction
5985
+ },
5986
+ {
5987
+ name: 'create',
5988
+ label: 'Create',
5989
+ hint: 'Create a new project',
5990
+ description: 'Create a new c15t project',
5991
+ action: createAction
5992
+ }
5993
+ ]
5994
+ };
5995
+ ({
5996
+ ...projectsCommand,
5997
+ name: 'instances',
5998
+ label: 'Instances',
5999
+ description: 'Alias for `c15t projects`',
6000
+ hidden: true
6001
+ });
5142
6002
  const validLogLevels = [
5143
6003
  'error',
5144
6004
  'warn',
@@ -5194,7 +6054,7 @@ const logger_logMessage = (logLevel, message, ...args)=>{
5194
6054
  }
5195
6055
  };
5196
6056
  const logger_createCliLogger = (level)=>{
5197
- const baseLogger = createLogger({
6057
+ const baseLogger = logger_createLogger({
5198
6058
  level,
5199
6059
  appName: 'c15t',
5200
6060
  log: (logLevel, message, ...args)=>{
@@ -5205,23 +6065,26 @@ const logger_createCliLogger = (level)=>{
5205
6065
  extendedLogger.message = (message)=>{
5206
6066
  __rspack_external__clack_prompts_3cae1695.log.message(message);
5207
6067
  };
5208
- extendedLogger.note = (message, ...args)=>{
5209
- const messageStr = 'string' == typeof message ? message : String(message);
5210
- const title = args.length > 0 && 'string' == typeof args[0] ? args[0] : void 0;
5211
- __rspack_external__clack_prompts_3cae1695.note(messageStr, title, {
6068
+ extendedLogger.note = (content, title)=>{
6069
+ __rspack_external__clack_prompts_3cae1695.note(content, title, {
5212
6070
  format: (line)=>line
5213
6071
  });
5214
6072
  };
5215
- extendedLogger.success = (message, ...args)=>{
5216
- logger_logMessage('success', message, ...args);
6073
+ extendedLogger.success = (message)=>{
6074
+ logger_logMessage('success', message);
5217
6075
  };
5218
- extendedLogger.failed = (message, ...args)=>{
5219
- logger_logMessage('failed', message, ...args);
6076
+ extendedLogger.failed = (message)=>{
6077
+ logger_logMessage('failed', message);
5220
6078
  process.exit(0);
5221
6079
  };
5222
6080
  extendedLogger.outro = (message)=>{
5223
6081
  __rspack_external__clack_prompts_3cae1695.outro(message);
5224
6082
  };
6083
+ extendedLogger.step = (current, total, label)=>{
6084
+ const filled = picocolors.green('█'.repeat(current));
6085
+ const empty = picocolors.dim('░'.repeat(total - current));
6086
+ __rspack_external__clack_prompts_3cae1695.log.step(`[${filled}${empty}] Step ${current}/${total}: ${label}`);
6087
+ };
5225
6088
  return extendedLogger;
5226
6089
  };
5227
6090
  async function addAndInstallDependenciesViaPM(projectRoot, dependencies, packageManager) {
@@ -6279,10 +7142,13 @@ async function createCliContext(rawArgs, cwd, commands) {
6279
7142
  const telemetryDisabled = true === parsedFlags['no-telemetry'];
6280
7143
  const telemetryDebug = true === parsedFlags['telemetry-debug'];
6281
7144
  try {
6282
- context.telemetry = createTelemetry({
7145
+ context.telemetry = telemetry_createTelemetry({
6283
7146
  disabled: telemetryDisabled,
6284
7147
  debug: telemetryDebug,
6285
7148
  defaultProperties: {
7149
+ entryCommand: commandName ?? 'interactive',
7150
+ commandArgsCount: commandArgs.length,
7151
+ enabledFlags: Object.entries(parsedFlags).filter(([, value])=>false !== value && void 0 !== value).map(([key])=>key).sort(),
6286
7152
  cliVersion: context.fs.getPackageInfo().version,
6287
7153
  framework: context.framework.framework ?? 'unknown',
6288
7154
  frameworkVersion: context.framework.frameworkVersion ?? 'unknown',
@@ -6297,9 +7163,20 @@ async function createCliContext(rawArgs, cwd, commands) {
6297
7163
  if (telemetryDisabled) logger.debug('Telemetry is disabled by user preference');
6298
7164
  else if (telemetryDebug) logger.debug('Telemetry initialized with debug mode enabled');
6299
7165
  else logger.debug('Telemetry initialized');
7166
+ context.telemetry.trackEvent(TelemetryEventName.CLI_ENVIRONMENT_DETECTED, {
7167
+ command: commandName ?? 'interactive',
7168
+ projectRootChanged: context.projectRoot !== cwd,
7169
+ framework: context.framework.framework ?? 'unknown',
7170
+ frameworkVersion: context.framework.frameworkVersion ?? 'unknown',
7171
+ packageManager: context.packageManager.name,
7172
+ packageManagerVersion: context.packageManager.version ?? 'unknown',
7173
+ hasReact: context.framework.hasReact,
7174
+ reactVersion: context.framework.reactVersion ?? 'unknown',
7175
+ tailwindVersion: context.framework.tailwindVersion ?? 'unknown'
7176
+ });
6300
7177
  } catch {
6301
7178
  logger.warn('Failed to initialize telemetry, continuing with telemetry disabled');
6302
- context.telemetry = createTelemetry({
7179
+ context.telemetry = telemetry_createTelemetry({
6303
7180
  disabled: true,
6304
7181
  logger: context.logger
6305
7182
  });
@@ -6363,6 +7240,21 @@ const src_commands = [
6363
7240
  await open_0(constants_URLS.GITHUB);
6364
7241
  logger.success('Thank you for your support!');
6365
7242
  }
7243
+ },
7244
+ {
7245
+ name: 'projects',
7246
+ label: 'Projects',
7247
+ hint: 'Manage your c15t projects',
7248
+ description: 'List, select, and create c15t projects.',
7249
+ action: (context)=>projectsAction(context)
7250
+ },
7251
+ {
7252
+ name: 'instances',
7253
+ label: 'Instances',
7254
+ hint: 'Alias for `projects`',
7255
+ description: 'Alias for `c15t projects`.',
7256
+ action: (context)=>projectsAction(context),
7257
+ hidden: true
6366
7258
  }
6367
7259
  ];
6368
7260
  async function main() {
@@ -6438,7 +7330,7 @@ flag or set ${picocolors.cyan('C15T_TELEMETRY_DISABLED=1')} in your environment.
6438
7330
  } else {
6439
7331
  logger.debug('No command specified, entering interactive selection.');
6440
7332
  telemetry.trackEvent(TelemetryEventName.INTERACTIVE_MENU_OPENED, {});
6441
- const promptOptions = src_commands.map((cmd)=>({
7333
+ const promptOptions = src_commands.filter((cmd)=>!cmd.hidden).map((cmd)=>({
6442
7334
  value: cmd.name,
6443
7335
  label: cmd.label,
6444
7336
  hint: cmd.hint
@@ -6506,4 +7398,4 @@ flag or set ${picocolors.cyan('C15T_TELEMETRY_DISABLED=1')} in your environment.
6506
7398
  await telemetry.shutdown();
6507
7399
  }
6508
7400
  main();
6509
- export { __webpack_require__temp as __webpack_require__, LAYOUT_PATTERNS, PAGES_APP_PATTERNS, STORAGE_MODES, config_store_getAccessToken as getAccessToken, constants_REGEX, getAuthState, logger_formatLogMessage, main, setSelectedInstanceId, storeTokens };
7401
+ export { __webpack_require__temp as __webpack_require__, LAYOUT_PATTERNS, PAGES_APP_PATTERNS, STORAGE_MODES, config_store_getAccessToken as getAccessToken, constants_REGEX, ensureGlobalCssStylesheetImports, formatSearchedCssPaths, getAuthState, getSelectedInstanceId, isLoggedIn, logger_formatLogMessage, main, setSelectedInstanceId, storeTokens };