code-brick 0.1.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 ADDED
@@ -0,0 +1,1830 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+ import pc13 from "picocolors";
6
+
7
+ // src/commands/init.ts
8
+ import * as p from "@clack/prompts";
9
+ import pc from "picocolors";
10
+
11
+ // src/lib/storage.ts
12
+ import fs from "fs-extra";
13
+ import path from "path";
14
+ import os from "os";
15
+ var CODEBRICK_DIR = path.join(os.homedir(), ".codebrick");
16
+ var CONFIG_FILE = path.join(CODEBRICK_DIR, "config.json");
17
+ var STORE_FILE = path.join(CODEBRICK_DIR, "store.json");
18
+ var TEMPLATES_DIR = path.join(CODEBRICK_DIR, "templates");
19
+ async function isInitialized() {
20
+ return fs.pathExists(CODEBRICK_DIR);
21
+ }
22
+ async function initializeStorage() {
23
+ await fs.ensureDir(CODEBRICK_DIR);
24
+ await fs.ensureDir(TEMPLATES_DIR);
25
+ if (!await fs.pathExists(CONFIG_FILE)) {
26
+ const defaultConfig = {
27
+ defaultRef: "main"
28
+ };
29
+ await fs.writeJson(CONFIG_FILE, defaultConfig, { spaces: 2 });
30
+ }
31
+ if (!await fs.pathExists(STORE_FILE)) {
32
+ const defaultStore = {
33
+ version: "1.0",
34
+ templates: {}
35
+ };
36
+ await fs.writeJson(STORE_FILE, defaultStore, { spaces: 2 });
37
+ }
38
+ }
39
+ async function loadStore() {
40
+ if (!await fs.pathExists(STORE_FILE)) {
41
+ return { version: "1.0", templates: {} };
42
+ }
43
+ return fs.readJson(STORE_FILE);
44
+ }
45
+ async function saveStore(store) {
46
+ await fs.writeJson(STORE_FILE, store, { spaces: 2 });
47
+ }
48
+ function getTemplatePath(name) {
49
+ return path.join(TEMPLATES_DIR, name);
50
+ }
51
+ function getTemplateMetadataPath(name) {
52
+ return path.join(getTemplatePath(name), "brick.json");
53
+ }
54
+ async function loadTemplateMetadata(name) {
55
+ const metadataPath = getTemplateMetadataPath(name);
56
+ if (!await fs.pathExists(metadataPath)) {
57
+ return null;
58
+ }
59
+ return fs.readJson(metadataPath);
60
+ }
61
+ async function saveTemplateMetadata(name, metadata) {
62
+ const metadataPath = getTemplateMetadataPath(name);
63
+ await fs.writeJson(metadataPath, metadata, { spaces: 2 });
64
+ }
65
+ async function templateExists(name) {
66
+ const store = await loadStore();
67
+ return name in store.templates;
68
+ }
69
+ async function getTemplate(name) {
70
+ const store = await loadStore();
71
+ return store.templates[name] || null;
72
+ }
73
+ async function addTemplateToStore(name, template) {
74
+ const store = await loadStore();
75
+ store.templates[name] = template;
76
+ await saveStore(store);
77
+ }
78
+ async function removeTemplateFromStore(name) {
79
+ const store = await loadStore();
80
+ delete store.templates[name];
81
+ await saveStore(store);
82
+ }
83
+ async function deleteTemplateFiles(name) {
84
+ const templatePath = getTemplatePath(name);
85
+ if (await fs.pathExists(templatePath)) {
86
+ await fs.remove(templatePath);
87
+ }
88
+ }
89
+ async function resolveTemplateName(nameOrIndex) {
90
+ const store = await loadStore();
91
+ const names = Object.keys(store.templates);
92
+ const index = parseInt(nameOrIndex, 10);
93
+ if (!isNaN(index) && index >= 0 && index < names.length) {
94
+ return names[index];
95
+ }
96
+ if (nameOrIndex in store.templates) {
97
+ return nameOrIndex;
98
+ }
99
+ return null;
100
+ }
101
+
102
+ // src/commands/init.ts
103
+ async function initCommand() {
104
+ p.intro(pc.cyan("\u{1F9F1} CodeBrick"));
105
+ const alreadyInitialized = await isInitialized();
106
+ if (alreadyInitialized) {
107
+ p.log.warn("CodeBrick is already initialized!");
108
+ p.log.info(`Storage location: ${pc.dim(CODEBRICK_DIR)}`);
109
+ p.outro("Run " + pc.cyan("brick list") + " to see your templates");
110
+ return;
111
+ }
112
+ const spinner8 = p.spinner();
113
+ spinner8.start("Initializing CodeBrick...");
114
+ try {
115
+ await initializeStorage();
116
+ spinner8.stop("CodeBrick initialized successfully!");
117
+ console.log();
118
+ console.log(` Storage location: ${pc.dim(CODEBRICK_DIR)}`);
119
+ console.log();
120
+ console.log(pc.bold(" Quick Start:"));
121
+ console.log(
122
+ ` ${pc.cyan("brick save")} my-auth ./src/auth Save a template`
123
+ );
124
+ console.log(
125
+ ` ${pc.cyan("brick list")} View all templates`
126
+ );
127
+ console.log(
128
+ ` ${pc.cyan("brick apply")} my-auth Apply to current project`
129
+ );
130
+ console.log();
131
+ p.outro("Happy templating! \u{1F389}");
132
+ } catch (error) {
133
+ spinner8.stop("Failed to initialize CodeBrick");
134
+ p.log.error(
135
+ error instanceof Error ? error.message : "Unknown error occurred"
136
+ );
137
+ process.exit(1);
138
+ }
139
+ }
140
+
141
+ // src/commands/save.ts
142
+ import * as p2 from "@clack/prompts";
143
+ import pc3 from "picocolors";
144
+ import fs3 from "fs-extra";
145
+ import path3 from "path";
146
+ import fg from "fast-glob";
147
+
148
+ // src/lib/utils.ts
149
+ import fs2 from "fs-extra";
150
+ import path2 from "path";
151
+ import pc2 from "picocolors";
152
+ var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
153
+ // Node.js / JavaScript
154
+ "node_modules",
155
+ ".npm",
156
+ ".pnpm-store",
157
+ ".yarn",
158
+ ".pnp",
159
+ "bower_components",
160
+ // Flutter / Dart
161
+ ".dart_tool",
162
+ ".pub-cache",
163
+ ".pub",
164
+ "build",
165
+ // Python
166
+ "__pycache__",
167
+ ".venv",
168
+ "venv",
169
+ "env",
170
+ ".env",
171
+ ".eggs",
172
+ "*.egg-info",
173
+ ".pytest_cache",
174
+ ".mypy_cache",
175
+ ".tox",
176
+ ".nox",
177
+ "site-packages",
178
+ // Go
179
+ "vendor",
180
+ // Rust
181
+ "target",
182
+ // Java / Kotlin / Android
183
+ ".gradle",
184
+ ".idea",
185
+ "out",
186
+ ".cxx",
187
+ // Ruby
188
+ ".bundle",
189
+ // PHP
190
+ "vendor",
191
+ // .NET / C#
192
+ "bin",
193
+ "obj",
194
+ "packages",
195
+ // iOS / macOS
196
+ "Pods",
197
+ ".symlinks",
198
+ "DerivedData",
199
+ // General build outputs
200
+ "dist",
201
+ "build",
202
+ "out",
203
+ "output",
204
+ ".next",
205
+ ".nuxt",
206
+ ".output",
207
+ ".vercel",
208
+ ".netlify",
209
+ // Caches
210
+ ".cache",
211
+ ".parcel-cache",
212
+ ".turbo",
213
+ ".temp",
214
+ ".tmp",
215
+ "tmp",
216
+ "temp",
217
+ "coverage",
218
+ ".nyc_output",
219
+ // Version control
220
+ ".git",
221
+ ".svn",
222
+ ".hg",
223
+ // IDE / Editor
224
+ ".vscode",
225
+ ".idea",
226
+ ".vs",
227
+ ".settings",
228
+ "*.swp",
229
+ "*.swo"
230
+ ]);
231
+ var IGNORED_FILES = /* @__PURE__ */ new Set([
232
+ ".DS_Store",
233
+ "Thumbs.db",
234
+ ".gitignore",
235
+ ".gitattributes",
236
+ ".editorconfig",
237
+ "brick.json",
238
+ ".env",
239
+ ".env.local",
240
+ ".env.development",
241
+ ".env.production",
242
+ "*.log",
243
+ "npm-debug.log",
244
+ "yarn-error.log",
245
+ ".flutter-plugins",
246
+ ".flutter-plugins-dependencies",
247
+ ".packages",
248
+ "pubspec.lock",
249
+ "package-lock.json",
250
+ "yarn.lock",
251
+ "pnpm-lock.yaml",
252
+ "Podfile.lock",
253
+ "Gemfile.lock",
254
+ "poetry.lock",
255
+ "Cargo.lock",
256
+ "go.sum"
257
+ ]);
258
+ function shouldIgnoreDirectory(name) {
259
+ if (name.startsWith(".")) {
260
+ return true;
261
+ }
262
+ return IGNORED_DIRECTORIES.has(name);
263
+ }
264
+ function shouldIgnoreFile(name) {
265
+ if (name.startsWith(".")) {
266
+ return true;
267
+ }
268
+ if (IGNORED_FILES.has(name)) {
269
+ return true;
270
+ }
271
+ for (const pattern of IGNORED_FILES) {
272
+ if (pattern.startsWith("*")) {
273
+ const ext = pattern.slice(1);
274
+ if (name.endsWith(ext)) {
275
+ return true;
276
+ }
277
+ }
278
+ }
279
+ return false;
280
+ }
281
+ function formatSize(bytes) {
282
+ if (bytes < 1024) return `${bytes} B`;
283
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
284
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
285
+ }
286
+ function formatDate(dateString) {
287
+ const date = new Date(dateString);
288
+ return date.toLocaleDateString("en-US", {
289
+ year: "numeric",
290
+ month: "short",
291
+ day: "numeric"
292
+ });
293
+ }
294
+ async function getFilesRecursive(dir, baseDir = dir) {
295
+ const files = [];
296
+ const entries = await fs2.readdir(dir, { withFileTypes: true });
297
+ for (const entry of entries) {
298
+ const fullPath = path2.join(dir, entry.name);
299
+ const relativePath = path2.relative(baseDir, fullPath);
300
+ if (entry.isDirectory()) {
301
+ if (shouldIgnoreDirectory(entry.name)) {
302
+ continue;
303
+ }
304
+ const subFiles = await getFilesRecursive(fullPath, baseDir);
305
+ files.push(...subFiles);
306
+ } else {
307
+ if (!shouldIgnoreFile(entry.name)) {
308
+ files.push(relativePath);
309
+ }
310
+ }
311
+ }
312
+ return files.sort();
313
+ }
314
+ async function getDirectoryStats(dir) {
315
+ let files = 0;
316
+ let directories = 0;
317
+ let totalSize = 0;
318
+ const processDir = async (currentDir) => {
319
+ const entries = await fs2.readdir(currentDir, { withFileTypes: true });
320
+ for (const entry of entries) {
321
+ const fullPath = path2.join(currentDir, entry.name);
322
+ if (entry.isDirectory()) {
323
+ if (!shouldIgnoreDirectory(entry.name)) {
324
+ directories++;
325
+ await processDir(fullPath);
326
+ }
327
+ } else {
328
+ if (!shouldIgnoreFile(entry.name)) {
329
+ files++;
330
+ const stat = await fs2.stat(fullPath);
331
+ totalSize += stat.size;
332
+ }
333
+ }
334
+ }
335
+ };
336
+ await processDir(dir);
337
+ return { files, directories, totalSize };
338
+ }
339
+ async function buildTree(dir, includeSize = false) {
340
+ const entries = await fs2.readdir(dir, { withFileTypes: true });
341
+ const nodes = [];
342
+ const sortedEntries = entries.sort((a, b) => {
343
+ if (a.isDirectory() && !b.isDirectory()) return -1;
344
+ if (!a.isDirectory() && b.isDirectory()) return 1;
345
+ return a.name.localeCompare(b.name);
346
+ });
347
+ for (const entry of sortedEntries) {
348
+ const fullPath = path2.join(dir, entry.name);
349
+ if (entry.isDirectory()) {
350
+ if (shouldIgnoreDirectory(entry.name)) continue;
351
+ const children = await buildTree(fullPath, includeSize);
352
+ nodes.push({
353
+ name: entry.name,
354
+ type: "directory",
355
+ children
356
+ });
357
+ } else {
358
+ if (shouldIgnoreFile(entry.name)) continue;
359
+ const node = {
360
+ name: entry.name,
361
+ type: "file"
362
+ };
363
+ if (includeSize) {
364
+ const stat = await fs2.stat(fullPath);
365
+ node.size = stat.size;
366
+ }
367
+ nodes.push(node);
368
+ }
369
+ }
370
+ return nodes;
371
+ }
372
+ function renderTree(nodes, prefix = "", isLast = true, showSize = false) {
373
+ let output = "";
374
+ nodes.forEach((node, index) => {
375
+ const isLastNode = index === nodes.length - 1;
376
+ const connector = isLastNode ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
377
+ const sizeStr = showSize && node.size !== void 0 ? pc2.dim(` (${formatSize(node.size)})`) : "";
378
+ if (node.type === "directory") {
379
+ output += `${prefix}${connector}${pc2.blue(node.name)}/
380
+ `;
381
+ if (node.children && node.children.length > 0) {
382
+ const newPrefix = prefix + (isLastNode ? " " : "\u2502 ");
383
+ output += renderTree(node.children, newPrefix, isLastNode, showSize);
384
+ }
385
+ } else {
386
+ output += `${prefix}${connector}${node.name}${sizeStr}
387
+ `;
388
+ }
389
+ });
390
+ return output;
391
+ }
392
+ function createBox(title, content, width = 60) {
393
+ const lines = [];
394
+ const innerWidth = width - 4;
395
+ lines.push(pc2.dim("\u250C" + "\u2500".repeat(width - 2) + "\u2510"));
396
+ const titlePadding = Math.max(0, innerWidth - stripAnsi(title).length);
397
+ lines.push(pc2.dim("\u2502") + " " + title + " ".repeat(titlePadding) + pc2.dim("\u2502"));
398
+ lines.push(pc2.dim("\u251C" + "\u2500".repeat(width - 2) + "\u2524"));
399
+ for (const line of content) {
400
+ const stripped = stripAnsi(line);
401
+ const padding = Math.max(0, innerWidth - stripped.length);
402
+ lines.push(pc2.dim("\u2502") + " " + line + " ".repeat(padding) + pc2.dim("\u2502"));
403
+ }
404
+ lines.push(pc2.dim("\u2514" + "\u2500".repeat(width - 2) + "\u2518"));
405
+ return lines.join("\n");
406
+ }
407
+ function stripAnsi(str) {
408
+ return str.replace(
409
+ /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
410
+ ""
411
+ );
412
+ }
413
+ function padEnd(str, width) {
414
+ const stripped = stripAnsi(str);
415
+ const padding = Math.max(0, width - stripped.length);
416
+ return str + " ".repeat(padding);
417
+ }
418
+ function truncate(str, maxLength) {
419
+ if (str.length <= maxLength) return str;
420
+ return str.slice(0, maxLength - 3) + "...";
421
+ }
422
+ function createTable(headers, rows, columnWidths) {
423
+ const lines = [];
424
+ const topBorder = "\u250C" + columnWidths.map((w) => "\u2500".repeat(w + 2)).join("\u252C") + "\u2510";
425
+ lines.push(pc2.dim(topBorder));
426
+ const headerRow = pc2.dim("\u2502") + headers.map((h, i) => " " + pc2.bold(padEnd(h, columnWidths[i])) + " ").join(pc2.dim("\u2502")) + pc2.dim("\u2502");
427
+ lines.push(headerRow);
428
+ const headerSep = "\u251C" + columnWidths.map((w) => "\u2500".repeat(w + 2)).join("\u253C") + "\u2524";
429
+ lines.push(pc2.dim(headerSep));
430
+ for (const row of rows) {
431
+ const dataRow = pc2.dim("\u2502") + row.map((cell, i) => " " + padEnd(cell, columnWidths[i]) + " ").join(pc2.dim("\u2502")) + pc2.dim("\u2502");
432
+ lines.push(dataRow);
433
+ }
434
+ const bottomBorder = "\u2514" + columnWidths.map((w) => "\u2500".repeat(w + 2)).join("\u2534") + "\u2518";
435
+ lines.push(pc2.dim(bottomBorder));
436
+ return lines.join("\n");
437
+ }
438
+ async function detectPackageManager(dir) {
439
+ if (await fs2.pathExists(path2.join(dir, "bun.lockb"))) return "bun";
440
+ if (await fs2.pathExists(path2.join(dir, "pnpm-lock.yaml"))) return "pnpm";
441
+ if (await fs2.pathExists(path2.join(dir, "yarn.lock"))) return "yarn";
442
+ if (await fs2.pathExists(path2.join(dir, "package-lock.json"))) return "npm";
443
+ if (await fs2.pathExists(path2.join(dir, "package.json"))) return "npm";
444
+ return null;
445
+ }
446
+ function detectDependencies(content, filename) {
447
+ const deps = /* @__PURE__ */ new Set();
448
+ if (filename.endsWith(".ts") || filename.endsWith(".tsx") || filename.endsWith(".js") || filename.endsWith(".jsx")) {
449
+ const importRegex = /import\s+.*?\s+from\s+['"]([^'"./][^'"]*)['"]/g;
450
+ let match;
451
+ while ((match = importRegex.exec(content)) !== null) {
452
+ const pkg = match[1].split("/")[0];
453
+ if (!pkg.startsWith("@")) {
454
+ deps.add(pkg);
455
+ } else {
456
+ const parts = match[1].split("/");
457
+ if (parts.length >= 2) {
458
+ deps.add(`${parts[0]}/${parts[1]}`);
459
+ }
460
+ }
461
+ }
462
+ const requireRegex = /require\s*\(\s*['"]([^'"./][^'"]*)['"]\s*\)/g;
463
+ while ((match = requireRegex.exec(content)) !== null) {
464
+ const pkg = match[1].split("/")[0];
465
+ if (!pkg.startsWith("@")) {
466
+ deps.add(pkg);
467
+ } else {
468
+ const parts = match[1].split("/");
469
+ if (parts.length >= 2) {
470
+ deps.add(`${parts[0]}/${parts[1]}`);
471
+ }
472
+ }
473
+ }
474
+ }
475
+ if (filename.endsWith(".py")) {
476
+ const importRegex = /^(?:from|import)\s+(\w+)/gm;
477
+ let match;
478
+ while ((match = importRegex.exec(content)) !== null) {
479
+ const pkg = match[1];
480
+ if (!["os", "sys", "json", "re", "typing", "dataclasses"].includes(pkg)) {
481
+ deps.add(pkg);
482
+ }
483
+ }
484
+ }
485
+ return Array.from(deps);
486
+ }
487
+
488
+ // src/commands/save.ts
489
+ async function saveCommand(name, sourcePath, options) {
490
+ p2.intro(pc3.cyan("\u{1F9F1} Saving template"));
491
+ if (!await isInitialized()) {
492
+ p2.log.error(
493
+ "CodeBrick is not initialized. Run " + pc3.cyan("brick init") + " first."
494
+ );
495
+ process.exit(1);
496
+ }
497
+ const resolvedPath = sourcePath ? path3.resolve(sourcePath) : process.cwd();
498
+ if (!await fs3.pathExists(resolvedPath)) {
499
+ p2.log.error(`Path does not exist: ${resolvedPath}`);
500
+ process.exit(1);
501
+ }
502
+ const stat = await fs3.stat(resolvedPath);
503
+ if (!stat.isDirectory()) {
504
+ p2.log.error("Source path must be a directory");
505
+ process.exit(1);
506
+ }
507
+ if (await templateExists(name)) {
508
+ const shouldOverwrite = await p2.confirm({
509
+ message: `Template '${name}' already exists. Overwrite?`,
510
+ initialValue: false
511
+ });
512
+ if (p2.isCancel(shouldOverwrite) || !shouldOverwrite) {
513
+ p2.cancel("Operation cancelled");
514
+ process.exit(0);
515
+ }
516
+ const existingPath = getTemplatePath(name);
517
+ await fs3.remove(existingPath);
518
+ }
519
+ let files;
520
+ if (options.include) {
521
+ const patterns = options.include.split(",").map((p12) => p12.trim());
522
+ files = await fg(patterns, {
523
+ cwd: resolvedPath,
524
+ dot: false,
525
+ ignore: options.exclude?.split(",").map((p12) => p12.trim()) || []
526
+ });
527
+ } else {
528
+ files = await getFilesRecursive(resolvedPath);
529
+ if (options.exclude) {
530
+ const excludePatterns = options.exclude.split(",").map((p12) => p12.trim());
531
+ const excluded = await fg(excludePatterns, {
532
+ cwd: resolvedPath,
533
+ dot: false
534
+ });
535
+ const excludeSet = new Set(excluded);
536
+ files = files.filter((f) => !excludeSet.has(f));
537
+ }
538
+ }
539
+ if (files.length === 0) {
540
+ p2.log.error("No files found in the specified path");
541
+ process.exit(1);
542
+ }
543
+ const stats = await getDirectoryStats(resolvedPath);
544
+ p2.log.info(`Source: ${pc3.dim(resolvedPath)}`);
545
+ p2.log.info(
546
+ `Files: ${pc3.green(files.length.toString())} files` + (stats.directories > 0 ? `, ${stats.directories} directories` : "")
547
+ );
548
+ let detectedDeps = [];
549
+ if (options.detectDeps) {
550
+ const spinner9 = p2.spinner();
551
+ spinner9.start("Detecting dependencies...");
552
+ for (const file of files) {
553
+ const filePath = path3.join(resolvedPath, file);
554
+ try {
555
+ const content = await fs3.readFile(filePath, "utf-8");
556
+ const deps = detectDependencies(content, file);
557
+ detectedDeps.push(...deps);
558
+ } catch {
559
+ }
560
+ }
561
+ detectedDeps = [...new Set(detectedDeps)];
562
+ spinner9.stop(`Detected ${detectedDeps.length} dependencies`);
563
+ if (detectedDeps.length > 0) {
564
+ p2.log.info(`Dependencies: ${pc3.dim(detectedDeps.join(", "))}`);
565
+ const addDeps = await p2.confirm({
566
+ message: "Add these to template metadata?",
567
+ initialValue: true
568
+ });
569
+ if (p2.isCancel(addDeps)) {
570
+ p2.cancel("Operation cancelled");
571
+ process.exit(0);
572
+ }
573
+ if (!addDeps) {
574
+ detectedDeps = [];
575
+ }
576
+ }
577
+ }
578
+ let description = options.description;
579
+ if (!description) {
580
+ const descInput = await p2.text({
581
+ message: "Template description (optional):",
582
+ placeholder: "A reusable code template"
583
+ });
584
+ if (p2.isCancel(descInput)) {
585
+ p2.cancel("Operation cancelled");
586
+ process.exit(0);
587
+ }
588
+ description = descInput || "";
589
+ }
590
+ const tags = options.tags ? options.tags.split(",").map((t) => t.trim()).filter(Boolean) : [];
591
+ const spinner8 = p2.spinner();
592
+ spinner8.start("Copying files...");
593
+ const templatePath = getTemplatePath(name);
594
+ await fs3.ensureDir(templatePath);
595
+ for (const file of files) {
596
+ const sourcefile = path3.join(resolvedPath, file);
597
+ const destFile = path3.join(templatePath, file);
598
+ await fs3.ensureDir(path3.dirname(destFile));
599
+ await fs3.copy(sourcefile, destFile);
600
+ }
601
+ const now = (/* @__PURE__ */ new Date()).toISOString();
602
+ const metadata = {
603
+ name,
604
+ type: "local",
605
+ version: "1.0.0",
606
+ description: description || "",
607
+ createdAt: now,
608
+ updatedAt: now,
609
+ source: {
610
+ origin: "local",
611
+ path: resolvedPath
612
+ },
613
+ files,
614
+ tags
615
+ };
616
+ if (detectedDeps.length > 0) {
617
+ metadata.dependencies = {};
618
+ for (const dep of detectedDeps) {
619
+ metadata.dependencies[dep] = "*";
620
+ }
621
+ }
622
+ await saveTemplateMetadata(name, metadata);
623
+ const template = {
624
+ type: "local",
625
+ path: `templates/${name}`,
626
+ description: description || "",
627
+ tags,
628
+ createdAt: now,
629
+ updatedAt: now
630
+ };
631
+ await addTemplateToStore(name, template);
632
+ spinner8.stop("Template saved!");
633
+ console.log();
634
+ console.log(` View structure: ${pc3.cyan(`brick tree ${name}`)}`);
635
+ console.log(` Apply template: ${pc3.cyan(`brick apply ${name}`)}`);
636
+ console.log();
637
+ p2.outro(`Template '${pc3.green(name)}' saved successfully!`);
638
+ }
639
+
640
+ // src/commands/list.ts
641
+ import * as p3 from "@clack/prompts";
642
+ import pc4 from "picocolors";
643
+ async function listCommand(options) {
644
+ if (!await isInitialized()) {
645
+ p3.log.error(
646
+ "CodeBrick is not initialized. Run " + pc4.cyan("brick init") + " first."
647
+ );
648
+ process.exit(1);
649
+ }
650
+ const store = await loadStore();
651
+ let templates = Object.entries(store.templates);
652
+ if (options.local) {
653
+ templates = templates.filter(([, t]) => t.type === "local");
654
+ } else if (options.remote) {
655
+ templates = templates.filter(([, t]) => t.type === "remote");
656
+ }
657
+ if (options.tag) {
658
+ templates = templates.filter(
659
+ ([, t]) => t.tags.some((tag) => tag.toLowerCase() === options.tag?.toLowerCase())
660
+ );
661
+ }
662
+ if (templates.length === 0) {
663
+ console.log();
664
+ if (options.tag) {
665
+ console.log(pc4.dim(` No templates found with tag '${options.tag}'`));
666
+ } else if (options.local) {
667
+ console.log(pc4.dim(" No local templates found"));
668
+ } else if (options.remote) {
669
+ console.log(pc4.dim(" No remote templates found"));
670
+ } else {
671
+ console.log(pc4.dim(" No templates saved yet"));
672
+ console.log();
673
+ console.log(
674
+ ` Get started: ${pc4.cyan("brick save")} my-template ./path/to/code`
675
+ );
676
+ }
677
+ console.log();
678
+ return;
679
+ }
680
+ if (options.detailed) {
681
+ await renderDetailedList(templates);
682
+ } else {
683
+ renderTableList(templates);
684
+ }
685
+ const localCount = templates.filter(([, t]) => t.type === "local").length;
686
+ const remoteCount = templates.filter(([, t]) => t.type === "remote").length;
687
+ console.log();
688
+ if (localCount > 0 && remoteCount > 0) {
689
+ console.log(
690
+ pc4.dim(`Total: ${templates.length} templates (${localCount} local, ${remoteCount} remote)`)
691
+ );
692
+ } else {
693
+ console.log(pc4.dim(`Total: ${templates.length} template${templates.length === 1 ? "" : "s"}`));
694
+ }
695
+ console.log();
696
+ console.log(pc4.dim("Tip: Use index or name with commands, e.g., brick tree 0"));
697
+ console.log();
698
+ }
699
+ function renderTableList(templates) {
700
+ const headers = ["#", "Name", "Type", "Description"];
701
+ const columnWidths = [3, 28, 8, 36];
702
+ const rows = templates.map(([name, template], index) => {
703
+ const typeColor = template.type === "local" ? pc4.green : pc4.blue;
704
+ return [
705
+ pc4.yellow(index.toString()),
706
+ truncate(name, columnWidths[1]),
707
+ typeColor(template.type),
708
+ truncate(template.description || "-", columnWidths[3])
709
+ ];
710
+ });
711
+ console.log();
712
+ console.log(createTable(headers, rows, columnWidths));
713
+ }
714
+ async function renderDetailedList(templates) {
715
+ const localTemplates = templates.filter(([, t]) => t.type === "local");
716
+ const remoteTemplates = templates.filter(([, t]) => t.type === "remote");
717
+ let globalIndex = 0;
718
+ console.log();
719
+ if (localTemplates.length > 0) {
720
+ console.log(pc4.bold("LOCAL TEMPLATES"));
721
+ console.log(pc4.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
722
+ console.log();
723
+ for (const [name, template] of localTemplates) {
724
+ const metadata = await loadTemplateMetadata(name);
725
+ const fileCount = metadata?.files.length || 0;
726
+ console.log(pc4.yellow(`[${globalIndex}]`) + " " + pc4.bold(pc4.green(name)));
727
+ console.log(` Type: ${pc4.dim("local")}`);
728
+ console.log(` Description: ${template.description || pc4.dim("No description")}`);
729
+ console.log(` Files: ${fileCount} files`);
730
+ if (template.tags.length > 0) {
731
+ console.log(` Tags: ${template.tags.join(", ")}`);
732
+ }
733
+ console.log(` Created: ${formatDate(template.createdAt)}`);
734
+ console.log(` Updated: ${formatDate(template.updatedAt)}`);
735
+ console.log();
736
+ globalIndex++;
737
+ }
738
+ }
739
+ if (remoteTemplates.length > 0) {
740
+ if (localTemplates.length > 0) {
741
+ console.log();
742
+ }
743
+ console.log(pc4.bold("REMOTE TEMPLATES (GitHub)"));
744
+ console.log(pc4.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
745
+ console.log();
746
+ for (const [name, template] of remoteTemplates) {
747
+ const remote = template;
748
+ const source = `github:${remote.github.owner}/${remote.github.repo}/${remote.github.path}`;
749
+ const ref = remote.github.commit ? `${remote.github.ref} @ ${remote.github.commit.slice(0, 7)}` : remote.github.ref;
750
+ console.log(pc4.yellow(`[${globalIndex}]`) + " " + pc4.bold(pc4.blue(name)));
751
+ console.log(` Type: ${pc4.dim("remote")}`);
752
+ console.log(` Source: ${source}`);
753
+ console.log(` Ref: ${ref}`);
754
+ console.log(` Description: ${template.description || pc4.dim("No description")}`);
755
+ if (template.tags.length > 0) {
756
+ console.log(` Tags: ${template.tags.join(", ")}`);
757
+ }
758
+ console.log(` Linked: ${formatDate(template.createdAt)}`);
759
+ console.log();
760
+ globalIndex++;
761
+ }
762
+ }
763
+ }
764
+
765
+ // src/commands/tree.ts
766
+ import * as p4 from "@clack/prompts";
767
+ import pc5 from "picocolors";
768
+ async function treeCommand(nameOrIndex, options) {
769
+ if (!await isInitialized()) {
770
+ p4.log.error(
771
+ "CodeBrick is not initialized. Run " + pc5.cyan("brick init") + " first."
772
+ );
773
+ process.exit(1);
774
+ }
775
+ const name = await resolveTemplateName(nameOrIndex);
776
+ if (!name) {
777
+ p4.log.error(
778
+ `Template '${nameOrIndex}' not found. Run ${pc5.cyan("brick list")} to see available templates.`
779
+ );
780
+ process.exit(1);
781
+ }
782
+ const template = await getTemplate(name);
783
+ if (!template) {
784
+ p4.log.error(`Template '${name}' not found.`);
785
+ process.exit(1);
786
+ }
787
+ if (template.type === "remote") {
788
+ console.log();
789
+ console.log(pc5.bold(pc5.blue(name)) + pc5.dim(" (remote)"));
790
+ console.log(
791
+ pc5.dim(
792
+ `Source: github:${template.github.owner}/${template.github.repo}/${template.github.path}`
793
+ )
794
+ );
795
+ console.log();
796
+ console.log(
797
+ pc5.yellow(
798
+ "Remote template files are fetched on-demand. Use " + pc5.cyan(`brick pull ${name}`) + " to download locally."
799
+ )
800
+ );
801
+ console.log();
802
+ return;
803
+ }
804
+ const templatePath = getTemplatePath(name);
805
+ const tree = await buildTree(templatePath, options.size);
806
+ const stats = await getDirectoryStats(templatePath);
807
+ console.log();
808
+ if (options.size) {
809
+ console.log(pc5.bold(pc5.green(name)) + pc5.dim(` (${formatSize(stats.totalSize)})`));
810
+ } else {
811
+ console.log(pc5.bold(pc5.green(name)));
812
+ }
813
+ const treeOutput = renderTree(tree, "", true, options.size);
814
+ console.log(treeOutput);
815
+ const summary = [];
816
+ if (stats.files > 0) {
817
+ summary.push(`${stats.files} file${stats.files === 1 ? "" : "s"}`);
818
+ }
819
+ if (stats.directories > 0) {
820
+ summary.push(`${stats.directories} director${stats.directories === 1 ? "y" : "ies"}`);
821
+ }
822
+ if (options.size && stats.totalSize > 0) {
823
+ summary.push(`${formatSize(stats.totalSize)} total`);
824
+ }
825
+ console.log(pc5.dim(summary.join(", ")));
826
+ console.log();
827
+ }
828
+
829
+ // src/commands/apply.ts
830
+ import * as p5 from "@clack/prompts";
831
+ import pc6 from "picocolors";
832
+ import fs4 from "fs-extra";
833
+ import path4 from "path";
834
+ async function applyCommand(nameOrIndex, destination, options) {
835
+ p5.intro(pc6.cyan("\u{1F9F1} Applying template"));
836
+ if (!await isInitialized()) {
837
+ p5.log.error(
838
+ "CodeBrick is not initialized. Run " + pc6.cyan("brick init") + " first."
839
+ );
840
+ process.exit(1);
841
+ }
842
+ const name = await resolveTemplateName(nameOrIndex);
843
+ if (!name) {
844
+ p5.log.error(
845
+ `Template '${nameOrIndex}' not found. Run ${pc6.cyan("brick list")} to see available templates.`
846
+ );
847
+ process.exit(1);
848
+ }
849
+ const template = await getTemplate(name);
850
+ if (!template) {
851
+ p5.log.error(`Template '${name}' not found.`);
852
+ process.exit(1);
853
+ }
854
+ if (template.type === "remote") {
855
+ p5.log.error(
856
+ "Remote template support coming soon. Use " + pc6.cyan(`brick pull ${name}`) + " to download locally first."
857
+ );
858
+ process.exit(1);
859
+ }
860
+ const destPath = destination ? path4.resolve(destination) : process.cwd();
861
+ const metadata = await loadTemplateMetadata(name);
862
+ if (!metadata) {
863
+ p5.log.error("Failed to load template metadata");
864
+ process.exit(1);
865
+ }
866
+ p5.log.info(`Destination: ${pc6.dim(destPath)}`);
867
+ const templatePath = getTemplatePath(name);
868
+ const conflicts = [];
869
+ const filesToCopy = [];
870
+ for (const file of metadata.files) {
871
+ const destFile = path4.join(destPath, file);
872
+ if (await fs4.pathExists(destFile)) {
873
+ conflicts.push(file);
874
+ }
875
+ filesToCopy.push(file);
876
+ }
877
+ console.log();
878
+ console.log(pc6.bold(` ${filesToCopy.length} files will be created:`));
879
+ for (const file of filesToCopy.slice(0, 10)) {
880
+ const isConflict = conflicts.includes(file);
881
+ const prefix = isConflict ? pc6.yellow("~") : pc6.green("+");
882
+ console.log(` ${prefix} ${file}`);
883
+ }
884
+ if (filesToCopy.length > 10) {
885
+ console.log(pc6.dim(` ... and ${filesToCopy.length - 10} more`));
886
+ }
887
+ console.log();
888
+ if (conflicts.length > 0 && !options.force && !options.skipExisting) {
889
+ console.log(pc6.yellow(` \u26A0 ${conflicts.length} file(s) already exist:`));
890
+ for (const file of conflicts.slice(0, 5)) {
891
+ console.log(pc6.dim(` - ${file}`));
892
+ }
893
+ if (conflicts.length > 5) {
894
+ console.log(pc6.dim(` ... and ${conflicts.length - 5} more`));
895
+ }
896
+ console.log();
897
+ const conflictAction = await p5.select({
898
+ message: "How would you like to handle existing files?",
899
+ options: [
900
+ { value: "overwrite", label: "Overwrite all" },
901
+ { value: "skip", label: "Skip existing files" },
902
+ { value: "cancel", label: "Cancel" }
903
+ ]
904
+ });
905
+ if (p5.isCancel(conflictAction) || conflictAction === "cancel") {
906
+ p5.cancel("Operation cancelled");
907
+ process.exit(0);
908
+ }
909
+ if (conflictAction === "skip") {
910
+ options.skipExisting = true;
911
+ } else {
912
+ options.force = true;
913
+ }
914
+ }
915
+ const hasDeps = metadata.dependencies && Object.keys(metadata.dependencies).length > 0;
916
+ const hasDevDeps = metadata.devDependencies && Object.keys(metadata.devDependencies).length > 0;
917
+ if ((hasDeps || hasDevDeps) && options.deps !== false) {
918
+ const allDeps = {
919
+ ...metadata.dependencies,
920
+ ...metadata.devDependencies
921
+ };
922
+ console.log(pc6.bold(" Dependencies required:"));
923
+ for (const [dep, version] of Object.entries(allDeps)) {
924
+ console.log(` ${pc6.cyan(dep)} ${pc6.dim(version)}`);
925
+ }
926
+ console.log();
927
+ const versionChoice = await p5.select({
928
+ message: "How would you like to handle dependency versions?",
929
+ options: [
930
+ { value: "template", label: "Use template versions (recommended)" },
931
+ { value: "latest", label: "Use @latest for all packages" },
932
+ { value: "skip", label: "Skip dependency installation" }
933
+ ]
934
+ });
935
+ if (p5.isCancel(versionChoice)) {
936
+ p5.cancel("Operation cancelled");
937
+ process.exit(0);
938
+ }
939
+ if (versionChoice === "latest") {
940
+ options.latest = true;
941
+ }
942
+ }
943
+ if (options.dryRun) {
944
+ console.log(pc6.yellow(" Dry run - no files will be written"));
945
+ console.log();
946
+ p5.outro("Dry run complete");
947
+ return;
948
+ }
949
+ const confirmed = await p5.confirm({
950
+ message: "Proceed with installation?",
951
+ initialValue: true
952
+ });
953
+ if (p5.isCancel(confirmed) || !confirmed) {
954
+ p5.cancel("Operation cancelled");
955
+ process.exit(0);
956
+ }
957
+ const spinner8 = p5.spinner();
958
+ spinner8.start("Creating files...");
959
+ let copiedCount = 0;
960
+ let skippedCount = 0;
961
+ for (const file of metadata.files) {
962
+ const sourceFile = path4.join(templatePath, file);
963
+ const destFile = path4.join(destPath, file);
964
+ if (options.skipExisting && await fs4.pathExists(destFile)) {
965
+ skippedCount++;
966
+ continue;
967
+ }
968
+ await fs4.ensureDir(path4.dirname(destFile));
969
+ await fs4.copy(sourceFile, destFile, { overwrite: options.force });
970
+ copiedCount++;
971
+ }
972
+ spinner8.stop("Files created!");
973
+ console.log();
974
+ console.log(pc6.green(` \u2713 ${copiedCount} file(s) created`));
975
+ if (skippedCount > 0) {
976
+ console.log(pc6.yellow(` \u2298 ${skippedCount} file(s) skipped`));
977
+ }
978
+ if ((hasDeps || hasDevDeps) && options.deps !== false) {
979
+ const pm = await detectPackageManager(destPath);
980
+ const installCmd = pm === "yarn" ? "yarn add" : pm === "pnpm" ? "pnpm add" : pm === "bun" ? "bun add" : "npm install";
981
+ console.log();
982
+ console.log(pc6.dim(" To install dependencies, run:"));
983
+ const deps = Object.entries(metadata.dependencies || {}).map(
984
+ ([dep, version]) => options.latest ? dep : `${dep}@${version}`
985
+ );
986
+ const devDeps = Object.entries(metadata.devDependencies || {}).map(
987
+ ([dep, version]) => options.latest ? dep : `${dep}@${version}`
988
+ );
989
+ if (deps.length > 0) {
990
+ console.log(` ${pc6.cyan(installCmd)} ${deps.join(" ")}`);
991
+ }
992
+ if (devDeps.length > 0) {
993
+ const devFlag = pm === "yarn" ? "--dev" : pm === "bun" ? "--dev" : "-D";
994
+ console.log(` ${pc6.cyan(installCmd)} ${devFlag} ${devDeps.join(" ")}`);
995
+ }
996
+ }
997
+ console.log();
998
+ p5.outro("Template applied successfully! \u{1F389}");
999
+ }
1000
+
1001
+ // src/commands/info.ts
1002
+ import * as p6 from "@clack/prompts";
1003
+ import pc7 from "picocolors";
1004
+ async function infoCommand(nameOrIndex) {
1005
+ if (!await isInitialized()) {
1006
+ p6.log.error(
1007
+ "CodeBrick is not initialized. Run " + pc7.cyan("brick init") + " first."
1008
+ );
1009
+ process.exit(1);
1010
+ }
1011
+ const name = await resolveTemplateName(nameOrIndex);
1012
+ if (!name) {
1013
+ p6.log.error(
1014
+ `Template '${nameOrIndex}' not found. Run ${pc7.cyan("brick list")} to see available templates.`
1015
+ );
1016
+ process.exit(1);
1017
+ }
1018
+ const template = await getTemplate(name);
1019
+ if (!template) {
1020
+ p6.log.error(`Template '${name}' not found.`);
1021
+ process.exit(1);
1022
+ }
1023
+ console.log();
1024
+ if (template.type === "local") {
1025
+ const metadata = await loadTemplateMetadata(name);
1026
+ const templatePath = getTemplatePath(name);
1027
+ const stats = await getDirectoryStats(templatePath);
1028
+ const content = [
1029
+ "",
1030
+ `Description: ${metadata?.description || pc7.dim("No description")}`,
1031
+ `Version: ${metadata?.version || "1.0.0"}`,
1032
+ `Created: ${formatDate(template.createdAt)}`,
1033
+ `Updated: ${formatDate(template.updatedAt)}`,
1034
+ "",
1035
+ `Source: ${metadata?.source?.path || pc7.dim("Unknown")}`,
1036
+ `Storage: ${pc7.dim(`~/.codebrick/templates/${name}`)}`,
1037
+ ""
1038
+ ];
1039
+ if (template.tags.length > 0) {
1040
+ content.push(`Tags: ${template.tags.join(", ")}`);
1041
+ content.push("");
1042
+ }
1043
+ content.push(
1044
+ `Files: ${stats.files} file${stats.files === 1 ? "" : "s"}` + (stats.directories > 0 ? `, ${stats.directories} director${stats.directories === 1 ? "y" : "ies"}` : "")
1045
+ );
1046
+ content.push("");
1047
+ if (metadata?.dependencies && Object.keys(metadata.dependencies).length > 0) {
1048
+ content.push("Dependencies:");
1049
+ for (const [dep, version] of Object.entries(metadata.dependencies)) {
1050
+ content.push(` ${dep.padEnd(20)} ${pc7.dim(version)}`);
1051
+ }
1052
+ content.push("");
1053
+ }
1054
+ if (metadata?.devDependencies && Object.keys(metadata.devDependencies).length > 0) {
1055
+ content.push("Dev Dependencies:");
1056
+ for (const [dep, version] of Object.entries(metadata.devDependencies)) {
1057
+ content.push(` ${dep.padEnd(20)} ${pc7.dim(version)}`);
1058
+ }
1059
+ content.push("");
1060
+ }
1061
+ const title = pc7.bold(pc7.green(name));
1062
+ console.log(createBox(title, content, 60));
1063
+ } else {
1064
+ const github = template.github;
1065
+ const source = `github:${github.owner}/${github.repo}/${github.path}`;
1066
+ const ref = github.commit ? `${github.ref} @ ${github.commit.slice(0, 7)}` : github.ref;
1067
+ const content = [
1068
+ "",
1069
+ `Description: ${template.description || pc7.dim("No description")}`,
1070
+ `Linked: ${formatDate(template.createdAt)}`,
1071
+ "",
1072
+ `Source: ${source}`,
1073
+ `Ref: ${ref}`,
1074
+ ""
1075
+ ];
1076
+ if (template.tags.length > 0) {
1077
+ content.push(`Tags: ${template.tags.join(", ")}`);
1078
+ content.push("");
1079
+ }
1080
+ content.push(pc7.yellow("This is a remote template."));
1081
+ content.push(pc7.yellow("Files are fetched from GitHub on-demand."));
1082
+ content.push("");
1083
+ const title = pc7.bold(pc7.blue(name)) + pc7.dim(" (remote)");
1084
+ console.log(createBox(title, content, 60));
1085
+ }
1086
+ console.log();
1087
+ console.log(pc7.bold("Commands:"));
1088
+ console.log(` ${pc7.cyan(`brick tree ${name}`)} View file structure`);
1089
+ console.log(` ${pc7.cyan(`brick apply ${name}`)} Apply to project`);
1090
+ if (template.type === "local") {
1091
+ console.log(` ${pc7.cyan(`brick add ${name}`)} Add files to template`);
1092
+ } else {
1093
+ console.log(` ${pc7.cyan(`brick pull ${name}`)} Download locally`);
1094
+ }
1095
+ console.log();
1096
+ }
1097
+
1098
+ // src/commands/add.ts
1099
+ import * as p7 from "@clack/prompts";
1100
+ import pc8 from "picocolors";
1101
+ import fs5 from "fs-extra";
1102
+ import path5 from "path";
1103
+ import fg2 from "fast-glob";
1104
+ async function addCommand(nameOrIndex, filePatterns) {
1105
+ p7.intro(pc8.cyan("\u{1F9F1} Adding files to template"));
1106
+ if (!await isInitialized()) {
1107
+ p7.log.error(
1108
+ "CodeBrick is not initialized. Run " + pc8.cyan("brick init") + " first."
1109
+ );
1110
+ process.exit(1);
1111
+ }
1112
+ const name = await resolveTemplateName(nameOrIndex);
1113
+ if (!name) {
1114
+ p7.log.error(
1115
+ `Template '${nameOrIndex}' not found. Run ${pc8.cyan("brick list")} to see available templates.`
1116
+ );
1117
+ process.exit(1);
1118
+ }
1119
+ const template = await getTemplate(name);
1120
+ if (!template) {
1121
+ p7.log.error(`Template '${name}' not found.`);
1122
+ process.exit(1);
1123
+ }
1124
+ if (template.type === "remote") {
1125
+ p7.log.error(
1126
+ "Cannot add files to remote templates. Use " + pc8.cyan(`brick pull ${name}`) + " to convert to local first."
1127
+ );
1128
+ process.exit(1);
1129
+ }
1130
+ const cwd = process.cwd();
1131
+ const filesToAdd = [];
1132
+ for (const pattern of filePatterns) {
1133
+ const resolved = path5.resolve(pattern);
1134
+ if (await fs5.pathExists(resolved)) {
1135
+ const stat = await fs5.stat(resolved);
1136
+ if (stat.isDirectory()) {
1137
+ const files = await fg2("**/*", {
1138
+ cwd: resolved,
1139
+ dot: false,
1140
+ onlyFiles: true
1141
+ });
1142
+ const relativeDirPath = path5.relative(cwd, resolved);
1143
+ const basePath = relativeDirPath.startsWith("..") ? path5.basename(resolved) : relativeDirPath;
1144
+ for (const file of files) {
1145
+ filesToAdd.push({
1146
+ source: path5.join(resolved, file),
1147
+ relative: path5.join(basePath, file)
1148
+ });
1149
+ }
1150
+ } else {
1151
+ const relativeFilePath = path5.relative(cwd, resolved);
1152
+ filesToAdd.push({
1153
+ source: resolved,
1154
+ relative: relativeFilePath.startsWith("..") ? path5.basename(resolved) : relativeFilePath
1155
+ // Inside cwd, preserve path
1156
+ });
1157
+ }
1158
+ } else {
1159
+ const matches = await fg2(pattern, {
1160
+ cwd,
1161
+ dot: false,
1162
+ onlyFiles: true
1163
+ });
1164
+ for (const match of matches) {
1165
+ filesToAdd.push({
1166
+ source: path5.join(cwd, match),
1167
+ relative: match
1168
+ });
1169
+ }
1170
+ }
1171
+ }
1172
+ if (filesToAdd.length === 0) {
1173
+ p7.log.error("No files found matching the specified patterns");
1174
+ process.exit(1);
1175
+ }
1176
+ console.log();
1177
+ console.log(pc8.bold(` Files to add:`));
1178
+ for (const file of filesToAdd.slice(0, 10)) {
1179
+ console.log(` ${pc8.green("+")} ${file.relative}`);
1180
+ }
1181
+ if (filesToAdd.length > 10) {
1182
+ console.log(pc8.dim(` ... and ${filesToAdd.length - 10} more`));
1183
+ }
1184
+ console.log();
1185
+ const confirmed = await p7.confirm({
1186
+ message: `Confirm adding ${filesToAdd.length} file(s)?`,
1187
+ initialValue: true
1188
+ });
1189
+ if (p7.isCancel(confirmed) || !confirmed) {
1190
+ p7.cancel("Operation cancelled");
1191
+ process.exit(0);
1192
+ }
1193
+ const spinner8 = p7.spinner();
1194
+ spinner8.start("Adding files...");
1195
+ const templatePath = getTemplatePath(name);
1196
+ const metadata = await loadTemplateMetadata(name);
1197
+ if (!metadata) {
1198
+ spinner8.stop("Failed to load template metadata");
1199
+ process.exit(1);
1200
+ }
1201
+ const existingFiles = new Set(metadata.files);
1202
+ let addedCount = 0;
1203
+ for (const file of filesToAdd) {
1204
+ const destPath = path5.join(templatePath, file.relative);
1205
+ await fs5.ensureDir(path5.dirname(destPath));
1206
+ await fs5.copy(file.source, destPath, { overwrite: true });
1207
+ if (!existingFiles.has(file.relative)) {
1208
+ metadata.files.push(file.relative);
1209
+ existingFiles.add(file.relative);
1210
+ }
1211
+ addedCount++;
1212
+ }
1213
+ metadata.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1214
+ await saveTemplateMetadata(name, metadata);
1215
+ const updatedTemplate = {
1216
+ ...template,
1217
+ updatedAt: metadata.updatedAt
1218
+ };
1219
+ await addTemplateToStore(name, updatedTemplate);
1220
+ spinner8.stop(`Added ${addedCount} file(s)`);
1221
+ console.log();
1222
+ console.log(
1223
+ ` Template now has ${pc8.green(metadata.files.length.toString())} files`
1224
+ );
1225
+ console.log();
1226
+ p7.outro("Template updated! \u2713");
1227
+ }
1228
+
1229
+ // src/commands/remove-file.ts
1230
+ import * as p8 from "@clack/prompts";
1231
+ import pc9 from "picocolors";
1232
+ import fs6 from "fs-extra";
1233
+ import path6 from "path";
1234
+ async function removeFileCommand(nameOrIndex, filePatterns) {
1235
+ p8.intro(pc9.cyan("\u{1F9F1} Removing files from template"));
1236
+ if (!await isInitialized()) {
1237
+ p8.log.error(
1238
+ "CodeBrick is not initialized. Run " + pc9.cyan("brick init") + " first."
1239
+ );
1240
+ process.exit(1);
1241
+ }
1242
+ const name = await resolveTemplateName(nameOrIndex);
1243
+ if (!name) {
1244
+ p8.log.error(
1245
+ `Template '${nameOrIndex}' not found. Run ${pc9.cyan("brick list")} to see available templates.`
1246
+ );
1247
+ process.exit(1);
1248
+ }
1249
+ const template = await getTemplate(name);
1250
+ if (!template) {
1251
+ p8.log.error(`Template '${name}' not found.`);
1252
+ process.exit(1);
1253
+ }
1254
+ if (template.type === "remote") {
1255
+ p8.log.error(
1256
+ "Cannot remove files from remote templates. Use " + pc9.cyan(`brick pull ${name}`) + " to convert to local first."
1257
+ );
1258
+ process.exit(1);
1259
+ }
1260
+ const metadata = await loadTemplateMetadata(name);
1261
+ if (!metadata) {
1262
+ p8.log.error("Failed to load template metadata");
1263
+ process.exit(1);
1264
+ }
1265
+ const filesToRemove = [];
1266
+ const templatePath = getTemplatePath(name);
1267
+ for (const pattern of filePatterns) {
1268
+ if (metadata.files.includes(pattern)) {
1269
+ filesToRemove.push(pattern);
1270
+ continue;
1271
+ }
1272
+ const dirPattern = pattern.endsWith("/") ? pattern : pattern + "/";
1273
+ const matchingFiles = metadata.files.filter(
1274
+ (f) => f === pattern || f.startsWith(dirPattern)
1275
+ );
1276
+ if (matchingFiles.length > 0) {
1277
+ filesToRemove.push(...matchingFiles);
1278
+ continue;
1279
+ }
1280
+ const regexPattern = pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".");
1281
+ const regex = new RegExp(`^${regexPattern}$`);
1282
+ const matches = metadata.files.filter((f) => regex.test(f));
1283
+ filesToRemove.push(...matches);
1284
+ }
1285
+ const uniqueFilesToRemove = [...new Set(filesToRemove)];
1286
+ if (uniqueFilesToRemove.length === 0) {
1287
+ p8.log.error("No matching files found in template");
1288
+ p8.log.info(`Template files: ${metadata.files.join(", ")}`);
1289
+ process.exit(1);
1290
+ }
1291
+ console.log();
1292
+ console.log(pc9.bold(` Files to remove:`));
1293
+ for (const file of uniqueFilesToRemove.slice(0, 10)) {
1294
+ console.log(` ${pc9.red("-")} ${file}`);
1295
+ }
1296
+ if (uniqueFilesToRemove.length > 10) {
1297
+ console.log(pc9.dim(` ... and ${uniqueFilesToRemove.length - 10} more`));
1298
+ }
1299
+ console.log();
1300
+ const confirmed = await p8.confirm({
1301
+ message: `Confirm removing ${uniqueFilesToRemove.length} file(s)?`,
1302
+ initialValue: true
1303
+ });
1304
+ if (p8.isCancel(confirmed) || !confirmed) {
1305
+ p8.cancel("Operation cancelled");
1306
+ process.exit(0);
1307
+ }
1308
+ const spinner8 = p8.spinner();
1309
+ spinner8.start("Removing files...");
1310
+ let removedCount = 0;
1311
+ for (const file of uniqueFilesToRemove) {
1312
+ const filePath = path6.join(templatePath, file);
1313
+ if (await fs6.pathExists(filePath)) {
1314
+ await fs6.remove(filePath);
1315
+ }
1316
+ const index = metadata.files.indexOf(file);
1317
+ if (index > -1) {
1318
+ metadata.files.splice(index, 1);
1319
+ }
1320
+ removedCount++;
1321
+ }
1322
+ const cleanEmptyDirs = async (dir) => {
1323
+ const entries = await fs6.readdir(dir);
1324
+ for (const entry of entries) {
1325
+ const entryPath = path6.join(dir, entry);
1326
+ const stat = await fs6.stat(entryPath);
1327
+ if (stat.isDirectory()) {
1328
+ await cleanEmptyDirs(entryPath);
1329
+ const subEntries = await fs6.readdir(entryPath);
1330
+ if (subEntries.length === 0) {
1331
+ await fs6.remove(entryPath);
1332
+ }
1333
+ }
1334
+ }
1335
+ };
1336
+ await cleanEmptyDirs(templatePath);
1337
+ metadata.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1338
+ await saveTemplateMetadata(name, metadata);
1339
+ const updatedTemplate = {
1340
+ ...template,
1341
+ updatedAt: metadata.updatedAt
1342
+ };
1343
+ await addTemplateToStore(name, updatedTemplate);
1344
+ spinner8.stop(`Removed ${removedCount} file(s)`);
1345
+ console.log();
1346
+ console.log(
1347
+ ` Template now has ${pc9.green(metadata.files.length.toString())} files remaining`
1348
+ );
1349
+ console.log();
1350
+ p8.outro("Template updated! \u2713");
1351
+ }
1352
+
1353
+ // src/commands/delete.ts
1354
+ import * as p9 from "@clack/prompts";
1355
+ import pc10 from "picocolors";
1356
+ async function deleteCommand(nameOrIndex, options) {
1357
+ if (!await isInitialized()) {
1358
+ p9.log.error(
1359
+ "CodeBrick is not initialized. Run " + pc10.cyan("brick init") + " first."
1360
+ );
1361
+ process.exit(1);
1362
+ }
1363
+ const name = await resolveTemplateName(nameOrIndex);
1364
+ if (!name) {
1365
+ p9.log.error(
1366
+ `Template '${nameOrIndex}' not found. Run ${pc10.cyan("brick list")} to see available templates.`
1367
+ );
1368
+ process.exit(1);
1369
+ }
1370
+ const template = await getTemplate(name);
1371
+ if (!template) {
1372
+ p9.log.error(`Template '${name}' not found.`);
1373
+ process.exit(1);
1374
+ }
1375
+ let fileCount = 0;
1376
+ if (template.type === "local") {
1377
+ const metadata = await loadTemplateMetadata(name);
1378
+ fileCount = metadata?.files.length || 0;
1379
+ }
1380
+ if (!options.force) {
1381
+ console.log();
1382
+ console.log(pc10.yellow(` Delete template '${name}'?`));
1383
+ console.log();
1384
+ console.log(" This will permanently remove:");
1385
+ if (template.type === "local") {
1386
+ console.log(` - ${fileCount} file${fileCount === 1 ? "" : "s"}`);
1387
+ }
1388
+ console.log(" - All metadata");
1389
+ console.log();
1390
+ const confirmName = await p9.text({
1391
+ message: `Type '${name}' to confirm:`,
1392
+ placeholder: name,
1393
+ validate: (value) => {
1394
+ if (value !== name) {
1395
+ return `Please type '${name}' to confirm deletion`;
1396
+ }
1397
+ }
1398
+ });
1399
+ if (p9.isCancel(confirmName)) {
1400
+ p9.cancel("Operation cancelled");
1401
+ process.exit(0);
1402
+ }
1403
+ }
1404
+ const spinner8 = p9.spinner();
1405
+ spinner8.start("Deleting template...");
1406
+ try {
1407
+ if (template.type === "local") {
1408
+ await deleteTemplateFiles(name);
1409
+ }
1410
+ await removeTemplateFromStore(name);
1411
+ spinner8.stop("Template deleted");
1412
+ console.log();
1413
+ p9.outro(`Template '${pc10.red(name)}' has been deleted`);
1414
+ } catch (error) {
1415
+ spinner8.stop("Failed to delete template");
1416
+ p9.log.error(
1417
+ error instanceof Error ? error.message : "Unknown error occurred"
1418
+ );
1419
+ process.exit(1);
1420
+ }
1421
+ }
1422
+
1423
+ // src/commands/size.ts
1424
+ import * as p10 from "@clack/prompts";
1425
+ import pc11 from "picocolors";
1426
+ async function sizeCommand(nameOrIndex) {
1427
+ if (!await isInitialized()) {
1428
+ p10.log.error(
1429
+ "CodeBrick is not initialized. Run " + pc11.cyan("brick init") + " first."
1430
+ );
1431
+ process.exit(1);
1432
+ }
1433
+ if (nameOrIndex !== void 0) {
1434
+ await showSingleTemplateSize(nameOrIndex);
1435
+ } else {
1436
+ await showAllTemplateSizes();
1437
+ }
1438
+ }
1439
+ async function showSingleTemplateSize(nameOrIndex) {
1440
+ const name = await resolveTemplateName(nameOrIndex);
1441
+ if (!name) {
1442
+ p10.log.error(
1443
+ `Template '${nameOrIndex}' not found. Run ${pc11.cyan("brick list")} to see available templates.`
1444
+ );
1445
+ process.exit(1);
1446
+ }
1447
+ const template = await getTemplate(name);
1448
+ if (!template) {
1449
+ p10.log.error(`Template '${name}' not found.`);
1450
+ process.exit(1);
1451
+ }
1452
+ if (template.type === "remote") {
1453
+ console.log();
1454
+ console.log(pc11.bold(pc11.blue(name)) + pc11.dim(" (remote)"));
1455
+ console.log();
1456
+ console.log(
1457
+ pc11.yellow("Remote templates don't use local disk space.")
1458
+ );
1459
+ console.log(
1460
+ pc11.dim("Files are fetched from GitHub on-demand when applied.")
1461
+ );
1462
+ console.log();
1463
+ return;
1464
+ }
1465
+ const templatePath = getTemplatePath(name);
1466
+ const stats = await getDirectoryStats(templatePath);
1467
+ console.log();
1468
+ console.log(pc11.bold(pc11.green(name)));
1469
+ console.log();
1470
+ console.log(` Files: ${stats.files}`);
1471
+ console.log(` Directories: ${stats.directories}`);
1472
+ console.log(` Total Size: ${pc11.cyan(formatSize(stats.totalSize))}`);
1473
+ console.log();
1474
+ }
1475
+ async function showAllTemplateSizes() {
1476
+ const store = await loadStore();
1477
+ const templates = Object.entries(store.templates);
1478
+ if (templates.length === 0) {
1479
+ console.log();
1480
+ console.log(pc11.dim(" No templates saved yet"));
1481
+ console.log();
1482
+ return;
1483
+ }
1484
+ const headers = ["#", "Name", "Type", "Files", "Size"];
1485
+ const columnWidths = [3, 28, 8, 8, 12];
1486
+ const rows = [];
1487
+ let totalSize = 0;
1488
+ let totalFiles = 0;
1489
+ for (let index = 0; index < templates.length; index++) {
1490
+ const [name, template] = templates[index];
1491
+ const typeColor = template.type === "local" ? pc11.green : pc11.blue;
1492
+ if (template.type === "local") {
1493
+ const templatePath = getTemplatePath(name);
1494
+ const stats = await getDirectoryStats(templatePath);
1495
+ totalSize += stats.totalSize;
1496
+ totalFiles += stats.files;
1497
+ rows.push([
1498
+ pc11.yellow(index.toString()),
1499
+ truncate(name, columnWidths[1]),
1500
+ typeColor(template.type),
1501
+ stats.files.toString(),
1502
+ formatSize(stats.totalSize)
1503
+ ]);
1504
+ } else {
1505
+ rows.push([
1506
+ pc11.yellow(index.toString()),
1507
+ truncate(name, columnWidths[1]),
1508
+ typeColor(template.type),
1509
+ pc11.dim("-"),
1510
+ pc11.dim("remote")
1511
+ ]);
1512
+ }
1513
+ }
1514
+ console.log();
1515
+ console.log(createTable(headers, rows, columnWidths));
1516
+ console.log();
1517
+ console.log(
1518
+ pc11.dim(`Total: ${totalFiles} files, ${pc11.cyan(formatSize(totalSize))}`)
1519
+ );
1520
+ console.log();
1521
+ }
1522
+
1523
+ // src/commands/clean.ts
1524
+ import * as p11 from "@clack/prompts";
1525
+ import pc12 from "picocolors";
1526
+ import fs7 from "fs-extra";
1527
+ import path7 from "path";
1528
+ var LOCAL_IMPORT_PATTERNS = {
1529
+ // Dart/Flutter: package:project_name/... imports
1530
+ ".dart": [
1531
+ /^import\s+['"]package:[^\/]+\/[^'"]+['"]\s*;?\s*$/gm,
1532
+ /^export\s+['"]package:[^\/]+\/[^'"]+['"]\s*;?\s*$/gm
1533
+ ],
1534
+ // TypeScript/JavaScript: relative imports
1535
+ ".ts": [
1536
+ /^import\s+.*from\s+['"]\.\.?\/[^'"]+['"]\s*;?\s*$/gm,
1537
+ /^import\s+['"]\.\.?\/[^'"]+['"]\s*;?\s*$/gm,
1538
+ /^export\s+.*from\s+['"]\.\.?\/[^'"]+['"]\s*;?\s*$/gm
1539
+ ],
1540
+ ".tsx": [
1541
+ /^import\s+.*from\s+['"]\.\.?\/[^'"]+['"]\s*;?\s*$/gm,
1542
+ /^import\s+['"]\.\.?\/[^'"]+['"]\s*;?\s*$/gm,
1543
+ /^export\s+.*from\s+['"]\.\.?\/[^'"]+['"]\s*;?\s*$/gm
1544
+ ],
1545
+ ".js": [
1546
+ /^import\s+.*from\s+['"]\.\.?\/[^'"]+['"]\s*;?\s*$/gm,
1547
+ /^import\s+['"]\.\.?\/[^'"]+['"]\s*;?\s*$/gm,
1548
+ /^export\s+.*from\s+['"]\.\.?\/[^'"]+['"]\s*;?\s*$/gm,
1549
+ /^const\s+\w+\s*=\s*require\s*\(\s*['"]\.\.?\/[^'"]+['"]\s*\)\s*;?\s*$/gm
1550
+ ],
1551
+ ".jsx": [
1552
+ /^import\s+.*from\s+['"]\.\.?\/[^'"]+['"]\s*;?\s*$/gm,
1553
+ /^import\s+['"]\.\.?\/[^'"]+['"]\s*;?\s*$/gm,
1554
+ /^export\s+.*from\s+['"]\.\.?\/[^'"]+['"]\s*;?\s*$/gm
1555
+ ],
1556
+ // Python: relative imports
1557
+ ".py": [
1558
+ /^from\s+\.\S*\s+import\s+.*$/gm,
1559
+ /^import\s+\.\S+.*$/gm
1560
+ ],
1561
+ // Go: project-specific imports (anything not from standard lib or known packages)
1562
+ ".go": [
1563
+ // Will be handled with custom pattern
1564
+ ],
1565
+ // Rust: crate-relative imports
1566
+ ".rs": [
1567
+ /^use\s+crate::.*;\s*$/gm,
1568
+ /^use\s+super::.*;\s*$/gm
1569
+ ],
1570
+ // Swift: no standard local import pattern, handled by custom pattern
1571
+ ".swift": [],
1572
+ // Kotlin: project-specific imports
1573
+ ".kt": []
1574
+ };
1575
+ var EXTERNAL_PREFIXES = {
1576
+ ".dart": [
1577
+ "package:flutter/",
1578
+ "package:flutter_",
1579
+ "package:dart:",
1580
+ "package:go_router/",
1581
+ "package:dio/",
1582
+ "package:http/",
1583
+ "package:provider/",
1584
+ "package:bloc/",
1585
+ "package:flutter_bloc/",
1586
+ "package:get/",
1587
+ "package:riverpod/",
1588
+ "package:hooks_riverpod/",
1589
+ "package:freezed_annotation/",
1590
+ "package:json_annotation/",
1591
+ "package:equatable/",
1592
+ "package:dartz/",
1593
+ "package:injectable/",
1594
+ "package:get_it/",
1595
+ "package:shared_preferences/",
1596
+ "package:path_provider/",
1597
+ "package:sqflite/",
1598
+ "package:hive/",
1599
+ "package:firebase_",
1600
+ "package:cloud_firestore/",
1601
+ "package:intl/",
1602
+ "package:cached_network_image/",
1603
+ "package:image_picker/",
1604
+ "package:url_launcher/",
1605
+ "package:connectivity",
1606
+ "package:permission_handler/",
1607
+ "package:auto_route/",
1608
+ "package:mockito/",
1609
+ "package:test/",
1610
+ "package:flutter_test/"
1611
+ ]
1612
+ };
1613
+ function isExternalImport(line, ext) {
1614
+ const prefixes = EXTERNAL_PREFIXES[ext] || [];
1615
+ return prefixes.some((prefix) => line.includes(prefix));
1616
+ }
1617
+ function detectProjectName(templatePath) {
1618
+ const pubspecPath = path7.join(templatePath, "pubspec.yaml");
1619
+ if (fs7.existsSync(pubspecPath)) {
1620
+ try {
1621
+ const content = fs7.readFileSync(pubspecPath, "utf-8");
1622
+ const match = content.match(/^name:\s*(\S+)/m);
1623
+ if (match) return match[1];
1624
+ } catch {
1625
+ }
1626
+ }
1627
+ const packageJsonPath = path7.join(templatePath, "package.json");
1628
+ if (fs7.existsSync(packageJsonPath)) {
1629
+ try {
1630
+ const content = JSON.parse(fs7.readFileSync(packageJsonPath, "utf-8"));
1631
+ if (content.name) return content.name.replace(/^@[^/]+\//, "");
1632
+ } catch {
1633
+ }
1634
+ }
1635
+ const pyprojectPath = path7.join(templatePath, "pyproject.toml");
1636
+ if (fs7.existsSync(pyprojectPath)) {
1637
+ try {
1638
+ const content = fs7.readFileSync(pyprojectPath, "utf-8");
1639
+ const match = content.match(/name\s*=\s*["']([^"']+)["']/);
1640
+ if (match) return match[1];
1641
+ } catch {
1642
+ }
1643
+ }
1644
+ return null;
1645
+ }
1646
+ function cleanFileContent(content, ext, customPattern, projectName, keepExternal = true) {
1647
+ let cleaned = content;
1648
+ let removedCount = 0;
1649
+ const lines = content.split("\n");
1650
+ const cleanedLines = [];
1651
+ for (const line of lines) {
1652
+ let shouldRemove = false;
1653
+ if (customPattern) {
1654
+ const regex = new RegExp(customPattern);
1655
+ if (regex.test(line)) {
1656
+ shouldRemove = true;
1657
+ }
1658
+ }
1659
+ if (!shouldRemove && ext === ".dart" && projectName) {
1660
+ const projectImportPattern = new RegExp(
1661
+ `^(import|export)\\s+['"]package:${projectName}/`
1662
+ );
1663
+ if (projectImportPattern.test(line.trim())) {
1664
+ if (keepExternal && isExternalImport(line, ext)) {
1665
+ shouldRemove = false;
1666
+ } else {
1667
+ shouldRemove = true;
1668
+ }
1669
+ }
1670
+ }
1671
+ if (!shouldRemove) {
1672
+ const patterns = LOCAL_IMPORT_PATTERNS[ext] || [];
1673
+ for (const pattern of patterns) {
1674
+ pattern.lastIndex = 0;
1675
+ if (pattern.test(line.trim())) {
1676
+ if (keepExternal && isExternalImport(line, ext)) {
1677
+ shouldRemove = false;
1678
+ } else {
1679
+ shouldRemove = true;
1680
+ }
1681
+ break;
1682
+ }
1683
+ }
1684
+ }
1685
+ if (shouldRemove) {
1686
+ removedCount++;
1687
+ } else {
1688
+ cleanedLines.push(line);
1689
+ }
1690
+ }
1691
+ cleaned = cleanedLines.join("\n").replace(/\n{3,}/g, "\n\n");
1692
+ return { cleaned, removedCount };
1693
+ }
1694
+ async function cleanCommand(nameOrIndex, options) {
1695
+ p11.intro(pc12.cyan("\u{1F9F9} Cleaning template imports"));
1696
+ if (!await isInitialized()) {
1697
+ p11.log.error(
1698
+ "CodeBrick is not initialized. Run " + pc12.cyan("brick init") + " first."
1699
+ );
1700
+ process.exit(1);
1701
+ }
1702
+ const name = await resolveTemplateName(nameOrIndex);
1703
+ if (!name) {
1704
+ p11.log.error(
1705
+ `Template '${nameOrIndex}' not found. Run ${pc12.cyan("brick list")} to see available templates.`
1706
+ );
1707
+ process.exit(1);
1708
+ }
1709
+ const template = await getTemplate(name);
1710
+ if (!template) {
1711
+ p11.log.error(`Template '${name}' not found.`);
1712
+ process.exit(1);
1713
+ }
1714
+ if (template.type === "remote") {
1715
+ p11.log.error(
1716
+ "Cannot clean remote templates. Use " + pc12.cyan(`brick pull ${name}`) + " to convert to local first."
1717
+ );
1718
+ process.exit(1);
1719
+ }
1720
+ const templatePath = getTemplatePath(name);
1721
+ const metadata = await loadTemplateMetadata(name);
1722
+ if (!metadata) {
1723
+ p11.log.error("Failed to load template metadata.");
1724
+ process.exit(1);
1725
+ }
1726
+ const projectName = detectProjectName(templatePath);
1727
+ if (projectName) {
1728
+ p11.log.info(`Detected project name: ${pc12.cyan(projectName)}`);
1729
+ }
1730
+ if (options.pattern) {
1731
+ p11.log.info(`Using custom pattern: ${pc12.cyan(options.pattern)}`);
1732
+ }
1733
+ const filesToClean = [];
1734
+ let totalRemovals = 0;
1735
+ for (const file of metadata.files) {
1736
+ const ext = path7.extname(file).toLowerCase();
1737
+ const filePath = path7.join(templatePath, file);
1738
+ if (!LOCAL_IMPORT_PATTERNS[ext] && !options.pattern) continue;
1739
+ try {
1740
+ const content = await fs7.readFile(filePath, "utf-8");
1741
+ const { removedCount } = cleanFileContent(
1742
+ content,
1743
+ ext,
1744
+ options.pattern,
1745
+ projectName || void 0,
1746
+ options.keepExternal !== false
1747
+ );
1748
+ if (removedCount > 0) {
1749
+ filesToClean.push({ file, ext, removals: removedCount });
1750
+ totalRemovals += removedCount;
1751
+ }
1752
+ } catch {
1753
+ }
1754
+ }
1755
+ if (filesToClean.length === 0) {
1756
+ p11.log.info("No local imports found to clean.");
1757
+ p11.outro("Template is already clean! \u2713");
1758
+ return;
1759
+ }
1760
+ console.log();
1761
+ console.log(pc12.bold(` Files to clean:`));
1762
+ for (const { file, removals } of filesToClean.slice(0, 15)) {
1763
+ console.log(` ${pc12.yellow(file)} ${pc12.dim(`(${removals} imports)`)}`);
1764
+ }
1765
+ if (filesToClean.length > 15) {
1766
+ console.log(pc12.dim(` ... and ${filesToClean.length - 15} more files`));
1767
+ }
1768
+ console.log();
1769
+ console.log(
1770
+ ` Total: ${pc12.red(totalRemovals.toString())} local imports to remove`
1771
+ );
1772
+ console.log();
1773
+ if (options.dryRun) {
1774
+ p11.log.info(pc12.yellow("Dry run - no changes made"));
1775
+ p11.outro("Run without --dry-run to apply changes");
1776
+ return;
1777
+ }
1778
+ const confirmed = await p11.confirm({
1779
+ message: `Remove ${totalRemovals} local imports from ${filesToClean.length} files?`,
1780
+ initialValue: true
1781
+ });
1782
+ if (p11.isCancel(confirmed) || !confirmed) {
1783
+ p11.cancel("Operation cancelled");
1784
+ process.exit(0);
1785
+ }
1786
+ const spinner8 = p11.spinner();
1787
+ spinner8.start("Cleaning imports...");
1788
+ let cleanedFiles = 0;
1789
+ for (const { file, ext } of filesToClean) {
1790
+ const filePath = path7.join(templatePath, file);
1791
+ try {
1792
+ const content = await fs7.readFile(filePath, "utf-8");
1793
+ const { cleaned } = cleanFileContent(
1794
+ content,
1795
+ ext,
1796
+ options.pattern,
1797
+ projectName || void 0,
1798
+ options.keepExternal !== false
1799
+ );
1800
+ await fs7.writeFile(filePath, cleaned, "utf-8");
1801
+ cleanedFiles++;
1802
+ } catch {
1803
+ }
1804
+ }
1805
+ metadata.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1806
+ await saveTemplateMetadata(name, metadata);
1807
+ spinner8.stop(`Cleaned ${cleanedFiles} files`);
1808
+ console.log();
1809
+ console.log(` ${pc12.green("\u2713")} Removed ${totalRemovals} local imports`);
1810
+ console.log();
1811
+ p11.outro("Template cleaned successfully! \u2713");
1812
+ }
1813
+
1814
+ // src/index.ts
1815
+ var program = new Command();
1816
+ program.name("brick").description(
1817
+ pc13.cyan("\u{1F9F1} CodeBrick") + " - A framework-agnostic CLI for managing reusable code templates"
1818
+ ).version("0.1.0");
1819
+ program.command("init").description("Initialize CodeBrick in the system").action(initCommand);
1820
+ program.command("save <name> [path]").description("Create a new LOCAL template from files").option("-d, --description <desc>", "Template description").option("-t, --tags <tags>", "Comma-separated tags").option("--include <patterns>", "Glob patterns to include").option("--exclude <patterns>", "Glob patterns to exclude").option("--detect-deps", "Auto-detect dependencies from imports").action(saveCommand);
1821
+ program.command("list").alias("ls").description("List all saved templates").option("--local", "Show only local templates").option("--remote", "Show only remote templates").option("--tag <tag>", "Filter by tag").option("--detailed", "Show detailed view").action(listCommand);
1822
+ program.command("tree <name>").description("Display template file structure").option("--size", "Show file sizes").option("--preview", "Show content preview").action(treeCommand);
1823
+ program.command("apply <name> [destination]").description("Apply template to current/specified directory").option("-f, --force", "Overwrite existing files without prompting").option("--skip-existing", "Skip files that already exist").option("--dry-run", "Preview changes without writing files").option("--latest", "Use @latest for all dependency versions").option("--no-deps", "Don't prompt for dependency installation").action(applyCommand);
1824
+ program.command("info <name>").description("Show detailed template information").action(infoCommand);
1825
+ program.command("add <name> <files...>").description("Add files to an existing LOCAL template").action(addCommand);
1826
+ program.command("remove-file <name> <files...>").description("Remove files from a LOCAL template").action(removeFileCommand);
1827
+ program.command("delete <name>").alias("rm").description("Delete a template entirely").option("-f, --force", "Skip confirmation").action(deleteCommand);
1828
+ program.command("size [name]").description("Show template size(s) - all templates if no name provided").action(sizeCommand);
1829
+ program.command("clean <name>").description("Remove local/project-specific imports from template files").option("-p, --pattern <regex>", "Custom regex pattern to match imports to remove").option("--dry-run", "Preview changes without modifying files").option("--no-keep-external", "Also remove external package imports").action(cleanCommand);
1830
+ program.parse();