@carefully-built/cli 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,23 +1,143 @@
1
1
  # @carefully-built/cli
2
2
 
3
- CLI for adding Carefully Built components to apps as editable source code.
3
+ ![Carefully Built CLI hero](./assets/hero.png)
4
4
 
5
- ## Usage
5
+ Bring Carefully Built SaaS components into a React app in the form that fits the job: import the maintained package when you want upgrades, or eject the source when a component needs to become part of the product.
6
+
7
+ The CLI is the editable-source side of the Carefully Built SaaS Kit. It copies registry components into your app using the same mental model as shadcn: the code lands in your repository, keeps normal imports such as `@/lib/utils`, and can be changed without waiting for a package release.
8
+
9
+ ## Two Ways To Use The Kit
10
+
11
+ ### 1. Managed Package Imports
12
+
13
+ Use package imports when the component should stay shared and receive fixes through npm updates.
14
+
15
+ ```bash
16
+ bun add @carefully-built/ui
17
+ ```
18
+
19
+ ```tsx
20
+ import { Button, SmartTable, TableToolbar } from "@carefully-built/ui";
21
+ ```
22
+
23
+ This is the best default for stable primitives, CRUD helpers, table controls, layout shells, and shared SaaS behavior that should remain consistent across apps.
24
+
25
+ ### 2. Editable Source With The CLI
26
+
27
+ Use the CLI when a component needs to become local code.
28
+
29
+ ```bash
30
+ bunx @carefully-built/cli add button
31
+ ```
32
+
33
+ The CLI reads common shadcn project conventions. If your app uses `components.json` aliases and `tsconfig.json` paths like `@/* -> ./src/*`, files are copied into `src/components/ui` and `src/lib`. Without those conventions, files are copied to root-level `components/ui` and `lib`.
34
+
35
+ ## Install And Commands
36
+
37
+ You can run it without installing:
6
38
 
7
39
  ```bash
8
40
  bunx @carefully-built/cli list
9
41
  bunx @carefully-built/cli add button
10
42
  ```
11
43
 
12
- Use `--overwrite` to replace existing files:
44
+ Or with npm:
45
+
46
+ ```bash
47
+ npx @carefully-built/cli list
48
+ npx @carefully-built/cli add button
49
+ ```
50
+
51
+ Replace existing local files only when you ask for it:
13
52
 
14
53
  ```bash
15
54
  bunx @carefully-built/cli add button --overwrite
16
55
  ```
17
56
 
18
- The first registry entry copies:
57
+ ## What Gets Copied
58
+
59
+ The first registry entry is intentionally small:
60
+
61
+ ```txt
62
+ button
63
+ ├─ components/ui/button.tsx
64
+ └─ lib/utils.ts
65
+ ```
66
+
67
+ In a `src` app with shadcn aliases, that becomes:
68
+
69
+ ```txt
70
+ button
71
+ ├─ src/components/ui/button.tsx
72
+ └─ src/lib/utils.ts
73
+ ```
74
+
75
+ The copied `Button` uses:
19
76
 
20
- - `components/ui/button.tsx`
21
- - `lib/utils.ts`
77
+ - `class-variance-authority`
78
+ - `clsx`
79
+ - `tailwind-merge`
80
+ - `react`
81
+ - `radix-ui`
22
82
 
23
- Install the printed dependencies in the target app if they are not already present.
83
+ The CLI prints dependency hints after copying. Install anything your app does not already provide.
84
+
85
+ ## When To Import vs Eject
86
+
87
+ Import from `@carefully-built/ui` when:
88
+
89
+ - you want package updates and bug fixes without owning the source
90
+ - the component is generic enough to stay shared
91
+ - app-specific behavior can be passed through props, `className`, slots, or `classes`
92
+
93
+ Use `@carefully-built/cli add` when:
94
+
95
+ - the component is about to become product-specific
96
+ - design or behavior needs local edits
97
+ - you want to inspect and own every line in the consuming app
98
+ - the component should follow the same source-control workflow as the rest of the app
99
+
100
+ ## Current Registry
101
+
102
+ ```bash
103
+ bunx @carefully-built/cli list
104
+ ```
105
+
106
+ Available now:
107
+
108
+ - `button`
109
+
110
+ More registry entries should be added deliberately, starting with low-risk primitives and then moving toward composed SaaS surfaces such as tables, toolbars, forms, and shells.
111
+
112
+ ## Project Assumptions
113
+
114
+ The generated components assume a modern React app with:
115
+
116
+ - React 18 or 19
117
+ - TypeScript
118
+ - Tailwind-compatible design tokens such as `bg-primary`, `text-primary-foreground`, `border`, `ring`, and `muted`
119
+ - an alias for `@/lib/utils`, or a project structure where the CLI can resolve that alias through `components.json` and `tsconfig.json`
120
+
121
+ ## Development
122
+
123
+ From the monorepo root:
124
+
125
+ ```bash
126
+ bun install
127
+ bun run --cwd packages/cli test
128
+ bun run --cwd packages/cli typecheck
129
+ bun run --cwd packages/cli build
130
+ ```
131
+
132
+ Before publishing:
133
+
134
+ ```bash
135
+ cd packages/cli
136
+ npm publish --dry-run --access public
137
+ ```
138
+
139
+ Publish:
140
+
141
+ ```bash
142
+ npm publish --access public
143
+ ```
Binary file
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { constants, readFileSync } from "node:fs";
3
- import { access, copyFile, mkdir } from "node:fs/promises";
4
- import { dirname, join } from "node:path";
3
+ import { access, copyFile, mkdir, readFile } from "node:fs/promises";
4
+ import { dirname, join, normalize } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
 
7
7
  //#region src/registry.ts
@@ -31,17 +31,19 @@ async function addComponent({ componentName, cwd, overwrite }) {
31
31
  const created = [];
32
32
  const overwritten = [];
33
33
  const skipped = [];
34
+ const projectConfig = await readProjectConfig(cwd);
34
35
  for (const file of component.files) {
35
- const targetPath = join(cwd, file.target);
36
+ const target = resolveTargetPath(file.target, projectConfig);
37
+ const targetPath = join(cwd, target);
36
38
  const exists = await fileExists(targetPath);
37
39
  if (exists && !overwrite) {
38
- skipped.push(file.target);
40
+ skipped.push(target);
39
41
  continue;
40
42
  }
41
43
  await mkdir(dirname(targetPath), { recursive: true });
42
44
  await copyFile(file.source, targetPath);
43
- if (exists) overwritten.push(file.target);
44
- else created.push(file.target);
45
+ if (exists) overwritten.push(target);
46
+ else created.push(target);
45
47
  }
46
48
  return {
47
49
  componentName: component.name,
@@ -60,6 +62,61 @@ async function fileExists(path) {
60
62
  return false;
61
63
  }
62
64
  }
65
+ async function readProjectConfig(cwd) {
66
+ const [componentsJson, tsconfigJson] = await Promise.all([readJsonFile(join(cwd, "components.json")), readJsonFile(join(cwd, "tsconfig.json"))]);
67
+ return {
68
+ uiAlias: readString(componentsJson, ["aliases", "ui"]),
69
+ utilsAlias: readString(componentsJson, ["aliases", "utils"]),
70
+ ...readPrimaryTsconfigAlias(tsconfigJson)
71
+ };
72
+ }
73
+ function resolveTargetPath(target, config) {
74
+ if (target === "lib/utils.ts" && config.utilsAlias) return resolveAliasPath(`${config.utilsAlias}.ts`, config);
75
+ if (target.startsWith("components/ui/") && config.uiAlias) {
76
+ const fileName = target.slice(14);
77
+ return resolveAliasPath(`${config.uiAlias}/${fileName}`, config);
78
+ }
79
+ return target;
80
+ }
81
+ function resolveAliasPath(path, config) {
82
+ if (config.aliasPrefix && config.aliasTarget && path.startsWith(config.aliasPrefix)) return cleanPath(path.replace(config.aliasPrefix, config.aliasTarget));
83
+ if (path.startsWith("@/")) return cleanPath(path.slice(2));
84
+ return cleanPath(path);
85
+ }
86
+ function readPrimaryTsconfigAlias(value) {
87
+ const aliasTarget = readArray(readRecord(readRecord(value, "compilerOptions"), "paths"), "@/*").find((entry) => typeof entry === "string");
88
+ if (typeof aliasTarget !== "string") return {};
89
+ return {
90
+ aliasPrefix: "@/",
91
+ aliasTarget: aliasTarget.replace(/\/\*$/, "/")
92
+ };
93
+ }
94
+ async function readJsonFile(path) {
95
+ try {
96
+ return JSON.parse(stripJsonComments(await readFile(path, "utf8")));
97
+ } catch {
98
+ return;
99
+ }
100
+ }
101
+ function stripJsonComments(source) {
102
+ return source.replace(/^\s*\/\/.*$/gm, "");
103
+ }
104
+ function readString(value, path) {
105
+ let current = value;
106
+ for (const key of path) current = readRecord(current, key);
107
+ return typeof current === "string" ? current : void 0;
108
+ }
109
+ function readRecord(value, key) {
110
+ if (!value || typeof value !== "object") return;
111
+ return value[key];
112
+ }
113
+ function readArray(value, key) {
114
+ const array = readRecord(value, key);
115
+ return Array.isArray(array) ? array : [];
116
+ }
117
+ function cleanPath(path) {
118
+ return normalize(path).replace(/^\.\//, "");
119
+ }
63
120
 
64
121
  //#endregion
65
122
  //#region src/index.ts
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["created: string[]","overwritten: string[]","skipped: string[]"],"sources":["../src/registry.ts","../src/add.ts","../src/index.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nexport interface RegistryFile {\n readonly source: string;\n readonly target: string;\n}\n\nexport interface RegistryComponent {\n readonly name: string;\n readonly description: string;\n readonly dependencies: readonly string[];\n readonly peerDependencies: readonly string[];\n readonly files: readonly RegistryFile[];\n}\n\nconst packageRoot = join(dirname(fileURLToPath(import.meta.url)), \"..\");\nconst registryRoot = join(packageRoot, \"registry\");\nconst componentNames = [\"button\"] as const;\n\nexport function listRegistryComponents(): string[] {\n return [...componentNames];\n}\n\nexport function getRegistryComponent(\n componentName: string,\n): RegistryComponent | undefined {\n if (!componentNames.includes(componentName as (typeof componentNames)[number])) {\n return undefined;\n }\n\n const manifestPath = join(registryRoot, \"ui\", componentName, \"manifest.json\");\n const manifest = JSON.parse(readFileSync(manifestPath, \"utf8\")) as Omit<\n RegistryComponent,\n \"files\"\n > & {\n files: readonly RegistryFile[];\n };\n\n return {\n ...manifest,\n files: manifest.files.map((file) => ({\n ...file,\n source: join(registryRoot, \"ui\", componentName, file.source),\n })),\n };\n}\n","import { constants } from \"node:fs\";\nimport { access, copyFile, mkdir } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\n\nimport { getRegistryComponent } from \"./registry\";\n\nexport interface AddComponentOptions {\n readonly componentName: string;\n readonly cwd: string;\n readonly overwrite: boolean;\n}\n\nexport interface AddComponentResult {\n readonly componentName: string;\n readonly created: string[];\n readonly overwritten: string[];\n readonly skipped: string[];\n readonly dependencies: readonly string[];\n readonly peerDependencies: readonly string[];\n}\n\nexport async function addComponent({\n componentName,\n cwd,\n overwrite,\n}: AddComponentOptions): Promise<AddComponentResult> {\n const component = getRegistryComponent(componentName);\n\n if (!component) {\n throw new Error(`Unknown component \"${componentName}\"`);\n }\n\n const created: string[] = [];\n const overwritten: string[] = [];\n const skipped: string[] = [];\n\n for (const file of component.files) {\n const targetPath = join(cwd, file.target);\n const exists = await fileExists(targetPath);\n\n if (exists && !overwrite) {\n skipped.push(file.target);\n continue;\n }\n\n await mkdir(dirname(targetPath), { recursive: true });\n await copyFile(file.source, targetPath);\n\n if (exists) {\n overwritten.push(file.target);\n } else {\n created.push(file.target);\n }\n }\n\n return {\n componentName: component.name,\n created,\n overwritten,\n skipped,\n dependencies: component.dependencies,\n peerDependencies: component.peerDependencies,\n };\n}\n\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await access(path, constants.F_OK);\n return true;\n } catch {\n return false;\n }\n}\n","#!/usr/bin/env node\nimport { addComponent } from \"./add\";\nimport { listRegistryComponents } from \"./registry\";\n\ninterface ParsedArgs {\n readonly command?: string;\n readonly componentName?: string;\n readonly overwrite: boolean;\n readonly help: boolean;\n}\n\nexport async function runCli(argv = process.argv.slice(2)): Promise<void> {\n const args = parseArgs(argv);\n\n if (args.help || !args.command) {\n printHelp();\n return;\n }\n\n if (args.command === \"list\") {\n for (const componentName of listRegistryComponents()) {\n console.log(componentName);\n }\n return;\n }\n\n if (args.command === \"add\") {\n if (!args.componentName) {\n throw new Error(\"Missing component name. Example: carefully-built add button\");\n }\n\n const result = await addComponent({\n componentName: args.componentName,\n cwd: process.cwd(),\n overwrite: args.overwrite,\n });\n\n printAddResult(result);\n return;\n }\n\n throw new Error(`Unknown command \"${args.command}\"`);\n}\n\nfunction parseArgs(argv: readonly string[]): ParsedArgs {\n return {\n command: argv[0],\n componentName: argv[1]?.startsWith(\"-\") ? undefined : argv[1],\n overwrite: argv.includes(\"--overwrite\"),\n help: argv.includes(\"--help\") || argv.includes(\"-h\"),\n };\n}\n\nfunction printHelp(): void {\n console.log(`carefully-built\n\nUsage:\n carefully-built list\n carefully-built add <component> [--overwrite]\n\nComponents:\n ${listRegistryComponents().join(\", \")}\n`);\n}\n\nfunction printAddResult(result: Awaited<ReturnType<typeof addComponent>>): void {\n for (const file of result.created) {\n console.log(`created ${file}`);\n }\n for (const file of result.overwritten) {\n console.log(`overwrote ${file}`);\n }\n for (const file of result.skipped) {\n console.log(`skipped ${file}`);\n }\n\n if (result.dependencies.length > 0) {\n console.log(`dependencies: ${result.dependencies.join(\", \")}`);\n }\n if (result.peerDependencies.length > 0) {\n console.log(`peer dependencies: ${result.peerDependencies.join(\", \")}`);\n }\n}\n\nrunCli().catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n console.error(message);\n process.exitCode = 1;\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["created: string[]","overwritten: string[]","skipped: string[]"],"sources":["../src/registry.ts","../src/add.ts","../src/index.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nexport interface RegistryFile {\n readonly source: string;\n readonly target: string;\n}\n\nexport interface RegistryComponent {\n readonly name: string;\n readonly description: string;\n readonly dependencies: readonly string[];\n readonly peerDependencies: readonly string[];\n readonly files: readonly RegistryFile[];\n}\n\nconst packageRoot = join(dirname(fileURLToPath(import.meta.url)), \"..\");\nconst registryRoot = join(packageRoot, \"registry\");\nconst componentNames = [\"button\"] as const;\n\nexport function listRegistryComponents(): string[] {\n return [...componentNames];\n}\n\nexport function getRegistryComponent(\n componentName: string,\n): RegistryComponent | undefined {\n if (!componentNames.includes(componentName as (typeof componentNames)[number])) {\n return undefined;\n }\n\n const manifestPath = join(registryRoot, \"ui\", componentName, \"manifest.json\");\n const manifest = JSON.parse(readFileSync(manifestPath, \"utf8\")) as Omit<\n RegistryComponent,\n \"files\"\n > & {\n files: readonly RegistryFile[];\n };\n\n return {\n ...manifest,\n files: manifest.files.map((file) => ({\n ...file,\n source: join(registryRoot, \"ui\", componentName, file.source),\n })),\n };\n}\n","import { constants } from \"node:fs\";\nimport { access, copyFile, mkdir, readFile } from \"node:fs/promises\";\nimport { dirname, join, normalize } from \"node:path\";\n\nimport { getRegistryComponent } from \"./registry\";\n\nexport interface AddComponentOptions {\n readonly componentName: string;\n readonly cwd: string;\n readonly overwrite: boolean;\n}\n\nexport interface AddComponentResult {\n readonly componentName: string;\n readonly created: string[];\n readonly overwritten: string[];\n readonly skipped: string[];\n readonly dependencies: readonly string[];\n readonly peerDependencies: readonly string[];\n}\n\nexport async function addComponent({\n componentName,\n cwd,\n overwrite,\n}: AddComponentOptions): Promise<AddComponentResult> {\n const component = getRegistryComponent(componentName);\n\n if (!component) {\n throw new Error(`Unknown component \"${componentName}\"`);\n }\n\n const created: string[] = [];\n const overwritten: string[] = [];\n const skipped: string[] = [];\n const projectConfig = await readProjectConfig(cwd);\n\n for (const file of component.files) {\n const target = resolveTargetPath(file.target, projectConfig);\n const targetPath = join(cwd, target);\n const exists = await fileExists(targetPath);\n\n if (exists && !overwrite) {\n skipped.push(target);\n continue;\n }\n\n await mkdir(dirname(targetPath), { recursive: true });\n await copyFile(file.source, targetPath);\n\n if (exists) {\n overwritten.push(target);\n } else {\n created.push(target);\n }\n }\n\n return {\n componentName: component.name,\n created,\n overwritten,\n skipped,\n dependencies: component.dependencies,\n peerDependencies: component.peerDependencies,\n };\n}\n\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await access(path, constants.F_OK);\n return true;\n } catch {\n return false;\n }\n}\n\ninterface ProjectConfig {\n readonly uiAlias?: string;\n readonly utilsAlias?: string;\n readonly aliasPrefix?: string;\n readonly aliasTarget?: string;\n}\n\nasync function readProjectConfig(cwd: string): Promise<ProjectConfig> {\n const [componentsJson, tsconfigJson] = await Promise.all([\n readJsonFile(join(cwd, \"components.json\")),\n readJsonFile(join(cwd, \"tsconfig.json\")),\n ]);\n\n return {\n uiAlias: readString(componentsJson, [\"aliases\", \"ui\"]),\n utilsAlias: readString(componentsJson, [\"aliases\", \"utils\"]),\n ...readPrimaryTsconfigAlias(tsconfigJson),\n };\n}\n\nfunction resolveTargetPath(target: string, config: ProjectConfig): string {\n if (target === \"lib/utils.ts\" && config.utilsAlias) {\n return resolveAliasPath(`${config.utilsAlias}.ts`, config);\n }\n\n if (target.startsWith(\"components/ui/\") && config.uiAlias) {\n const fileName = target.slice(\"components/ui/\".length);\n return resolveAliasPath(`${config.uiAlias}/${fileName}`, config);\n }\n\n return target;\n}\n\nfunction resolveAliasPath(path: string, config: ProjectConfig): string {\n if (\n config.aliasPrefix &&\n config.aliasTarget &&\n path.startsWith(config.aliasPrefix)\n ) {\n return cleanPath(path.replace(config.aliasPrefix, config.aliasTarget));\n }\n\n if (path.startsWith(\"@/\")) {\n return cleanPath(path.slice(2));\n }\n\n return cleanPath(path);\n}\n\nfunction readPrimaryTsconfigAlias(value: unknown): Pick<\n ProjectConfig,\n \"aliasPrefix\" | \"aliasTarget\"\n> {\n const paths = readRecord(readRecord(value, \"compilerOptions\"), \"paths\");\n const aliasTargets = readArray(paths, \"@/*\");\n const aliasTarget = aliasTargets.find((entry) => typeof entry === \"string\");\n\n if (typeof aliasTarget !== \"string\") {\n return {};\n }\n\n return {\n aliasPrefix: \"@/\",\n aliasTarget: aliasTarget.replace(/\\/\\*$/, \"/\"),\n };\n}\n\nasync function readJsonFile(path: string): Promise<unknown> {\n try {\n return JSON.parse(stripJsonComments(await readFile(path, \"utf8\")));\n } catch {\n return undefined;\n }\n}\n\nfunction stripJsonComments(source: string): string {\n return source.replace(/^\\s*\\/\\/.*$/gm, \"\");\n}\n\nfunction readString(value: unknown, path: readonly string[]): string | undefined {\n let current = value;\n\n for (const key of path) {\n current = readRecord(current, key);\n }\n\n return typeof current === \"string\" ? current : undefined;\n}\n\nfunction readRecord(value: unknown, key: string): unknown {\n if (!value || typeof value !== \"object\") {\n return undefined;\n }\n\n return (value as Record<string, unknown>)[key];\n}\n\nfunction readArray(value: unknown, key: string): unknown[] {\n const array = readRecord(value, key);\n return Array.isArray(array) ? array : [];\n}\n\nfunction cleanPath(path: string): string {\n return normalize(path).replace(/^\\.\\//, \"\");\n}\n","#!/usr/bin/env node\nimport { addComponent } from \"./add\";\nimport { listRegistryComponents } from \"./registry\";\n\ninterface ParsedArgs {\n readonly command?: string;\n readonly componentName?: string;\n readonly overwrite: boolean;\n readonly help: boolean;\n}\n\nexport async function runCli(argv = process.argv.slice(2)): Promise<void> {\n const args = parseArgs(argv);\n\n if (args.help || !args.command) {\n printHelp();\n return;\n }\n\n if (args.command === \"list\") {\n for (const componentName of listRegistryComponents()) {\n console.log(componentName);\n }\n return;\n }\n\n if (args.command === \"add\") {\n if (!args.componentName) {\n throw new Error(\"Missing component name. Example: carefully-built add button\");\n }\n\n const result = await addComponent({\n componentName: args.componentName,\n cwd: process.cwd(),\n overwrite: args.overwrite,\n });\n\n printAddResult(result);\n return;\n }\n\n throw new Error(`Unknown command \"${args.command}\"`);\n}\n\nfunction parseArgs(argv: readonly string[]): ParsedArgs {\n return {\n command: argv[0],\n componentName: argv[1]?.startsWith(\"-\") ? undefined : argv[1],\n overwrite: argv.includes(\"--overwrite\"),\n help: argv.includes(\"--help\") || argv.includes(\"-h\"),\n };\n}\n\nfunction printHelp(): void {\n console.log(`carefully-built\n\nUsage:\n carefully-built list\n carefully-built add <component> [--overwrite]\n\nComponents:\n ${listRegistryComponents().join(\", \")}\n`);\n}\n\nfunction printAddResult(result: Awaited<ReturnType<typeof addComponent>>): void {\n for (const file of result.created) {\n console.log(`created ${file}`);\n }\n for (const file of result.overwritten) {\n console.log(`overwrote ${file}`);\n }\n for (const file of result.skipped) {\n console.log(`skipped ${file}`);\n }\n\n if (result.dependencies.length > 0) {\n console.log(`dependencies: ${result.dependencies.join(\", \")}`);\n }\n if (result.peerDependencies.length > 0) {\n console.log(`peer dependencies: ${result.peerDependencies.join(\", \")}`);\n }\n}\n\nrunCli().catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n console.error(message);\n process.exitCode = 1;\n});\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@carefully-built/cli",
3
- "version": "0.1.0",
4
- "description": "CLI for adding Carefully Built components to apps.",
3
+ "version": "0.1.1",
4
+ "description": "Add Carefully Built SaaS components to apps as editable source, with package imports when you want managed upgrades.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "Alessandro Dodi",
@@ -25,6 +25,7 @@
25
25
  "carefully-built": "dist/index.mjs"
26
26
  },
27
27
  "files": [
28
+ "assets",
28
29
  "dist",
29
30
  "registry",
30
31
  "README.md"