frakment 0.0.0-stage → 0.0.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/LICENSE ADDED
@@ -0,0 +1,190 @@
1
+ Frakment License
2
+ Version 1.0
3
+
4
+ Copyright (c) 2026 hanshapd <https://github.com/hanshapd>
5
+
6
+ This is a source-available license. It is NOT an open-source license
7
+ as defined by the Open Source Initiative (OSI).
8
+
9
+ In short:
10
+
11
+ - Use Frakment freely for personal, educational, and non-commercial
12
+ purposes. Just keep the attribution.
13
+
14
+ - Use Frakment commercially only after you get one pull request
15
+ merged into the main repository. That is the only path.
16
+
17
+ - Always keep the attribution. Always mark modifications.
18
+
19
+ ====================================================================
20
+ 1. DEFINITIONS
21
+ ====================================================================
22
+
23
+ "Software" means the source code, components, CLI, MCP server, agent
24
+ skills, documentation, and any associated files in this repository,
25
+ whether used in original or modified form.
26
+
27
+ "Personal Use" means use by an individual for personal projects,
28
+ learning, experimentation, hobby work, or internal tools that do not
29
+ generate revenue.
30
+
31
+ "Educational Use" means use by students, teachers, or institutions
32
+ for teaching, coursework, academic research, or non-commercial
33
+ educational activities.
34
+
35
+ "Commercial Use" means any use that:
36
+
37
+ (a) is part of a product, service, or website that generates revenue
38
+ directly or indirectly (including ads, subscriptions, paid
39
+ features, consulting, or paid support);
40
+ (b) is deployed in production by a for-profit entity; or
41
+ (c) is used to build something that is sold, licensed, or monetized
42
+ in any form.
43
+
44
+ "Qualifying Contribution" means a pull request that has been
45
+ reviewed and merged into the main branch of
46
+ github.com/hanshapd/frakment, where all of the following are true:
47
+
48
+ (a) The commit author is you (your GitHub username or email is
49
+ recorded in the commit metadata).
50
+ (b) The pull request makes at least one substantive change: fixes a
51
+ bug, adds a feature, adds or corrects documentation, adds a
52
+ test, translates content, or fixes a typo.
53
+ (c) The pull request has not been reverted within 30 days of being
54
+ merged.
55
+
56
+ There is no minimum number of lines changed. A single-line typo fix
57
+ qualifies. A documentation clarification qualifies. A test addition
58
+ qualifies.
59
+
60
+ A pull request that was closed without merging does NOT qualify. A
61
+ pull request that was merged and later reverted within 30 days does
62
+ NOT qualify.
63
+
64
+ ====================================================================
65
+ 2. GRANT OF RIGHTS
66
+ ====================================================================
67
+
68
+ Subject to the terms of this license, you are granted permission to:
69
+
70
+ (a) View, read, and study the source code.
71
+ (b) Fork, clone, and copy the repository.
72
+ (c) Modify the source code.
73
+ (d) Distribute the original or modified version.
74
+
75
+ These rights apply without additional conditions for Personal Use and
76
+ Educational Use, provided you satisfy the Attribution Requirements in
77
+ Section 3.
78
+
79
+ These rights apply for Commercial Use only after you have made a
80
+ Qualifying Contribution, in addition to the Attribution Requirements.
81
+
82
+ ====================================================================
83
+ 3. ATTRIBUTION REQUIREMENTS (ALWAYS)
84
+ ====================================================================
85
+
86
+ Any use, copy, modification, or distribution of the Software, in
87
+ whole or in part, for Personal, Educational, or Commercial Use, must:
88
+
89
+ (a) Include the following attribution in the README, documentation,
90
+ footer, or another reasonably visible user-facing location:
91
+
92
+ Based on Frakment by hanshapd
93
+ https://github.com/hanshapd/frakment
94
+
95
+ (b) Include this LICENSE file unmodified in all copies and
96
+ derivative works.
97
+
98
+ (c) Clearly mark any modified versions as modified, with a brief
99
+ description of the changes (e.g., in a CHANGELOG or README).
100
+
101
+ ====================================================================
102
+ 4. COMMERCIAL USE REQUIREMENT
103
+ ====================================================================
104
+
105
+ Before using the Software for any Commercial Use, you (or the legal
106
+ entity you represent) must have made one Qualifying Contribution as
107
+ defined in Section 1. This is the only path to Commercial Use.
108
+
109
+ --------------------------------------------------------------------
110
+ 4.1 SCOPE OF SATISFACTION
111
+ --------------------------------------------------------------------
112
+
113
+ Once a Qualifying Contribution has been made, the requirement is
114
+ satisfied permanently for:
115
+
116
+ - the individual who made the contribution, OR
117
+ - the legal entity (company, organization, or team) on whose
118
+ behalf the contribution was made,
119
+
120
+ for all future Commercial Use of the Software by that individual or
121
+ entity. No further contributions are required for future commercial
122
+ use by the same party.
123
+
124
+ --------------------------------------------------------------------
125
+ 4.2 EVIDENCE ON REQUEST
126
+ --------------------------------------------------------------------
127
+
128
+ If hanshapd requests proof that the Commercial Use requirement has
129
+ been satisfied, you must provide, within 30 days, a link to the
130
+ merged pull request on GitHub.
131
+
132
+ Refusal or failure to provide evidence on request voids the
133
+ permission for Commercial Use until evidence is provided.
134
+
135
+ --------------------------------------------------------------------
136
+ 4.3 NO REQUIREMENT FOR NON-COMMERCIAL USE
137
+ --------------------------------------------------------------------
138
+
139
+ Personal Use and Educational Use do NOT require a Qualifying
140
+ Contribution. Attribution (Section 3) is the only requirement for
141
+ those uses.
142
+
143
+ ====================================================================
144
+ 5. RESTRICTIONS
145
+ ====================================================================
146
+
147
+ You may not:
148
+
149
+ (a) Remove, hide, or alter copyright notices or the attribution
150
+ required by Section 3.
151
+ (b) Claim authorship of this Software or of any derivative work.
152
+ (c) Use the names "Frakment", "frakment", or "hanshapd", or any
153
+ associated logos or branding, to endorse or promote derivative
154
+ works without prior written permission.
155
+ (d) Sublicense this Software under different terms. Any party that
156
+ receives a copy from you does so subject to this same license.
157
+
158
+ ====================================================================
159
+ 6. NO TRADEMARK LICENSE
160
+ ====================================================================
161
+
162
+ This license does not grant any rights to use the names "Frakment",
163
+ "frakment", or "hanshapd", or any associated logos or marks, except
164
+ as strictly necessary for the attribution described in Section 3.
165
+
166
+ ====================================================================
167
+ 7. WARRANTY
168
+ ====================================================================
169
+
170
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
171
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
172
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND
173
+ NON-INFRINGEMENT.
174
+
175
+ IN NO EVENT SHALL THE AUTHOR OR COPYRIGHT HOLDER BE LIABLE FOR ANY
176
+ CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
177
+ TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE
178
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
179
+
180
+ ====================================================================
181
+ 8. ACCEPTANCE
182
+ ====================================================================
183
+
184
+ By accessing, using, copying, modifying, or distributing this project,
185
+ you agree to be bound by the terms above. If you do not agree, you are
186
+ not granted permission to use this project and must delete all copies
187
+ in your possession.
188
+
189
+ For permission requests or questions about this license, contact
190
+ hanshapd on GitHub: https://github.com/hanshapd
package/README.md CHANGED
@@ -1,3 +1,88 @@
1
- # Temporary Holding Version
1
+ # frakment CLI
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Monochrome design system and component registry CLI.
4
+
5
+ ## Requirements
6
+
7
+ - Node.js >= 22.12.0
8
+ - Tailwind CSS v4 (`@import "tailwindcss";` in your `globals.css`)
9
+
10
+ ## Usage
11
+
12
+ ### `frakment init`
13
+
14
+ ```bash
15
+ npx frakment@latest init
16
+ ```
17
+
18
+ What it does:
19
+
20
+ 1. Writes `components.json` at project root (CLI config)
21
+ 2. Injects the theme block into `globals.css` between `/* ==== FRAKMENT THEME START/END ==== */` markers (design tokens + 9 `@utility` + `.dark` + `@custom-variant dark`)
22
+ 3. Preserves anything outside the markers — your `@import` statements, overrides, and custom CSS stay untouched
23
+ 4. Creates `lib/utils.ts` (the `cn` helper) if missing
24
+ 5. Installs 6 dependencies: `clsx`, `tailwind-merge`, `class-variance-authority`, `radix-ui`, `lucide-react`, `motion`
25
+
26
+ **Idempotent.** Safe to re-run:
27
+
28
+ - Existing `components.json` is preserved unless you choose to overwrite
29
+ - Marker block is replaced in place (no duplicates)
30
+ - Legacy inline theme blocks (from older `init` versions) are auto-migrated
31
+
32
+ ### `frakment add <component>`
33
+
34
+ ```bash
35
+ npx frakment@latest add button
36
+ ```
37
+
38
+ Fetches the component (plus all `registryDependencies` recursively) from the registry, writes all files, and installs any missing npm dependencies.
39
+
40
+ Flags:
41
+
42
+ - `--registry <url>` — override the registry URL (default: from `components.json`)
43
+ - `--overwrite` — overwrite existing files without prompting
44
+
45
+ ### `frakment list`
46
+
47
+ ```bash
48
+ npx frakment@latest list
49
+ ```
50
+
51
+ Lists all available components from the registry.
52
+
53
+ ## Registry
54
+
55
+ The default registry URL is written to `components.json` during `init`. If the public registry is not live yet, pass `--registry <url>` explicitly:
56
+
57
+ ```bash
58
+ npx frakment@latest add badge --registry http://localhost:8765
59
+ ```
60
+
61
+ ## Troubleshooting
62
+
63
+ ### "Frakment requires Tailwind v4"
64
+
65
+ Your `globals.css` must contain `@import "tailwindcss";`. Frakment relies on Tailwind v4 features (`@theme`, `@utility`, `@custom-variant`). Tailwind v3 is not supported.
66
+
67
+ ### "Could not find globals.css"
68
+
69
+ The CLI looks for one of:
70
+
71
+ - `app/globals.css`
72
+ - `src/app/globals.css`
73
+ - `styles/globals.css`
74
+ - `src/styles/globals.css`
75
+
76
+ If none exist, create one with at minimum:
77
+
78
+ ```css
79
+ @import "tailwindcss";
80
+ ```
81
+
82
+ ### `src/` directory projects
83
+
84
+ If your `globals.css` lives at `src/app/globals.css`, component files are written to `src/components/`, `src/lib/`, `src/hooks/`. Binary assets under `public/` are written to `public/` at the project root (Next.js serves `public/` from the root regardless of `src/` layout).
85
+
86
+ ## License
87
+
88
+ Source-available. See [LICENSE](./LICENSE). Commercial use requires at least one merged pull request to [arrafipd/frakment](https://github.com/arrafipd/frakment).
package/bin/index.js ADDED
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { Command } from "commander";
5
+ import pc from "picocolors";
6
+
7
+ import { add } from "../src/commands/add.js";
8
+ import { init } from "../src/commands/init.js";
9
+ import { list } from "../src/commands/list.js";
10
+
11
+ const __dirname = import.meta.dirname;
12
+ const pkg = JSON.parse(
13
+ readFileSync(path.resolve(__dirname, "../package.json"), "utf-8")
14
+ );
15
+
16
+ const program = new Command();
17
+
18
+ program
19
+ .name("frakment")
20
+ .description("Monochrome design system and component registry")
21
+ .version(pkg.version)
22
+ .addHelpText(
23
+ "after",
24
+ `
25
+ Examples:
26
+ ${pc.dim("$ frakment init")}
27
+ ${pc.dim("$ frakment add status-chip")}
28
+ ${pc.dim("$ frakment list")}
29
+ `
30
+ );
31
+
32
+ program
33
+ .command("init")
34
+ .description("Initialize frakment in your project")
35
+ .action(init);
36
+
37
+ program
38
+ .command("add <component>")
39
+ .description("Add a component from the frakment registry")
40
+ .option("--registry <url>", "Custom registry URL")
41
+ .option("--overwrite", "Overwrite existing files", false)
42
+ .action(add);
43
+
44
+ program
45
+ .command("list")
46
+ .description("List available components")
47
+ .option("--registry <url>", "Custom registry URL")
48
+ .action(list);
49
+
50
+ program.parse();
package/package.json CHANGED
@@ -1,6 +1,32 @@
1
1
  {
2
2
  "name": "frakment",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.0.1",
4
+ "description": "CLI and design system for the frakment component registry",
5
+ "keywords": [
6
+ "components",
7
+ "frakment",
8
+ "monochrome",
9
+ "registry",
10
+ "tailwindcss"
11
+ ],
12
+ "license": "SEE LICENSE IN LICENSE",
13
+ "author": "arrafipd (https://github.com/arrafipd)",
14
+ "type": "module",
15
+ "bin": {
16
+ "frakment": "./bin/index.js"
17
+ },
18
+ "files": [
19
+ "bin",
20
+ "src",
21
+ "templates"
22
+ ],
23
+ "dependencies": {
24
+ "@clack/prompts": "^1.8.1",
25
+ "commander": "^15.0.0",
26
+ "execa": "^10.2.1",
27
+ "picocolors": "^1.0.0"
28
+ },
29
+ "engines": {
30
+ "node": ">=22.12.0"
31
+ }
32
+ }
@@ -0,0 +1,142 @@
1
+ import { existsSync } from "node:fs";
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+
5
+ import * as p from "@clack/prompts";
6
+ import { execa } from "execa";
7
+ import pc from "picocolors";
8
+
9
+ import { detectPackageManager, findProjectRoot } from "../utils/detect.js";
10
+
11
+ async function fetchComponent(name, registryUrl, s) {
12
+ s.start(`fetching ${name}`);
13
+ try {
14
+ const res = await fetch(`${registryUrl}/${name}.json`);
15
+ if (!res.ok) {
16
+ throw new Error(`HTTP ${res.status}`);
17
+ }
18
+ const payload = await res.json();
19
+ s.stop(`${pc.green("✓")} fetched ${name}`);
20
+ return payload;
21
+ } catch {
22
+ s.stop(`${pc.red("✗")} ${name} not found in registry`);
23
+ process.exit(1);
24
+ }
25
+ }
26
+
27
+ async function writeFileSafely(file, cwd, options) {
28
+ const targetPath = path.join(cwd, file.path);
29
+ if (existsSync(targetPath) && !options.overwrite) {
30
+ const overwrite = await p.confirm({
31
+ initialValue: false,
32
+ message: `${file.path} already exists. Overwrite?`,
33
+ });
34
+ if (p.isCancel(overwrite) || !overwrite) {
35
+ p.log.info(`skipped ${file.path}`);
36
+ return;
37
+ }
38
+ }
39
+ await mkdir(path.dirname(targetPath), { recursive: true });
40
+ const output =
41
+ file.encoding === "base64"
42
+ ? Buffer.from(file.content, "base64")
43
+ : file.content.replaceAll("\r\n", "\n");
44
+ await writeFile(targetPath, output);
45
+ p.log.success(file.path);
46
+ }
47
+
48
+ export async function add(componentName, options) {
49
+ p.intro(`${pc.dim("frakment")} add ${pc.bold(componentName)}`);
50
+
51
+ let cwd;
52
+ try {
53
+ cwd = findProjectRoot(process.cwd());
54
+ } catch (error) {
55
+ p.outro(pc.red("error ") + error.message);
56
+ process.exit(1);
57
+ }
58
+
59
+ const configPath = path.join(cwd, "components.json");
60
+ if (!existsSync(configPath)) {
61
+ p.outro(
62
+ `${pc.red(
63
+ "error "
64
+ )}components.json not found. Run \`frakment init\` first.`
65
+ );
66
+ process.exit(1);
67
+ }
68
+
69
+ const configRaw = await readFile(configPath, "utf-8");
70
+ const config = JSON.parse(configRaw.replace(/^\uFEFF/u, ""));
71
+ const registryUrl = (options.registry ?? config.registry).replace(/\/$/u, "");
72
+ const s = p.spinner();
73
+
74
+ // 1. Recursively fetch the component and its registryDependencies
75
+ const queue = [componentName];
76
+ const fetched = new Map();
77
+ const npmDeps = new Set();
78
+
79
+ while (queue.length) {
80
+ const name = queue.shift();
81
+ if (fetched.has(name)) {
82
+ continue;
83
+ }
84
+ const payload = await fetchComponent(name, registryUrl, s);
85
+ fetched.set(name, payload);
86
+ for (const d of payload.dependencies ?? []) {
87
+ npmDeps.add(d);
88
+ }
89
+ for (const d of payload.registryDependencies ?? []) {
90
+ if (!fetched.has(d)) {
91
+ queue.push(d);
92
+ }
93
+ }
94
+ }
95
+
96
+ // 2. Write registry deps first (imports resolve), then main component
97
+ // Detect src/ layout: component files go under src/, but public/ assets
98
+ // stay at project root (Next.js serves public/ from root regardless of src/).
99
+ const isSrcLayout = config.tailwind?.css?.startsWith("src/") ?? false;
100
+ const resolveTargetPath = (p) => {
101
+ if (!isSrcLayout) {
102
+ return p;
103
+ }
104
+ if (p.startsWith("public/")) {
105
+ return p;
106
+ }
107
+ return `src/${p}`;
108
+ };
109
+
110
+ const order = [...fetched.keys()].filter((n) => n !== componentName);
111
+ order.push(componentName);
112
+ for (const name of order) {
113
+ for (const file of fetched.get(name).files ?? []) {
114
+ const targetFile = { ...file, path: resolveTargetPath(file.path) };
115
+ await writeFileSafely(targetFile, cwd, options);
116
+ }
117
+ }
118
+
119
+ // 3. Install npm dependencies
120
+ if (npmDeps.size > 0) {
121
+ const pkgPath = path.join(cwd, "package.json");
122
+ const pkgRaw = await readFile(pkgPath, "utf-8");
123
+ const pkg = JSON.parse(pkgRaw.replace(/^\uFEFF/u, ""));
124
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
125
+ const missing = [...npmDeps].filter((d) => !allDeps[d]);
126
+ if (missing.length > 0) {
127
+ s.start(`installing ${missing.join(", ")}`);
128
+ const pm = detectPackageManager(cwd);
129
+ try {
130
+ const args =
131
+ pm === "npm" ? ["install", ...missing] : ["add", ...missing];
132
+ await execa(pm, args, { cwd, stdio: "ignore" });
133
+ s.stop(`${pc.green("✓")} installed ${missing.length} package(s)`);
134
+ } catch (error) {
135
+ s.stop(`${pc.red("✗")} failed to install dependencies`);
136
+ p.log.warn(error.message);
137
+ }
138
+ }
139
+ }
140
+
141
+ p.outro(`${pc.green("✓")} ${componentName} added`);
142
+ }