claudekit-cli 1.2.2 → 1.3.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/dist/index.js CHANGED
@@ -5800,11 +5800,6 @@ var require_ignore = __commonJS((exports, module) => {
5800
5800
  }
5801
5801
  });
5802
5802
 
5803
- // src/index.ts
5804
- import { readFileSync } from "fs";
5805
- import { join as join6 } from "path";
5806
- import { fileURLToPath } from "url";
5807
-
5808
5803
  // node_modules/cac/dist/index.mjs
5809
5804
  import { EventEmitter } from "events";
5810
5805
  function toArr(any) {
@@ -6408,7 +6403,7 @@ var cac = (name = "") => new CAC(name);
6408
6403
 
6409
6404
  // src/commands/new.ts
6410
6405
  var import_fs_extra2 = __toESM(require_lib(), 1);
6411
- import { resolve } from "node:path";
6406
+ import { resolve as resolve2 } from "node:path";
6412
6407
 
6413
6408
  // src/lib/auth.ts
6414
6409
  import { execSync } from "node:child_process";
@@ -10930,7 +10925,8 @@ var KitType = exports_external.enum(["engineer", "marketing"]);
10930
10925
  var NewCommandOptionsSchema = exports_external.object({
10931
10926
  dir: exports_external.string().default("."),
10932
10927
  kit: KitType.optional(),
10933
- version: exports_external.string().optional()
10928
+ version: exports_external.string().optional(),
10929
+ force: exports_external.boolean().default(false)
10934
10930
  });
10935
10931
  var UpdateCommandOptionsSchema = exports_external.object({
10936
10932
  dir: exports_external.string().default("."),
@@ -11328,7 +11324,7 @@ var import_ignore = __toESM(require_ignore(), 1);
11328
11324
  import { createWriteStream as createWriteStream2 } from "node:fs";
11329
11325
  import { mkdir as mkdir3 } from "node:fs/promises";
11330
11326
  import { tmpdir } from "node:os";
11331
- import { join as join3 } from "node:path";
11327
+ import { join as join3, relative, resolve } from "node:path";
11332
11328
 
11333
11329
  // node_modules/@isaacs/fs-minipass/dist/esm/index.js
11334
11330
  import EE from "events";
@@ -21204,6 +21200,7 @@ function createSpinner(options) {
21204
21200
 
21205
21201
  // src/lib/download.ts
21206
21202
  class DownloadManager {
21203
+ static MAX_EXTRACTION_SIZE = 500 * 1024 * 1024;
21207
21204
  static EXCLUDE_PATTERNS = [
21208
21205
  ".git",
21209
21206
  ".git/**",
@@ -21215,10 +21212,26 @@ class DownloadManager {
21215
21212
  "Thumbs.db",
21216
21213
  "*.log"
21217
21214
  ];
21215
+ totalExtractedSize = 0;
21218
21216
  shouldExclude(filePath) {
21219
21217
  const ig = import_ignore.default().add(DownloadManager.EXCLUDE_PATTERNS);
21220
21218
  return ig.ignores(filePath);
21221
21219
  }
21220
+ isPathSafe(basePath, targetPath) {
21221
+ const resolvedBase = resolve(basePath);
21222
+ const resolvedTarget = resolve(targetPath);
21223
+ const relativePath = relative(resolvedBase, resolvedTarget);
21224
+ return !relativePath.startsWith("..") && !relativePath.startsWith("/") && resolvedTarget.startsWith(resolvedBase);
21225
+ }
21226
+ checkExtractionSize(fileSize) {
21227
+ this.totalExtractedSize += fileSize;
21228
+ if (this.totalExtractedSize > DownloadManager.MAX_EXTRACTION_SIZE) {
21229
+ throw new ExtractionError(`Archive exceeds maximum extraction size of ${this.formatBytes(DownloadManager.MAX_EXTRACTION_SIZE)}. Possible archive bomb detected.`);
21230
+ }
21231
+ }
21232
+ resetExtractionSize() {
21233
+ this.totalExtractedSize = 0;
21234
+ }
21222
21235
  async downloadAsset(asset, destDir) {
21223
21236
  try {
21224
21237
  const destPath = join3(destDir, asset.name);
@@ -21328,6 +21341,7 @@ class DownloadManager {
21328
21341
  async extractArchive(archivePath, destDir, archiveType) {
21329
21342
  const spinner = createSpinner("Extracting files...").start();
21330
21343
  try {
21344
+ this.resetExtractionSize();
21331
21345
  const detectedType = archiveType || this.detectArchiveType(archivePath);
21332
21346
  await mkdir3(destDir, { recursive: true });
21333
21347
  if (detectedType === "tar.gz") {
@@ -21398,8 +21412,8 @@ class DownloadManager {
21398
21412
  }
21399
21413
  }
21400
21414
  isWrapperDirectory(dirName) {
21401
- const versionPattern = /^[\w-]+-v?\d+\.\d+\.\d+/;
21402
- const hashPattern = /^[\w-]+-[a-f0-9]{7,}$/;
21415
+ const versionPattern = /^[\w-]+-v?\d+\.\d+\.\d+(-[\w.]+)?$/;
21416
+ const hashPattern = /^[\w-]+-[a-f0-9]{7,40}$/;
21403
21417
  return versionPattern.test(dirName) || hashPattern.test(dirName);
21404
21418
  }
21405
21419
  async extractZip(archivePath, destDir) {
@@ -21447,13 +21461,17 @@ class DownloadManager {
21447
21461
  }
21448
21462
  async moveDirectoryContents(sourceDir, destDir) {
21449
21463
  const { readdir, stat, mkdir: mkdirPromise, copyFile } = await import("node:fs/promises");
21450
- const { join: pathJoin, relative } = await import("node:path");
21464
+ const { join: pathJoin, relative: relative2 } = await import("node:path");
21451
21465
  await mkdirPromise(destDir, { recursive: true });
21452
21466
  const entries = await readdir(sourceDir);
21453
21467
  for (const entry of entries) {
21454
21468
  const sourcePath = pathJoin(sourceDir, entry);
21455
21469
  const destPath = pathJoin(destDir, entry);
21456
- const relativePath = relative(sourceDir, sourcePath);
21470
+ const relativePath = relative2(sourceDir, sourcePath);
21471
+ if (!this.isPathSafe(destDir, destPath)) {
21472
+ logger.warning(`Skipping unsafe path: ${relativePath}`);
21473
+ throw new ExtractionError(`Path traversal attempt detected: ${relativePath}`);
21474
+ }
21457
21475
  if (this.shouldExclude(relativePath)) {
21458
21476
  logger.debug(`Excluding: ${relativePath}`);
21459
21477
  continue;
@@ -21462,19 +21480,24 @@ class DownloadManager {
21462
21480
  if (entryStat.isDirectory()) {
21463
21481
  await this.copyDirectory(sourcePath, destPath);
21464
21482
  } else {
21483
+ this.checkExtractionSize(entryStat.size);
21465
21484
  await copyFile(sourcePath, destPath);
21466
21485
  }
21467
21486
  }
21468
21487
  }
21469
21488
  async copyDirectory(sourceDir, destDir) {
21470
21489
  const { readdir, stat, mkdir: mkdirPromise, copyFile } = await import("node:fs/promises");
21471
- const { join: pathJoin, relative } = await import("node:path");
21490
+ const { join: pathJoin, relative: relative2 } = await import("node:path");
21472
21491
  await mkdirPromise(destDir, { recursive: true });
21473
21492
  const entries = await readdir(sourceDir);
21474
21493
  for (const entry of entries) {
21475
21494
  const sourcePath = pathJoin(sourceDir, entry);
21476
21495
  const destPath = pathJoin(destDir, entry);
21477
- const relativePath = relative(sourceDir, sourcePath);
21496
+ const relativePath = relative2(sourceDir, sourcePath);
21497
+ if (!this.isPathSafe(destDir, destPath)) {
21498
+ logger.warning(`Skipping unsafe path: ${relativePath}`);
21499
+ throw new ExtractionError(`Path traversal attempt detected: ${relativePath}`);
21500
+ }
21478
21501
  if (this.shouldExclude(relativePath)) {
21479
21502
  logger.debug(`Excluding: ${relativePath}`);
21480
21503
  continue;
@@ -21483,6 +21506,7 @@ class DownloadManager {
21483
21506
  if (entryStat.isDirectory()) {
21484
21507
  await this.copyDirectory(sourcePath, destPath);
21485
21508
  } else {
21509
+ this.checkExtractionSize(entryStat.size);
21486
21510
  await copyFile(sourcePath, destPath);
21487
21511
  }
21488
21512
  }
@@ -21504,22 +21528,28 @@ class DownloadManager {
21504
21528
  const entries = await readdir(extractDir);
21505
21529
  logger.debug(`Extracted files: ${entries.join(", ")}`);
21506
21530
  if (entries.length === 0) {
21507
- logger.warning("Extraction resulted in no files");
21508
- return false;
21531
+ throw new ExtractionError("Extraction resulted in no files");
21509
21532
  }
21510
21533
  const criticalPaths = [".claude", "CLAUDE.md"];
21534
+ const missingPaths = [];
21511
21535
  for (const path8 of criticalPaths) {
21512
21536
  try {
21513
21537
  await access(pathJoin(extractDir, path8), constants2.F_OK);
21514
21538
  logger.debug(`✓ Found: ${path8}`);
21515
21539
  } catch {
21516
21540
  logger.warning(`Expected path not found: ${path8}`);
21541
+ missingPaths.push(path8);
21517
21542
  }
21518
21543
  }
21519
- return true;
21544
+ if (missingPaths.length > 0) {
21545
+ logger.warning(`Some expected paths are missing: ${missingPaths.join(", ")}. This may not be a ClaudeKit project.`);
21546
+ }
21547
+ logger.debug("Extraction validation passed");
21520
21548
  } catch (error2) {
21521
- logger.error(`Validation failed: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
21522
- return false;
21549
+ if (error2 instanceof ExtractionError) {
21550
+ throw error2;
21551
+ }
21552
+ throw new ExtractionError(`Validation failed: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
21523
21553
  }
21524
21554
  }
21525
21555
  async createTempDir() {
@@ -21679,7 +21709,7 @@ class GitHubClient {
21679
21709
  }
21680
21710
 
21681
21711
  // src/lib/merge.ts
21682
- import { join as join4, relative } from "node:path";
21712
+ import { join as join4, relative as relative2 } from "node:path";
21683
21713
  var import_fs_extra = __toESM(require_lib(), 1);
21684
21714
  var import_ignore2 = __toESM(require_ignore(), 1);
21685
21715
  class FileMerger {
@@ -21705,7 +21735,7 @@ class FileMerger {
21705
21735
  const conflicts = [];
21706
21736
  const files = await this.getFiles(sourceDir);
21707
21737
  for (const file of files) {
21708
- const relativePath = relative(sourceDir, file);
21738
+ const relativePath = relative2(sourceDir, file);
21709
21739
  if (this.ig.ignores(relativePath)) {
21710
21740
  continue;
21711
21741
  }
@@ -21721,7 +21751,7 @@ class FileMerger {
21721
21751
  let copiedCount = 0;
21722
21752
  let skippedCount = 0;
21723
21753
  for (const file of files) {
21724
- const relativePath = relative(sourceDir, file);
21754
+ const relativePath = relative2(sourceDir, file);
21725
21755
  if (this.ig.ignores(relativePath)) {
21726
21756
  logger.debug(`Skipping protected file: ${relativePath}`);
21727
21757
  skippedCount++;
@@ -21859,27 +21889,42 @@ async function newCommand(options) {
21859
21889
  prompts.intro("\uD83D\uDE80 ClaudeKit - Create New Project");
21860
21890
  try {
21861
21891
  const validOptions = NewCommandOptionsSchema.parse(options);
21892
+ const isNonInteractive = !process.stdin.isTTY || process.env.CI === "true" || process.env.NON_INTERACTIVE === "true";
21862
21893
  const config = await ConfigManager.get();
21863
21894
  let kit = validOptions.kit || config.defaults?.kit;
21864
21895
  if (!kit) {
21896
+ if (isNonInteractive) {
21897
+ throw new Error("Kit must be specified via --kit flag in non-interactive mode");
21898
+ }
21865
21899
  kit = await prompts.selectKit();
21866
21900
  }
21867
21901
  const kitConfig = AVAILABLE_KITS[kit];
21868
21902
  logger.info(`Selected kit: ${kitConfig.name}`);
21869
21903
  let targetDir = validOptions.dir || config.defaults?.dir || ".";
21870
21904
  if (!validOptions.dir && !config.defaults?.dir) {
21871
- targetDir = await prompts.getDirectory(targetDir);
21905
+ if (isNonInteractive) {
21906
+ targetDir = ".";
21907
+ } else {
21908
+ targetDir = await prompts.getDirectory(targetDir);
21909
+ }
21872
21910
  }
21873
- const resolvedDir = resolve(targetDir);
21911
+ const resolvedDir = resolve2(targetDir);
21874
21912
  logger.info(`Target directory: ${resolvedDir}`);
21875
21913
  if (await import_fs_extra2.pathExists(resolvedDir)) {
21876
21914
  const files = await import_fs_extra2.readdir(resolvedDir);
21877
21915
  const isEmpty = files.length === 0;
21878
21916
  if (!isEmpty) {
21879
- const continueAnyway = await prompts.confirm("Directory is not empty. Files may be overwritten. Continue?");
21880
- if (!continueAnyway) {
21881
- logger.warning("Operation cancelled");
21882
- return;
21917
+ if (isNonInteractive) {
21918
+ if (!validOptions.force) {
21919
+ throw new Error("Directory is not empty. Use --force flag to overwrite in non-interactive mode");
21920
+ }
21921
+ logger.info("Directory is not empty. Proceeding with --force flag");
21922
+ } else {
21923
+ const continueAnyway = await prompts.confirm("Directory is not empty. Files may be overwritten. Continue?");
21924
+ if (!continueAnyway) {
21925
+ logger.warning("Operation cancelled");
21926
+ return;
21927
+ }
21883
21928
  }
21884
21929
  }
21885
21930
  }
@@ -21953,11 +21998,11 @@ bun run dev`, "Next steps");
21953
21998
 
21954
21999
  // src/commands/update.ts
21955
22000
  var import_fs_extra4 = __toESM(require_lib(), 1);
21956
- import { resolve as resolve3 } from "node:path";
22001
+ import { resolve as resolve4 } from "node:path";
21957
22002
 
21958
22003
  // src/utils/file-scanner.ts
21959
22004
  var import_fs_extra3 = __toESM(require_lib(), 1);
21960
- import { join as join5, relative as relative2, resolve as resolve2 } from "node:path";
22005
+ import { join as join5, relative as relative3, resolve as resolve3 } from "node:path";
21961
22006
  class FileScanner {
21962
22007
  static async getFiles(dirPath, relativeTo) {
21963
22008
  const basePath = relativeTo || dirPath;
@@ -21982,7 +22027,7 @@ class FileScanner {
21982
22027
  const subFiles = await FileScanner.getFiles(fullPath, basePath);
21983
22028
  files.push(...subFiles);
21984
22029
  } else if (stats.isFile()) {
21985
- const relativePath = relative2(basePath, fullPath);
22030
+ const relativePath = relative3(basePath, fullPath);
21986
22031
  files.push(relativePath);
21987
22032
  }
21988
22033
  }
@@ -22010,8 +22055,8 @@ class FileScanner {
22010
22055
  return customFiles;
22011
22056
  }
22012
22057
  static isSafePath(basePath, targetPath) {
22013
- const resolvedBase = resolve2(basePath);
22014
- const resolvedTarget = resolve2(targetPath);
22058
+ const resolvedBase = resolve3(basePath);
22059
+ const resolvedTarget = resolve3(targetPath);
22015
22060
  return resolvedTarget.startsWith(resolvedBase);
22016
22061
  }
22017
22062
  }
@@ -22033,7 +22078,7 @@ async function updateCommand(options) {
22033
22078
  if (!validOptions.dir && !config.defaults?.dir) {
22034
22079
  targetDir = await prompts.getDirectory(targetDir);
22035
22080
  }
22036
- const resolvedDir = resolve3(targetDir);
22081
+ const resolvedDir = resolve4(targetDir);
22037
22082
  logger.info(`Target directory: ${resolvedDir}`);
22038
22083
  if (!await import_fs_extra4.pathExists(resolvedDir)) {
22039
22084
  logger.error(`Directory does not exist: ${resolvedDir}`);
@@ -22309,6 +22354,10 @@ class Logger2 {
22309
22354
  }
22310
22355
  }
22311
22356
  var logger2 = new Logger2;
22357
+ // src/version.json
22358
+ var version_default = {
22359
+ version: "1.2.1"
22360
+ };
22312
22361
 
22313
22362
  // src/index.ts
22314
22363
  if (process.stdout.setEncoding) {
@@ -22317,12 +22366,11 @@ if (process.stdout.setEncoding) {
22317
22366
  if (process.stderr.setEncoding) {
22318
22367
  process.stderr.setEncoding("utf8");
22319
22368
  }
22320
- var __dirname2 = fileURLToPath(new URL(".", import.meta.url));
22321
- var packageJson = JSON.parse(readFileSync(join6(__dirname2, "../package.json"), "utf-8"));
22369
+ var packageVersion = version_default.version;
22322
22370
  var cli = cac("ck");
22323
22371
  cli.option("--verbose, -v", "Enable verbose logging for debugging");
22324
22372
  cli.option("--log-file <path>", "Write logs to file");
22325
- cli.command("new", "Bootstrap a new ClaudeKit project").option("--dir <dir>", "Target directory (default: .)").option("--kit <kit>", "Kit to use (engineer, marketing)").option("--version <version>", "Specific version to download (default: latest)").action(async (options) => {
22373
+ cli.command("new", "Bootstrap a new ClaudeKit project").option("--dir <dir>", "Target directory (default: .)").option("--kit <kit>", "Kit to use (engineer, marketing)").option("--version <version>", "Specific version to download (default: latest)").option("--force", "Overwrite existing files without confirmation").action(async (options) => {
22326
22374
  await newCommand(options);
22327
22375
  });
22328
22376
  cli.command("update", "Update existing ClaudeKit project").option("--dir <dir>", "Target directory (default: .)").option("--kit <kit>", "Kit to use (engineer, marketing)").option("--version <version>", "Specific version to download (default: latest)").action(async (options) => {
@@ -22331,7 +22379,7 @@ cli.command("update", "Update existing ClaudeKit project").option("--dir <dir>",
22331
22379
  cli.command("versions", "List available versions of ClaudeKit repositories").option("--kit <kit>", "Filter by specific kit (engineer, marketing)").option("--limit <limit>", "Number of releases to show (default: 30)").option("--all", "Show all releases including prereleases").action(async (options) => {
22332
22380
  await versionCommand(options);
22333
22381
  });
22334
- cli.version(packageJson.version);
22382
+ cli.version(packageVersion);
22335
22383
  cli.help();
22336
22384
  var parsed = cli.parse(process.argv, { run: false });
22337
22385
  var envVerbose = process.env.CLAUDEKIT_VERBOSE === "1" || process.env.CLAUDEKIT_VERBOSE === "true";
@@ -22343,7 +22391,7 @@ if (parsed.options.logFile) {
22343
22391
  logger2.setLogFile(parsed.options.logFile);
22344
22392
  }
22345
22393
  logger2.verbose("ClaudeKit CLI starting", {
22346
- version: packageJson.version,
22394
+ version: packageVersion,
22347
22395
  command: parsed.args[0] || "none",
22348
22396
  options: parsed.options,
22349
22397
  cwd: process.cwd(),
package/package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "name": "claudekit-cli",
3
- "version": "1.2.2",
3
+ "version": "1.3.0",
4
4
  "description": "CLI tool for bootstrapping and updating ClaudeKit projects",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "ck": "./dist/index.js"
8
8
  },
9
9
  "scripts": {
10
- "dev": "bun run src/index.ts",
11
- "build": "bun build src/index.ts --outdir dist --target node --external unzipper --external keytar --external @octokit/rest",
12
- "compile": "bun build src/index.ts --compile --outfile ck",
13
- "test": "bun test",
14
- "test:watch": "bun test --watch",
15
- "lint": "biome check .",
16
- "format": "biome format --write .",
17
- "typecheck": "tsc --noEmit"
10
+ "dev": "bun run src/index.ts >> logs.txt 2>&1",
11
+ "build": "bun build src/index.ts --outdir dist --target node --external keytar --external @octokit/rest >> logs.txt 2>&1",
12
+ "compile": "bun build src/index.ts --compile --outfile ck >> logs.txt 2>&1",
13
+ "test": "bun test >> logs.txt 2>&1",
14
+ "test:watch": "bun test --watch >> logs.txt 2>&1",
15
+ "lint": "biome check . >> logs.txt 2>&1",
16
+ "format": "biome format --write . >> logs.txt 2>&1",
17
+ "typecheck": "tsc --noEmit >> logs.txt 2>&1"
18
18
  },
19
19
  "keywords": [
20
20
  "cli",
@@ -53,7 +53,6 @@
53
53
  "@types/node": "^22.10.1",
54
54
  "@types/tar": "^6.1.13",
55
55
  "@types/tmp": "^0.2.6",
56
- "@types/unzipper": "^0.10.10",
57
56
  "semantic-release": "^24.2.0",
58
57
  "typescript": "^5.7.2"
59
58
  }
@@ -19,12 +19,19 @@ export async function newCommand(options: NewCommandOptions): Promise<void> {
19
19
  // Validate and parse options
20
20
  const validOptions = NewCommandOptionsSchema.parse(options);
21
21
 
22
+ // Detect non-interactive mode
23
+ const isNonInteractive =
24
+ !process.stdin.isTTY || process.env.CI === "true" || process.env.NON_INTERACTIVE === "true";
25
+
22
26
  // Load config for defaults
23
27
  const config = await ConfigManager.get();
24
28
 
25
29
  // Get kit selection
26
30
  let kit = validOptions.kit || config.defaults?.kit;
27
31
  if (!kit) {
32
+ if (isNonInteractive) {
33
+ throw new Error("Kit must be specified via --kit flag in non-interactive mode");
34
+ }
28
35
  kit = await prompts.selectKit();
29
36
  }
30
37
 
@@ -34,7 +41,11 @@ export async function newCommand(options: NewCommandOptions): Promise<void> {
34
41
  // Get target directory
35
42
  let targetDir = validOptions.dir || config.defaults?.dir || ".";
36
43
  if (!validOptions.dir && !config.defaults?.dir) {
37
- targetDir = await prompts.getDirectory(targetDir);
44
+ if (isNonInteractive) {
45
+ targetDir = ".";
46
+ } else {
47
+ targetDir = await prompts.getDirectory(targetDir);
48
+ }
38
49
  }
39
50
 
40
51
  const resolvedDir = resolve(targetDir);
@@ -45,12 +56,21 @@ export async function newCommand(options: NewCommandOptions): Promise<void> {
45
56
  const files = await readdir(resolvedDir);
46
57
  const isEmpty = files.length === 0;
47
58
  if (!isEmpty) {
48
- const continueAnyway = await prompts.confirm(
49
- "Directory is not empty. Files may be overwritten. Continue?",
50
- );
51
- if (!continueAnyway) {
52
- logger.warning("Operation cancelled");
53
- return;
59
+ if (isNonInteractive) {
60
+ if (!validOptions.force) {
61
+ throw new Error(
62
+ "Directory is not empty. Use --force flag to overwrite in non-interactive mode",
63
+ );
64
+ }
65
+ logger.info("Directory is not empty. Proceeding with --force flag");
66
+ } else {
67
+ const continueAnyway = await prompts.confirm(
68
+ "Directory is not empty. Files may be overwritten. Continue?",
69
+ );
70
+ if (!continueAnyway) {
71
+ logger.warning("Operation cancelled");
72
+ return;
73
+ }
54
74
  }
55
75
  }
56
76
  }
package/src/index.ts CHANGED
@@ -1,13 +1,11 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
- import { readFileSync } from "node:fs";
4
- import { join } from "node:path";
5
- import { fileURLToPath } from "node:url";
6
3
  import { cac } from "cac";
7
4
  import { newCommand } from "./commands/new.js";
8
5
  import { updateCommand } from "./commands/update.js";
9
6
  import { versionCommand } from "./commands/version.js";
10
7
  import { logger } from "./utils/logger.js";
8
+ import versionInfo from "./version.json" assert { type: "json" };
11
9
 
12
10
  // Set proper output encoding to prevent unicode rendering issues
13
11
  if (process.stdout.setEncoding) {
@@ -17,10 +15,7 @@ if (process.stderr.setEncoding) {
17
15
  process.stderr.setEncoding("utf8");
18
16
  }
19
17
 
20
- const __dirname = fileURLToPath(new URL(".", import.meta.url));
21
-
22
- // Read package.json for version
23
- const packageJson = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
18
+ const packageVersion = versionInfo.version;
24
19
 
25
20
  const cli = cac("ck");
26
21
 
@@ -34,6 +29,7 @@ cli
34
29
  .option("--dir <dir>", "Target directory (default: .)")
35
30
  .option("--kit <kit>", "Kit to use (engineer, marketing)")
36
31
  .option("--version <version>", "Specific version to download (default: latest)")
32
+ .option("--force", "Overwrite existing files without confirmation")
37
33
  .action(async (options) => {
38
34
  await newCommand(options);
39
35
  });
@@ -59,7 +55,7 @@ cli
59
55
  });
60
56
 
61
57
  // Version
62
- cli.version(packageJson.version);
58
+ cli.version(packageVersion);
63
59
 
64
60
  // Help
65
61
  cli.help();
@@ -85,7 +81,7 @@ if (parsed.options.logFile) {
85
81
 
86
82
  // Log startup info in verbose mode
87
83
  logger.verbose("ClaudeKit CLI starting", {
88
- version: packageJson.version,
84
+ version: packageVersion,
89
85
  command: parsed.args[0] || "none",
90
86
  options: parsed.options,
91
87
  cwd: process.cwd(),