@chemx/starter-kit 26.9.9-700 → 26.9.9-786
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/cli/audit.js +16 -4
- package/cli/index.js +285 -175
- package/docs/CHANGELOG.md +4 -1
- package/package.json +1 -1
package/cli/audit.js
CHANGED
|
@@ -183,6 +183,18 @@ export const auditFile = (filePath, relativePath) => {
|
|
|
183
183
|
return violations;
|
|
184
184
|
};
|
|
185
185
|
|
|
186
|
+
const IGNORED_DIRS = new Set(['node_modules', 'dist', 'build', 'vendor', '.git', '.next', '.turbo', '.output', 'out']);
|
|
187
|
+
|
|
188
|
+
const isSourceFile = (name) => {
|
|
189
|
+
return (
|
|
190
|
+
/\.(tsx|ts|jsx|js|vue)$/.test(name) &&
|
|
191
|
+
!name.endsWith('.d.ts') &&
|
|
192
|
+
!name.includes('.test.') &&
|
|
193
|
+
!name.includes('.spec.') &&
|
|
194
|
+
!name.includes('.min.')
|
|
195
|
+
);
|
|
196
|
+
};
|
|
197
|
+
|
|
186
198
|
export const scanDirectory = (targetDir, baseDir) => {
|
|
187
199
|
let results = [];
|
|
188
200
|
if (!fs.existsSync(targetDir)) return results;
|
|
@@ -193,10 +205,10 @@ export const scanDirectory = (targetDir, baseDir) => {
|
|
|
193
205
|
const relPath = path.relative(baseDir, fullPath);
|
|
194
206
|
|
|
195
207
|
if (entry.isDirectory()) {
|
|
196
|
-
if (!
|
|
208
|
+
if (!IGNORED_DIRS.has(entry.name)) {
|
|
197
209
|
results = results.concat(scanDirectory(fullPath, baseDir));
|
|
198
210
|
}
|
|
199
|
-
} else if (
|
|
211
|
+
} else if (isSourceFile(entry.name)) {
|
|
200
212
|
results = results.concat(auditFile(fullPath, relPath));
|
|
201
213
|
}
|
|
202
214
|
}
|
|
@@ -211,10 +223,10 @@ const countTotalScannedFiles = (targetDir) => {
|
|
|
211
223
|
for (const entry of entries) {
|
|
212
224
|
const fullPath = path.join(targetDir, entry.name);
|
|
213
225
|
if (entry.isDirectory()) {
|
|
214
|
-
if (!
|
|
226
|
+
if (!IGNORED_DIRS.has(entry.name)) {
|
|
215
227
|
count += countTotalScannedFiles(fullPath);
|
|
216
228
|
}
|
|
217
|
-
} else if (
|
|
229
|
+
} else if (isSourceFile(entry.name)) {
|
|
218
230
|
count += 1;
|
|
219
231
|
}
|
|
220
232
|
}
|
package/cli/index.js
CHANGED
|
@@ -1,59 +1,65 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import fs from
|
|
4
|
-
import path from
|
|
5
|
-
import os from
|
|
6
|
-
import readline from
|
|
7
|
-
import { spawnSync } from
|
|
8
|
-
import { runAudit as executeAstAudit, auditFile } from
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import readline from "node:readline";
|
|
7
|
+
import { spawnSync } from "node:child_process";
|
|
8
|
+
import { runAudit as executeAstAudit, auditFile } from "./audit.js";
|
|
9
9
|
|
|
10
10
|
const rawArgs = process.argv.slice(2);
|
|
11
|
-
const invokedBin = path.basename(process.argv[1] ||
|
|
12
|
-
const isCreateInvoked =
|
|
11
|
+
const invokedBin = path.basename(process.argv[1] || "");
|
|
12
|
+
const isCreateInvoked =
|
|
13
|
+
invokedBin.includes("create-chemx") || (rawArgs[0] && rawArgs[0] === "create");
|
|
13
14
|
|
|
14
|
-
const CONFIG_DIR = path.join(os.homedir(),
|
|
15
|
-
const CONFIG_FILE = path.join(CONFIG_DIR,
|
|
16
|
-
const DEVICE_FILE = path.join(CONFIG_DIR,
|
|
15
|
+
const CONFIG_DIR = path.join(os.homedir(), ".chemical-x");
|
|
16
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
17
|
+
const DEVICE_FILE = path.join(CONFIG_DIR, "device_id");
|
|
17
18
|
|
|
18
|
-
const API_BASE = process.env.CHEMICAL_X_API_URL ||
|
|
19
|
-
const
|
|
20
|
-
const
|
|
19
|
+
const API_BASE = process.env.CHEMICAL_X_API_URL || "https://chemicalx.xophz.com";
|
|
20
|
+
const URL_LEARN = "https://chemicalx.xophz.com";
|
|
21
|
+
const URL_STANDARD = "https://mycompassconsulting.com/buy/chemical-x/standard";
|
|
22
|
+
const URL_MASTER = "https://mycompassconsulting.com/buy/chemical-x/master";
|
|
21
23
|
|
|
22
24
|
if (!fs.existsSync(CONFIG_DIR)) {
|
|
23
|
-
try {
|
|
25
|
+
try {
|
|
26
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
27
|
+
} catch {}
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
const openBrowser = (url) => {
|
|
27
31
|
const platform = process.platform;
|
|
28
32
|
try {
|
|
29
|
-
if (platform ===
|
|
30
|
-
else if (platform ===
|
|
31
|
-
|
|
33
|
+
if (platform === "darwin") spawnSync("open", [url], { stdio: "ignore" });
|
|
34
|
+
else if (platform === "win32")
|
|
35
|
+
spawnSync("cmd.exe", ["/c", "start", '""', url], { stdio: "ignore" });
|
|
36
|
+
else spawnSync("xdg-open", [url], { stdio: "ignore" });
|
|
32
37
|
} catch {}
|
|
33
38
|
};
|
|
34
39
|
|
|
35
40
|
const hasGum = () => {
|
|
36
41
|
try {
|
|
37
|
-
return spawnSync(
|
|
42
|
+
return spawnSync("which", ["gum"], { stdio: "ignore" }).status === 0;
|
|
38
43
|
} catch {
|
|
39
44
|
return false;
|
|
40
45
|
}
|
|
41
46
|
};
|
|
42
47
|
|
|
43
|
-
const gumChoose = (options, header =
|
|
44
|
-
const args = [
|
|
48
|
+
const gumChoose = (options, header = "") => {
|
|
49
|
+
const args = ["choose"];
|
|
45
50
|
if (header) {
|
|
46
|
-
args.
|
|
51
|
+
args.push(`--header=${header}`, "--header.foreground=81");
|
|
47
52
|
}
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
args.push("--cursor.foreground=81", ...options);
|
|
54
|
+
const res = spawnSync("gum", args, { encoding: "utf-8", stdio: ["inherit", "pipe", "inherit"] });
|
|
55
|
+
return (res.stdout || "").trim();
|
|
50
56
|
};
|
|
51
57
|
|
|
52
|
-
const gumInput = (promptText, placeholder =
|
|
53
|
-
const args = [
|
|
54
|
-
if (isPassword) args.push(
|
|
55
|
-
const res = spawnSync(
|
|
56
|
-
return (res.stdout ||
|
|
58
|
+
const gumInput = (promptText, placeholder = "", isPassword = false) => {
|
|
59
|
+
const args = ["input", `--prompt=${promptText} `, `--placeholder=${placeholder}`];
|
|
60
|
+
if (isPassword) args.push("--password");
|
|
61
|
+
const res = spawnSync("gum", args, { encoding: "utf-8", stdio: ["inherit", "pipe", "inherit"] });
|
|
62
|
+
return (res.stdout || "").trim();
|
|
57
63
|
};
|
|
58
64
|
|
|
59
65
|
const promptQuestion = (query) => {
|
|
@@ -69,19 +75,21 @@ const promptQuestion = (query) => {
|
|
|
69
75
|
const getOrCreateDeviceId = () => {
|
|
70
76
|
if (fs.existsSync(DEVICE_FILE)) {
|
|
71
77
|
try {
|
|
72
|
-
const id = fs.readFileSync(DEVICE_FILE,
|
|
78
|
+
const id = fs.readFileSync(DEVICE_FILE, "utf-8").trim();
|
|
73
79
|
if (id) return id;
|
|
74
80
|
} catch {}
|
|
75
81
|
}
|
|
76
82
|
const newId = `cli_${Math.random().toString(36).substring(2, 12)}_${Date.now()}`;
|
|
77
|
-
try {
|
|
83
|
+
try {
|
|
84
|
+
fs.writeFileSync(DEVICE_FILE, newId, "utf-8");
|
|
85
|
+
} catch {}
|
|
78
86
|
return newId;
|
|
79
87
|
};
|
|
80
88
|
|
|
81
89
|
const getCachedLicenseKey = () => {
|
|
82
90
|
if (fs.existsSync(CONFIG_FILE)) {
|
|
83
91
|
try {
|
|
84
|
-
const data = JSON.parse(fs.readFileSync(CONFIG_FILE,
|
|
92
|
+
const data = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8"));
|
|
85
93
|
return data.licenseKey || null;
|
|
86
94
|
} catch {
|
|
87
95
|
return null;
|
|
@@ -92,33 +100,45 @@ const getCachedLicenseKey = () => {
|
|
|
92
100
|
|
|
93
101
|
const saveLicenseKey = (licenseKey) => {
|
|
94
102
|
try {
|
|
95
|
-
fs.writeFileSync(
|
|
103
|
+
fs.writeFileSync(
|
|
104
|
+
CONFIG_FILE,
|
|
105
|
+
JSON.stringify({ licenseKey, updatedAt: new Date().toISOString() }, null, 2),
|
|
106
|
+
"utf-8"
|
|
107
|
+
);
|
|
96
108
|
} catch {}
|
|
97
109
|
};
|
|
98
110
|
|
|
99
|
-
const renderBanner = (title =
|
|
111
|
+
const renderBanner = (title = "Chemical X Protocol: Quantum Architecture") => {
|
|
100
112
|
if (hasGum()) {
|
|
101
|
-
spawnSync(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
113
|
+
spawnSync(
|
|
114
|
+
"gum",
|
|
115
|
+
[
|
|
116
|
+
"style",
|
|
117
|
+
"--border=normal",
|
|
118
|
+
"--margin=1",
|
|
119
|
+
"--padding=1 2",
|
|
120
|
+
"--border-foreground=45",
|
|
121
|
+
"--foreground=81",
|
|
122
|
+
"--bold",
|
|
123
|
+
` ${title}\n Zero-Context-Rot Scaffolding & Engineering Directives`
|
|
124
|
+
],
|
|
125
|
+
{ stdio: "inherit" }
|
|
126
|
+
);
|
|
111
127
|
} else {
|
|
112
|
-
process.stdout.write(
|
|
128
|
+
process.stdout.write(
|
|
129
|
+
"\n\x1b[38;2;98;201;255m=====================================================\x1b[0m\n"
|
|
130
|
+
);
|
|
113
131
|
process.stdout.write(`\x1b[1m\x1b[38;2;98;201;255m ${title}\x1b[0m\n`);
|
|
114
|
-
process.stdout.write(
|
|
115
|
-
process.stdout.write(
|
|
132
|
+
process.stdout.write(" Zero-Context-Rot Scaffolding & Engineering Directives\n");
|
|
133
|
+
process.stdout.write(
|
|
134
|
+
"\x1b[38;2;98;201;255m=====================================================\x1b[0m\n\n"
|
|
135
|
+
);
|
|
116
136
|
}
|
|
117
137
|
};
|
|
118
138
|
|
|
119
139
|
const obtainLicenseKey = async () => {
|
|
120
140
|
let cached = getCachedLicenseKey();
|
|
121
|
-
const cliFlagIdx = rawArgs.indexOf(
|
|
141
|
+
const cliFlagIdx = rawArgs.indexOf("--license");
|
|
122
142
|
if (cliFlagIdx !== -1 && rawArgs[cliFlagIdx + 1]) {
|
|
123
143
|
cached = rawArgs[cliFlagIdx + 1].trim();
|
|
124
144
|
}
|
|
@@ -130,77 +150,96 @@ const obtainLicenseKey = async () => {
|
|
|
130
150
|
const useGum = hasGum();
|
|
131
151
|
|
|
132
152
|
if (useGum) {
|
|
133
|
-
const choice = gumChoose(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
153
|
+
const choice = gumChoose(
|
|
154
|
+
[
|
|
155
|
+
"1. Visit chemicalx.xophz.com to learn more",
|
|
156
|
+
"2. Buy eBook w/ AGENTS.md Rule Book ($27) -> Launch Checkout",
|
|
157
|
+
"3. Buy Master Bundle ($47) -> Launch Checkout",
|
|
158
|
+
"4. Enter License Key (CX-XXXX-XXXX-XXXX)",
|
|
159
|
+
"5. Run Free Public Audit (npx chemx audit)",
|
|
160
|
+
"6. Exit"
|
|
161
|
+
],
|
|
162
|
+
"Chemical X Scaffolding Requires a Paid License:"
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
if (choice.startsWith("1.")) {
|
|
166
|
+
process.stdout.write(`\x1b[36mOpening Chemical X Portal in default browser:\x1b[0m ${URL_LEARN}\n`);
|
|
167
|
+
openBrowser(URL_LEARN);
|
|
168
|
+
process.stdout.write("\nOnce completed, paste your Sponsor / VIP License Key below.\n");
|
|
169
|
+
return gumInput("License Key (CX-XXXX-XXXX-XXXX):", "CX-XXXX-XXXX-XXXX");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (choice.startsWith("2.")) {
|
|
173
|
+
process.stdout.write(`\x1b[36mOpening Standard Vault checkout in default browser:\x1b[0m ${URL_STANDARD}\n`);
|
|
143
174
|
openBrowser(URL_STANDARD);
|
|
144
|
-
process.stdout.write(
|
|
145
|
-
return gumInput(
|
|
175
|
+
process.stdout.write("\nOnce completed, paste your Sponsor / VIP License Key below.\n");
|
|
176
|
+
return gumInput("License Key (CX-XXXX-XXXX-XXXX):", "CX-XXXX-XXXX-XXXX");
|
|
146
177
|
}
|
|
147
178
|
|
|
148
|
-
if (choice.startsWith(
|
|
149
|
-
process.stdout.write(`\x1b[36mOpening checkout in default browser:\x1b[0m ${URL_MASTER}\n`);
|
|
179
|
+
if (choice.startsWith("3.")) {
|
|
180
|
+
process.stdout.write(`\x1b[36mOpening Master Bundle checkout in default browser:\x1b[0m ${URL_MASTER}\n`);
|
|
150
181
|
openBrowser(URL_MASTER);
|
|
151
|
-
process.stdout.write(
|
|
152
|
-
return gumInput(
|
|
182
|
+
process.stdout.write("\nOnce completed, paste your Sponsor / VIP License Key below.\n");
|
|
183
|
+
return gumInput("License Key (CX-XXXX-XXXX-XXXX):", "CX-XXXX-XXXX-XXXX");
|
|
153
184
|
}
|
|
154
185
|
|
|
155
|
-
if (choice.startsWith(
|
|
156
|
-
return gumInput(
|
|
186
|
+
if (choice.startsWith("4.")) {
|
|
187
|
+
return gumInput("License Key (CX-XXXX-XXXX-XXXX):", "CX-XXXX-XXXX-XXXX");
|
|
157
188
|
}
|
|
158
189
|
|
|
159
|
-
if (choice.startsWith(
|
|
190
|
+
if (choice.startsWith("5.")) {
|
|
160
191
|
await runAudit(null, true);
|
|
161
192
|
}
|
|
162
193
|
|
|
163
|
-
if (choice.startsWith(
|
|
194
|
+
if (choice.startsWith("6.") || !choice) {
|
|
164
195
|
process.exit(0);
|
|
165
196
|
}
|
|
166
197
|
|
|
167
|
-
return gumInput(
|
|
198
|
+
return gumInput("License Key (CX-XXXX-XXXX-XXXX):", "CX-XXXX-XXXX-XXXX");
|
|
168
199
|
}
|
|
169
200
|
|
|
170
|
-
process.stdout.write(
|
|
171
|
-
process.stdout.write(
|
|
172
|
-
process.stdout.write(
|
|
173
|
-
process.stdout.write(
|
|
174
|
-
process.stdout.write(
|
|
175
|
-
process.stdout.write(
|
|
176
|
-
|
|
177
|
-
|
|
201
|
+
process.stdout.write("\x1b[1mChemical X Scaffolding Requires a Paid License:\x1b[0m\n");
|
|
202
|
+
process.stdout.write(" [1] Visit chemicalx.xophz.com to learn more\n");
|
|
203
|
+
process.stdout.write(" [2] Buy eBook w/ AGENTS.md Rule Book ($27) - Opens browser\n");
|
|
204
|
+
process.stdout.write(" [3] Buy Master Bundle ($47) - Opens browser\n");
|
|
205
|
+
process.stdout.write(" [4] Enter License Key\n");
|
|
206
|
+
process.stdout.write(" [5] Run Free Public Audit (npx chemx audit)\n");
|
|
207
|
+
process.stdout.write(" [6] Exit\n\n");
|
|
208
|
+
|
|
209
|
+
const selection = await promptQuestion("Select option [1-6] (default: 1): ");
|
|
210
|
+
const effectiveChoice = selection.trim() || "1";
|
|
211
|
+
|
|
212
|
+
if (effectiveChoice === "1") {
|
|
213
|
+
process.stdout.write(`Opening: ${URL_LEARN}\n`);
|
|
214
|
+
openBrowser(URL_LEARN);
|
|
215
|
+
return promptQuestion("Enter License Key after review (or press Enter to exit): ");
|
|
216
|
+
}
|
|
178
217
|
|
|
179
|
-
if (
|
|
218
|
+
if (effectiveChoice === "2") {
|
|
180
219
|
process.stdout.write(`Opening: ${URL_STANDARD}\n`);
|
|
181
220
|
openBrowser(URL_STANDARD);
|
|
182
|
-
return promptQuestion(
|
|
221
|
+
return promptQuestion("Enter License Key after purchase: ");
|
|
183
222
|
}
|
|
184
223
|
|
|
185
|
-
if (
|
|
224
|
+
if (effectiveChoice === "3") {
|
|
186
225
|
process.stdout.write(`Opening: ${URL_MASTER}\n`);
|
|
187
226
|
openBrowser(URL_MASTER);
|
|
188
|
-
return promptQuestion(
|
|
227
|
+
return promptQuestion("Enter License Key after purchase: ");
|
|
189
228
|
}
|
|
190
229
|
|
|
191
|
-
if (
|
|
192
|
-
return promptQuestion(
|
|
230
|
+
if (effectiveChoice === "4") {
|
|
231
|
+
return promptQuestion("Enter License Key (CX-XXXX-XXXX-XXXX): ");
|
|
193
232
|
}
|
|
194
233
|
|
|
195
|
-
if (
|
|
234
|
+
if (effectiveChoice === "5") {
|
|
196
235
|
await runAudit(null, true);
|
|
197
236
|
}
|
|
198
237
|
|
|
199
|
-
if (
|
|
238
|
+
if (effectiveChoice === "6") {
|
|
200
239
|
process.exit(0);
|
|
201
240
|
}
|
|
202
241
|
|
|
203
|
-
return promptQuestion(
|
|
242
|
+
return promptQuestion("Enter Chemical X Sponsor License Key (CX-XXXX-XXXX-XXXX): ");
|
|
204
243
|
};
|
|
205
244
|
|
|
206
245
|
const fetchStarterKitFiles = async (licenseKey) => {
|
|
@@ -211,51 +250,61 @@ const fetchStarterKitFiles = async (licenseKey) => {
|
|
|
211
250
|
|
|
212
251
|
try {
|
|
213
252
|
const res = await fetch(`${API_BASE}/api/starter-kit/download`, {
|
|
214
|
-
method:
|
|
215
|
-
headers: {
|
|
253
|
+
method: "POST",
|
|
254
|
+
headers: { "Content-Type": "application/json" },
|
|
216
255
|
body: JSON.stringify({ licenseKey: normalizedKey, deviceId })
|
|
217
256
|
});
|
|
218
257
|
|
|
219
258
|
const responseData = await res.json();
|
|
220
259
|
|
|
221
260
|
if (!res.ok || !responseData.valid) {
|
|
222
|
-
process.stderr.write(
|
|
261
|
+
process.stderr.write(
|
|
262
|
+
`\x1b[31m✕ License Verification Failed: ${responseData.error || "Invalid key."}\x1b[0m\n`
|
|
263
|
+
);
|
|
223
264
|
process.stderr.write(`Purchase key at: ${URL_STANDARD}\n\n`);
|
|
224
265
|
process.exit(1);
|
|
225
266
|
}
|
|
226
267
|
|
|
227
268
|
saveLicenseKey(normalizedKey);
|
|
228
|
-
process.stdout.write(
|
|
269
|
+
process.stdout.write(
|
|
270
|
+
`\x1b[32m✔ Verified License for @${responseData.githubUser || "sponsor"}\x1b[0m\n\n`
|
|
271
|
+
);
|
|
229
272
|
return responseData.files || {};
|
|
230
273
|
} catch (err) {
|
|
231
|
-
process.stderr.write(
|
|
274
|
+
process.stderr.write(
|
|
275
|
+
`\x1b[31m✕ Network Error: Failed to reach edge server (${err.message}).\x1b[0m\n`
|
|
276
|
+
);
|
|
232
277
|
process.exit(1);
|
|
233
278
|
}
|
|
234
279
|
};
|
|
235
280
|
|
|
236
281
|
const runScaffold = async (projectName) => {
|
|
237
|
-
renderBanner(
|
|
282
|
+
renderBanner("Chemical X: Quantum Scaffolder (npm create chemx)");
|
|
238
283
|
|
|
239
284
|
const licenseKey = await obtainLicenseKey();
|
|
240
285
|
if (!licenseKey) {
|
|
241
|
-
process.stderr.write(
|
|
286
|
+
process.stderr.write(
|
|
287
|
+
"\x1b[31m✕ Valid license key is required to scaffold blueprints.\x1b[0m\n"
|
|
288
|
+
);
|
|
242
289
|
process.exit(1);
|
|
243
290
|
}
|
|
244
291
|
|
|
245
292
|
let targetName = projectName;
|
|
246
293
|
if (!targetName) {
|
|
247
294
|
if (hasGum()) {
|
|
248
|
-
targetName = gumInput(
|
|
295
|
+
targetName = gumInput("Project directory name:", "my-quantum-app");
|
|
249
296
|
} else {
|
|
250
|
-
targetName = await promptQuestion(
|
|
297
|
+
targetName = await promptQuestion("Project directory name [my-quantum-app]: ");
|
|
251
298
|
}
|
|
252
299
|
}
|
|
253
300
|
|
|
254
|
-
const finalDirName = targetName.trim() ||
|
|
301
|
+
const finalDirName = targetName.trim() || "my-quantum-app";
|
|
255
302
|
const targetDir = path.resolve(process.cwd(), finalDirName);
|
|
256
303
|
|
|
257
304
|
if (fs.existsSync(targetDir) && fs.readdirSync(targetDir).length > 0) {
|
|
258
|
-
process.stderr.write(
|
|
305
|
+
process.stderr.write(
|
|
306
|
+
`\x1b[31m✕ Error: Directory '${finalDirName}' already exists and is not empty.\x1b[0m\n`
|
|
307
|
+
);
|
|
259
308
|
process.exit(1);
|
|
260
309
|
}
|
|
261
310
|
|
|
@@ -270,32 +319,34 @@ const runScaffold = async (projectName) => {
|
|
|
270
319
|
if (!fs.existsSync(dirName)) {
|
|
271
320
|
fs.mkdirSync(dirName, { recursive: true });
|
|
272
321
|
}
|
|
273
|
-
fs.writeFileSync(fullPath, content,
|
|
322
|
+
fs.writeFileSync(fullPath, content, "utf-8");
|
|
274
323
|
process.stdout.write(` \x1b[32m✔\x1b[0m ${relPath}\n`);
|
|
275
324
|
}
|
|
276
325
|
|
|
277
|
-
const cursorRulesPath = path.join(targetDir,
|
|
326
|
+
const cursorRulesPath = path.join(targetDir, ".cursorrules");
|
|
278
327
|
if (!fs.existsSync(cursorRulesPath)) {
|
|
279
328
|
const rules = `# Chemical X Quantum Architecture Directives\nStrictly follow AGENTS.md rules. Never exceed 500 lines per file. All molecule capsules must stay under 100 lines.\n`;
|
|
280
|
-
fs.writeFileSync(cursorRulesPath, rules,
|
|
329
|
+
fs.writeFileSync(cursorRulesPath, rules, "utf-8");
|
|
281
330
|
process.stdout.write(` \x1b[32m✔\x1b[0m .cursorrules\n`);
|
|
282
331
|
}
|
|
283
332
|
|
|
284
|
-
process.stdout.write(
|
|
285
|
-
|
|
333
|
+
process.stdout.write(
|
|
334
|
+
`\n\x1b[1m\x1b[32m✔ Quantum project created successfully at ${finalDirName}!\x1b[0m\n\n`
|
|
335
|
+
);
|
|
336
|
+
process.stdout.write("Next Steps:\n");
|
|
286
337
|
process.stdout.write(` 1. cd ${finalDirName}\n`);
|
|
287
|
-
process.stdout.write(
|
|
288
|
-
process.stdout.write(
|
|
289
|
-
process.stdout.write(
|
|
338
|
+
process.stdout.write(" 2. Review AGENTS.md for line budgets and architecture standards\n");
|
|
339
|
+
process.stdout.write(" 3. Run npx chemx generate m-<feature> to create capsules\n");
|
|
340
|
+
process.stdout.write(" 4. Run npx chemx audit to scan for line budget compliance\n\n");
|
|
290
341
|
};
|
|
291
342
|
|
|
292
|
-
const runInit = async (targetSubDir =
|
|
293
|
-
renderBanner(
|
|
343
|
+
const runInit = async (targetSubDir = "src/chemical-x") => {
|
|
344
|
+
renderBanner("Chemical X: In-Repo Capsule Drop-in");
|
|
294
345
|
|
|
295
346
|
const targetDir = path.resolve(process.cwd(), targetSubDir);
|
|
296
347
|
const licenseKey = await obtainLicenseKey();
|
|
297
348
|
if (!licenseKey) {
|
|
298
|
-
process.stderr.write(
|
|
349
|
+
process.stderr.write("\x1b[31m✕ Valid license key is required.\x1b[0m\n");
|
|
299
350
|
process.exit(1);
|
|
300
351
|
}
|
|
301
352
|
|
|
@@ -310,16 +361,18 @@ const runInit = async (targetSubDir = 'src/chemical-x') => {
|
|
|
310
361
|
if (!fs.existsSync(dirName)) {
|
|
311
362
|
fs.mkdirSync(dirName, { recursive: true });
|
|
312
363
|
}
|
|
313
|
-
fs.writeFileSync(fullPath, content,
|
|
364
|
+
fs.writeFileSync(fullPath, content, "utf-8");
|
|
314
365
|
process.stdout.write(` \x1b[32m✔\x1b[0m ${relPath}\n`);
|
|
315
366
|
count++;
|
|
316
367
|
}
|
|
317
368
|
|
|
318
|
-
process.stdout.write(
|
|
369
|
+
process.stdout.write(
|
|
370
|
+
`\n\x1b[1m\x1b[32m✔ Successfully installed ${count} Chemical X assets into ${targetSubDir}!\x1b[0m\n\n`
|
|
371
|
+
);
|
|
319
372
|
};
|
|
320
373
|
|
|
321
374
|
const runGenerateCapsule = (capsuleName) => {
|
|
322
|
-
const normalizedName = capsuleName.startsWith(
|
|
375
|
+
const normalizedName = capsuleName.startsWith("m-") ? capsuleName : `m-${capsuleName}`;
|
|
323
376
|
const targetDir = path.resolve(process.cwd(), normalizedName);
|
|
324
377
|
|
|
325
378
|
if (fs.existsSync(targetDir)) {
|
|
@@ -330,9 +383,9 @@ const runGenerateCapsule = (capsuleName) => {
|
|
|
330
383
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
331
384
|
|
|
332
385
|
const pascalName = normalizedName
|
|
333
|
-
.split(
|
|
386
|
+
.split("-")
|
|
334
387
|
.map((p) => p.charAt(0).toUpperCase() + p.slice(1))
|
|
335
|
-
.join(
|
|
388
|
+
.join("");
|
|
336
389
|
|
|
337
390
|
const componentCode = `import React from 'react';
|
|
338
391
|
import type { ${pascalName}Props } from './types';
|
|
@@ -357,37 +410,46 @@ export default ${pascalName};
|
|
|
357
410
|
export type { ${pascalName}Props } from './types';
|
|
358
411
|
`;
|
|
359
412
|
|
|
360
|
-
fs.writeFileSync(path.join(targetDir, `${normalizedName}.tsx`), componentCode,
|
|
361
|
-
fs.writeFileSync(path.join(targetDir,
|
|
362
|
-
fs.writeFileSync(path.join(targetDir,
|
|
413
|
+
fs.writeFileSync(path.join(targetDir, `${normalizedName}.tsx`), componentCode, "utf-8");
|
|
414
|
+
fs.writeFileSync(path.join(targetDir, "types.d.ts"), typesCode, "utf-8");
|
|
415
|
+
fs.writeFileSync(path.join(targetDir, "index.ts"), indexCode, "utf-8");
|
|
363
416
|
|
|
364
|
-
process.stdout.write(
|
|
417
|
+
process.stdout.write(
|
|
418
|
+
`\x1b[32m✔ Successfully generated crystalline capsule:\x1b[0m ${normalizedName}/\n`
|
|
419
|
+
);
|
|
365
420
|
process.stdout.write(` - ${normalizedName}/${normalizedName}.tsx (< 50 lines)\n`);
|
|
366
421
|
process.stdout.write(` - ${normalizedName}/types.d.ts\n`);
|
|
367
422
|
process.stdout.write(` - ${normalizedName}/index.ts\n\n`);
|
|
368
423
|
};
|
|
369
424
|
|
|
370
425
|
export const runAudit = async (customDir = null, isCli = false) => {
|
|
371
|
-
const isJson = rawArgs.includes(
|
|
372
|
-
const dirFlag = rawArgs.find((arg) => arg.startsWith(
|
|
373
|
-
const targetDir =
|
|
426
|
+
const isJson = rawArgs.includes("--json");
|
|
427
|
+
const dirFlag = rawArgs.find((arg) => arg.startsWith("--dir="));
|
|
428
|
+
const targetDir =
|
|
429
|
+
customDir || (dirFlag ? dirFlag.split("=")[1] : fs.existsSync("src") ? "src" : ".");
|
|
374
430
|
|
|
375
431
|
const report = executeAstAudit(targetDir);
|
|
376
432
|
|
|
377
433
|
if (isJson) {
|
|
378
|
-
process.stdout.write(JSON.stringify(report, null, 2) +
|
|
434
|
+
process.stdout.write(JSON.stringify(report, null, 2) + "\n");
|
|
379
435
|
if (isCli) process.exit(report.violations.length > 0 ? 1 : 0);
|
|
380
436
|
return report;
|
|
381
437
|
}
|
|
382
438
|
|
|
383
|
-
process.stdout.write(
|
|
439
|
+
process.stdout.write(
|
|
440
|
+
"\n\x1b[38;2;98;201;255m[Chemical X Context Hazard Audit]\x1b[0m Scanning codebase for AST architectural hazards...\n"
|
|
441
|
+
);
|
|
384
442
|
process.stdout.write(`Target Directory: ${targetDir}\n`);
|
|
385
443
|
process.stdout.write(`Scanned ${report.scannedFiles} source files.\n\n`);
|
|
386
444
|
|
|
387
445
|
if (report.violations.length === 0) {
|
|
388
|
-
process.stdout.write(
|
|
446
|
+
process.stdout.write(
|
|
447
|
+
"\x1b[1m\x1b[32m✔ 100% Quantum Compliant: Zero context hazard violations detected across all line budgets, hooks, and AST rules.\x1b[0m\n\n"
|
|
448
|
+
);
|
|
389
449
|
} else {
|
|
390
|
-
process.stdout.write(
|
|
450
|
+
process.stdout.write(
|
|
451
|
+
`\x1b[31m✕ FAILED: ${report.totalViolations} context hazard violations detected:\x1b[0m\n\n`
|
|
452
|
+
);
|
|
391
453
|
for (const v of report.violations) {
|
|
392
454
|
process.stdout.write(` \x1b[31m[${v.rule}]\x1b[0m \x1b[33m${v.filePath}:${v.line}\x1b[0m\n`);
|
|
393
455
|
process.stdout.write(` Hazard: ${v.hazard}\n`);
|
|
@@ -398,56 +460,95 @@ export const runAudit = async (customDir = null, isCli = false) => {
|
|
|
398
460
|
if (isCli) {
|
|
399
461
|
while (true) {
|
|
400
462
|
if (hasGum()) {
|
|
401
|
-
spawnSync(
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
463
|
+
spawnSync(
|
|
464
|
+
"gum",
|
|
465
|
+
[
|
|
466
|
+
"style",
|
|
467
|
+
"--border=rounded",
|
|
468
|
+
"--border-foreground=81",
|
|
469
|
+
"--padding=0 1",
|
|
470
|
+
"--bold",
|
|
471
|
+
"Chemical X: The Secret Sauce to Vibe Coding\nStop letting AI agents scour 2,000-line monoliths and hallucinate.\n25+ Years XP | Codified by Principal Systems Architect Xopher Pollard\nTarget File Budget: Max 500 lines/file (<100 lines/molecule) | 85% Token Burn Cut"
|
|
472
|
+
],
|
|
473
|
+
{ stdio: "inherit" }
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
const choice = gumChoose(
|
|
477
|
+
[
|
|
478
|
+
"1. Visit chemicalx.xophz.com to learn more",
|
|
479
|
+
"2. Buy eBook w/ AGENTS.md Rule Book ($27) -> Launch Checkout",
|
|
480
|
+
"3. Buy Master Bundle ($47) -> Launch Checkout",
|
|
481
|
+
"4. Enter License Key to Scaffold (Paid License Holders)",
|
|
482
|
+
"5. Exit"
|
|
483
|
+
],
|
|
484
|
+
"Unlock the Awesome Secret Sauce of Chemical X:"
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
if (choice.startsWith("1.")) {
|
|
488
|
+
process.stdout.write(
|
|
489
|
+
`\n\x1b[36mOpening Chemical X Portal in browser:\x1b[0m ${URL_LEARN}\n\n`
|
|
490
|
+
);
|
|
491
|
+
openBrowser(URL_LEARN);
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
if (choice.startsWith("2.")) {
|
|
495
|
+
process.stdout.write(
|
|
496
|
+
`\n\x1b[36mOpening Standard Vault checkout (eBook + AGENTS.md):\x1b[0m ${URL_STANDARD}\n\n`
|
|
497
|
+
);
|
|
419
498
|
openBrowser(URL_STANDARD);
|
|
420
499
|
continue;
|
|
421
500
|
}
|
|
422
|
-
if (choice.startsWith(
|
|
423
|
-
process.stdout.write(
|
|
501
|
+
if (choice.startsWith("3.")) {
|
|
502
|
+
process.stdout.write(
|
|
503
|
+
`\n\x1b[36mOpening Master Bundle checkout (Repo + Hooks + Prompts):\x1b[0m ${URL_MASTER}\n\n`
|
|
504
|
+
);
|
|
424
505
|
openBrowser(URL_MASTER);
|
|
425
506
|
continue;
|
|
426
507
|
}
|
|
427
|
-
if (choice.startsWith(
|
|
508
|
+
if (choice.startsWith("4.")) {
|
|
428
509
|
await runScaffold();
|
|
429
510
|
break;
|
|
430
511
|
}
|
|
431
512
|
break;
|
|
432
513
|
} else {
|
|
433
|
-
process.stdout.write(
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
process.stdout.write(
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
514
|
+
process.stdout.write(
|
|
515
|
+
"\n\x1b[1m\x1b[38;2;98;201;255mChemical X: The Secret Sauce to Vibe Coding\x1b[0m\n"
|
|
516
|
+
);
|
|
517
|
+
process.stdout.write(
|
|
518
|
+
"Stop letting AI agents scour 2,000-line monoliths and hallucinate breaking changes.\n" +
|
|
519
|
+
"25+ Years XP | Codified by Principal Systems Architect Xopher Pollard\n" +
|
|
520
|
+
"Target File Budget: Max 500 lines/file (<100 lines per molecule capsule).\n\n"
|
|
521
|
+
);
|
|
522
|
+
process.stdout.write(` [1] Visit chemicalx.xophz.com to learn more\n`);
|
|
523
|
+
process.stdout.write(
|
|
524
|
+
` [2] Buy eBook w/ AGENTS.md Rule Book ($27) - ${URL_STANDARD}\n` +
|
|
525
|
+
` Includes: Kindle/Print PDF eBook, 7 Quantum Chapters, Universal AGENTS.md & .cursorrules\n`
|
|
526
|
+
);
|
|
527
|
+
process.stdout.write(
|
|
528
|
+
` [3] Buy Master Bundle ($47) - ${URL_MASTER}\n` +
|
|
529
|
+
` Includes: Private Starter-Kit Repo, Pre-Commit Line Budget Hooks, 10x Prompts, VIP Discord\n`
|
|
530
|
+
);
|
|
531
|
+
process.stdout.write(" [4] Enter License Key to Scaffold (Paid License Holders)\n");
|
|
532
|
+
process.stdout.write(" [5] Exit\n\n");
|
|
533
|
+
|
|
534
|
+
const selection = await promptQuestion("Select option [1-5] (default: 1): ");
|
|
535
|
+
const effectiveSelection = selection.trim() || "1";
|
|
536
|
+
if (effectiveSelection === "1") {
|
|
537
|
+
process.stdout.write(`\nOpening: ${URL_LEARN}\n\n`);
|
|
538
|
+
openBrowser(URL_LEARN);
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
if (effectiveSelection === "2") {
|
|
441
542
|
process.stdout.write(`\nOpening: ${URL_STANDARD}\n\n`);
|
|
442
543
|
openBrowser(URL_STANDARD);
|
|
443
544
|
continue;
|
|
444
545
|
}
|
|
445
|
-
if (
|
|
546
|
+
if (effectiveSelection === "3") {
|
|
446
547
|
process.stdout.write(`\nOpening: ${URL_MASTER}\n\n`);
|
|
447
548
|
openBrowser(URL_MASTER);
|
|
448
549
|
continue;
|
|
449
550
|
}
|
|
450
|
-
if (
|
|
551
|
+
if (effectiveSelection === "4") {
|
|
451
552
|
await runScaffold();
|
|
452
553
|
break;
|
|
453
554
|
}
|
|
@@ -463,50 +564,60 @@ export { auditFile };
|
|
|
463
564
|
|
|
464
565
|
const printHelp = () => {
|
|
465
566
|
renderBanner();
|
|
466
|
-
process.stdout.write(
|
|
467
|
-
process.stdout.write(
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
process.stdout.write(
|
|
567
|
+
process.stdout.write("\x1b[1mAvailable Commands:\x1b[0m\n");
|
|
568
|
+
process.stdout.write(
|
|
569
|
+
" \x1b[36mnpm create chemx [dir]\x1b[0m [PAID] Scaffold complete Quantum Architecture project\n"
|
|
570
|
+
);
|
|
571
|
+
process.stdout.write(
|
|
572
|
+
" \x1b[36mnpx @chemx/starter-kit init [dir]\x1b[0m [PAID] Drop blueprints & hooks into existing project\n"
|
|
573
|
+
);
|
|
574
|
+
process.stdout.write(
|
|
575
|
+
" \x1b[36mnpx chemx generate <m-name>\x1b[0m Generate isolated molecule capsule (< 100 lines)\n"
|
|
576
|
+
);
|
|
577
|
+
process.stdout.write(
|
|
578
|
+
" \x1b[36mnpx chemx audit [--json] [--dir=src]\x1b[0m[FREE] Scan codebase for AST architectural hazards\n\n"
|
|
579
|
+
);
|
|
471
580
|
};
|
|
472
581
|
|
|
473
582
|
const main = async () => {
|
|
474
583
|
const firstArg = rawArgs[0];
|
|
475
584
|
|
|
476
585
|
if (isCreateInvoked) {
|
|
477
|
-
const dirArg = firstArg ===
|
|
586
|
+
const dirArg = firstArg === "create" ? rawArgs[1] : firstArg;
|
|
478
587
|
await runScaffold(dirArg);
|
|
479
588
|
return;
|
|
480
589
|
}
|
|
481
590
|
|
|
482
591
|
switch (firstArg) {
|
|
483
|
-
case
|
|
592
|
+
case "audit":
|
|
484
593
|
await runAudit(null, true);
|
|
485
594
|
break;
|
|
486
|
-
case
|
|
487
|
-
await runInit(rawArgs[1] ||
|
|
595
|
+
case "init":
|
|
596
|
+
await runInit(rawArgs[1] || "src/chemical-x");
|
|
488
597
|
break;
|
|
489
|
-
case
|
|
598
|
+
case "create":
|
|
490
599
|
await runScaffold(rawArgs[1]);
|
|
491
600
|
break;
|
|
492
|
-
case
|
|
493
|
-
case
|
|
494
|
-
case
|
|
601
|
+
case "generate":
|
|
602
|
+
case "capsule":
|
|
603
|
+
case "add":
|
|
495
604
|
if (!rawArgs[1]) {
|
|
496
|
-
process.stderr.write(
|
|
605
|
+
process.stderr.write(
|
|
606
|
+
"Usage: npx chemx generate <capsule-name>\nExample: npx chemx generate m-user-avatar\n"
|
|
607
|
+
);
|
|
497
608
|
process.exit(1);
|
|
498
609
|
}
|
|
499
610
|
runGenerateCapsule(rawArgs[1]);
|
|
500
611
|
break;
|
|
501
|
-
case
|
|
502
|
-
case
|
|
503
|
-
case
|
|
612
|
+
case "help":
|
|
613
|
+
case "--help":
|
|
614
|
+
case "-h":
|
|
504
615
|
printHelp();
|
|
505
616
|
break;
|
|
506
617
|
default:
|
|
507
|
-
if (firstArg && firstArg.startsWith(
|
|
618
|
+
if (firstArg && firstArg.startsWith("m-")) {
|
|
508
619
|
runGenerateCapsule(firstArg);
|
|
509
|
-
} else if (firstArg && !firstArg.startsWith(
|
|
620
|
+
} else if (firstArg && !firstArg.startsWith("-")) {
|
|
510
621
|
await runScaffold(firstArg);
|
|
511
622
|
} else {
|
|
512
623
|
printHelp();
|
|
@@ -519,4 +630,3 @@ main().catch((err) => {
|
|
|
519
630
|
process.stderr.write(`\x1b[31m✕ Unexpected Error: ${err.message}\x1b[0m\n`);
|
|
520
631
|
process.exit(1);
|
|
521
632
|
});
|
|
522
|
-
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -37,11 +37,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
|
37
37
|
- Unlocked `npx chemx audit` command as 100% free, unauthenticated, and ungated public utility with conversion CTAs.
|
|
38
38
|
- Added dual project scaffolder (`npm create chemx` / `create-chemx`) and in-repo capsule drop-in (`init`).
|
|
39
39
|
- Gated `runScaffold` (`npm create chemx`) with upfront license validation prior to project directory name prompt.
|
|
40
|
-
- Integrated interactive Gum CTA action buttons at the conclusion of public audit for instant checkout launch ($
|
|
40
|
+
- Integrated interactive Gum CTA action buttons at the conclusion of public audit for default portal browsing (`chemicalx.xophz.com`), instant checkout launch ($27 eBook w/ AGENTS.md / $47 Master Bundle), and key-gated scaffolding for license holders.
|
|
41
41
|
|
|
42
42
|
### Fixed
|
|
43
43
|
- Removed embedded offline blueprint fallbacks and preview bypass keys from CLI executable (`cli/index.js`).
|
|
44
44
|
- Restricted npm package distribution via `files` whitelist and `.npmignore` to prevent leaking private blueprints and hooks in public tarballs.
|
|
45
45
|
- Added explicit `--tag` support and automatic default fallback for prerelease/CalVer versions in multi-target publisher (`scripts/publish-both.mjs`).
|
|
46
|
+
- Removed unscoped `chem-x` target from publisher script due to npm registry similarity protection with `chemx`.
|
|
47
|
+
- Excluded 3rd-party `vendor` and `build` directories, as well as minified bundles (`*.min.*`), from the AST context hazard audit to eliminate false positives on bundled external libraries.
|
|
48
|
+
- Fixed argument ordering in `gumChoose` to pass `--header` and styling flags to the `choose` subcommand rather than prepending to the parent binary.
|
|
46
49
|
|
|
47
50
|
|