scaffoldrite 2.0.7 → 2.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/cli.js CHANGED
@@ -17,7 +17,10 @@ function warnLegacyConfig() {
17
17
  const legacyStructure = path_1.default.join(cwd, "structure.sr");
18
18
  const legacyIgnore = path_1.default.join(cwd, ".scaffoldignore");
19
19
  const newConfigDir = path_1.default.join(cwd, ".scaffoldrite");
20
- const hasLegacy = fs_1.default.existsSync(legacyStructure) || fs_1.default.existsSync(legacyIgnore);
20
+ const scaffoldriteProjectConfig = path_1.default.join(newConfigDir, "scaffoldrite-project.json");
21
+ const scaffoldriteGlobalConfig = path_1.default.join(newConfigDir, "scaffoldrite.json");
22
+ const hasLegacy = fs_1.default.existsSync(legacyStructure) || fs_1.default.existsSync(legacyIgnore)
23
+ || fs_1.default.existsSync(scaffoldriteProjectConfig) || fs_1.default.existsSync(scaffoldriteGlobalConfig);
21
24
  const hasNewConfig = fs_1.default.existsSync(newConfigDir);
22
25
  if (hasLegacy && hasNewConfig && !(0, utils_2.hasFlag)('--migrate')) {
23
26
  console.log(data_1.theme.warning(`${data_1.icons.warning} Detected legacy Scaffoldrite config in project root.\n` +
@@ -28,7 +28,6 @@ const index_7 = require("../utils/index");
28
28
  const index_8 = require("../utils/index");
29
29
  const index_9 = require("../utils/index");
30
30
  const index_10 = require("../utils/index");
31
- const checkpackage_1 = require("./checkpage/checkpackage");
32
31
  const args = process.argv.slice(3).filter((a) => !a.startsWith("--"));
33
32
  const arg3 = args[0];
34
33
  const arg4 = args[1];
@@ -131,7 +130,6 @@ exports.commandHandlers = {
131
130
  // return;
132
131
  // }
133
132
  // },
134
- "check-packages": checkpackage_1.checkAndReportPackages,
135
133
  init: async () => {
136
134
  const shouldOverwrite = force;
137
135
  const sDir = path_1.default.join(index_7.baseDir, ".scaffoldrite");
@@ -139,27 +137,26 @@ exports.commandHandlers = {
139
137
  if (!fs_1.default.existsSync(sDir)) {
140
138
  fs_1.default.mkdirSync(sDir, { recursive: true });
141
139
  }
140
+ const projectConfig = path_1.default.join(sDir, "project.json");
142
141
  const legacyStructure = path_1.default.join(index_7.baseDir, "structure.sr");
143
142
  const legacyIgnore = path_1.default.join(index_7.baseDir, ".scaffoldignore");
144
143
  const structureExists = fs_1.default.existsSync(index_6.STRUCTURE_PATH);
145
144
  const ignoreExists = fs_1.default.existsSync(index_6.IGNORE_PATH);
146
- /* ===============================
147
- * OVERWRITE GUARDS
148
- * =============================== */
145
+ const projectExists = fs_1.default.existsSync(projectConfig);
149
146
  const existingConfigs = [];
150
147
  if (structureExists)
151
148
  existingConfigs.push("structure.sr");
152
149
  if (ignoreExists)
153
150
  existingConfigs.push(".scaffoldignore");
151
+ if (projectExists)
152
+ existingConfigs.push("project.json");
154
153
  if (!shouldOverwrite && existingConfigs.length > 0) {
155
154
  console.error(index_4.theme.error.bold(`${index_4.icons.error} The following files already exist:\n`) +
156
155
  existingConfigs.map(f => index_4.theme.muted(` - ${f}`)).join("\n") +
157
156
  index_4.theme.warning(`\n\nUse --force to overwrite everything.`));
158
157
  (0, index_8.exit)(1);
159
158
  }
160
- /* ===============================
161
- * HANDLE MIGRATION
162
- * =============================== */
159
+ /* =============================== HANDLE MIGRATION =============================== */
163
160
  if (migrate) {
164
161
  let migrated = false;
165
162
  if (fs_1.default.existsSync(legacyStructure)) {
@@ -172,58 +169,62 @@ exports.commandHandlers = {
172
169
  console.log(index_4.theme.success(`${index_4.icons.check} Moved .scaffoldignore → .scaffoldrite/.scaffoldignore`));
173
170
  migrated = true;
174
171
  }
175
- if (!migrated) {
172
+ if (!migrated)
176
173
  console.log(index_4.theme.info(`${index_4.icons.info} No legacy config found to migrate.`));
177
- }
178
174
  return;
179
175
  }
180
- /* ===============================
181
- * EMPTY INIT
182
- * =============================== */
176
+ /* =============================== EMPTY INIT =============================== */
183
177
  if (empty) {
184
- const root = {
185
- type: "folder",
186
- name: ".",
187
- children: [],
188
- };
178
+ const root = { type: "folder", name: ".", children: [] };
189
179
  (0, index_3.saveStructure)(root, parsed.rawConstraints, index_6.STRUCTURE_PATH);
190
180
  if (shouldOverwrite || !fs_1.default.existsSync(index_6.IGNORE_PATH)) {
191
- if (shouldOverwrite && (fs_1.default.existsSync(index_6.IGNORE_PATH) || fs_1.default.existsSync(index_6.STRUCTURE_PATH))) {
192
- console.warn(index_4.theme.warning(`${index_4.icons.warning} Overwriting existing due to --force`));
193
- }
194
181
  fs_1.default.writeFileSync(index_6.IGNORE_PATH, index_1.DEFAULT_IGNORE_TEMPLATE);
195
182
  }
196
183
  console.log(index_4.theme.success(`${index_4.icons.success} Empty structure.sr created`));
197
- return;
198
184
  }
199
- /* ===============================
200
- * INIT FROM FILESYSTEM
201
- * =============================== */
202
- if (fromFs) {
185
+ else if (fromFs) {
186
+ /* =============================== INIT FROM FILESYSTEM =============================== */
203
187
  const targetDir = path_1.default.resolve(arg3 ?? index_7.baseDir);
204
188
  const ignoreList = (0, index_2.getIgnoreList)();
205
189
  const ast = (0, fsToAst_1.buildASTFromFS)(targetDir, ignoreList);
206
190
  (0, index_3.saveStructure)(ast, parsed.rawConstraints, index_6.STRUCTURE_PATH);
207
191
  if (shouldOverwrite || !fs_1.default.existsSync(index_6.IGNORE_PATH)) {
208
- if (shouldOverwrite && (fs_1.default.existsSync(index_6.IGNORE_PATH) || fs_1.default.existsSync(index_6.STRUCTURE_PATH))) {
209
- console.warn(index_4.theme.warning(`${index_4.icons.warning} Overwriting existing due to --force`));
210
- }
211
192
  fs_1.default.writeFileSync(index_6.IGNORE_PATH, index_1.DEFAULT_IGNORE_TEMPLATE);
212
193
  }
213
194
  console.log(index_4.theme.success(`${index_4.icons.success} structure.sr generated from filesystem: `) + index_4.theme.light(targetDir));
214
- return;
215
195
  }
216
- /* ===============================
217
- * DEFAULT INIT (TEMPLATE)
218
- * =============================== */
219
- (0, index_3.saveStructure)(parsed.root, parsed.rawConstraints, index_6.STRUCTURE_PATH);
220
- if (shouldOverwrite || !fs_1.default.existsSync(index_6.IGNORE_PATH)) {
221
- if (shouldOverwrite && (fs_1.default.existsSync(index_6.IGNORE_PATH) || fs_1.default.existsSync(index_6.STRUCTURE_PATH))) {
222
- console.warn(index_4.theme.warning(`${index_4.icons.warning} Overwriting existing due to --force`));
196
+ else {
197
+ /* =============================== DEFAULT INIT (TEMPLATE) =============================== */
198
+ (0, index_3.saveStructure)(parsed.root, parsed.rawConstraints, index_6.STRUCTURE_PATH);
199
+ if (shouldOverwrite || !fs_1.default.existsSync(index_6.IGNORE_PATH)) {
200
+ fs_1.default.writeFileSync(index_6.IGNORE_PATH, index_1.DEFAULT_IGNORE_TEMPLATE);
223
201
  }
224
- fs_1.default.writeFileSync(index_6.IGNORE_PATH, index_1.DEFAULT_IGNORE_TEMPLATE);
202
+ console.log(index_4.theme.success(`${index_4.icons.success} structure.sr created`));
203
+ }
204
+ /* =============================== CREATE GLOBAL JSON =============================== */
205
+ if (shouldOverwrite || !fs_1.default.existsSync(projectConfig)) {
206
+ fs_1.default.writeFileSync(projectConfig, JSON.stringify({
207
+ framework: "",
208
+ language: "",
209
+ version: "1.0.0",
210
+ author: "",
211
+ conventions: {
212
+ description: "",
213
+ folderStructure: [],
214
+ namingRules: [],
215
+ folderDepthLimits: [],
216
+ userNotes: []
217
+ },
218
+ additionalInfo: {
219
+ stateManagement: "",
220
+ styling: "",
221
+ testing: "",
222
+ routing: "",
223
+ apiClient: ""
224
+ },
225
+ }, null, 2));
226
+ console.log(index_4.theme.info(`${index_4.icons.info} Created project config: project.json`));
225
227
  }
226
- console.log(index_4.theme.success(`${index_4.icons.success} structure.sr created`));
227
228
  return;
228
229
  },
229
230
  update: async () => {
@@ -379,6 +380,56 @@ exports.commandHandlers = {
379
380
  }
380
381
  return;
381
382
  },
383
+ find: async () => {
384
+ const searchQuery = arg3;
385
+ if (!searchQuery) {
386
+ console.error(index_4.theme.error.bold(`${index_4.icons.error} Please provide a file, folder, or path to find.`));
387
+ (0, index_3.printUsage)("find");
388
+ (0, index_8.exit)(1);
389
+ }
390
+ const results = [];
391
+ const ignoreList = (0, index_2.getIgnoreList)();
392
+ const searchStructure = isStructure || (!isStructure && !isFS); // default searches both
393
+ const searchFilesystem = isFS || (!isStructure && !isFS);
394
+ // 1️⃣ Search in structure.sr
395
+ if (searchStructure && fs_1.default.existsSync(index_6.STRUCTURE_PATH)) {
396
+ const structure = (0, index_3.loadAST)();
397
+ function searchAST(node, currentPath = "") {
398
+ for (const child of node.children) {
399
+ const childPath = path_1.default.posix.join(currentPath, child.name);
400
+ if (child.name.includes(searchQuery) || childPath.includes(searchQuery)) {
401
+ results.push(`${index_4.icons.file} [structure] ${childPath}`);
402
+ }
403
+ if (child.type === "folder") {
404
+ searchAST(child, childPath);
405
+ }
406
+ }
407
+ }
408
+ searchAST(structure.root);
409
+ }
410
+ // 2️⃣ Search in filesystem
411
+ if (searchFilesystem) {
412
+ const fsAst = (0, fsToAst_1.buildASTFromFS)(index_7.baseDir, ignoreList);
413
+ function searchFSAST(node, currentPath = "") {
414
+ for (const child of node.children) {
415
+ const childPath = path_1.default.posix.join(currentPath, child.name);
416
+ if (child.name.includes(searchQuery) || childPath.includes(searchQuery)) {
417
+ results.push(`${index_4.icons.folder} [fs] ${childPath}`);
418
+ }
419
+ if (child.type === "folder") {
420
+ searchFSAST(child, childPath);
421
+ }
422
+ }
423
+ }
424
+ searchFSAST(fsAst);
425
+ }
426
+ if (results.length === 0) {
427
+ console.log(index_4.theme.warning(`${index_4.icons.warning} Could not find '${searchQuery}' in the selected scope.`));
428
+ return;
429
+ }
430
+ console.log(index_4.theme.primary.bold(`\nFound ${results.length} match(es) for '${searchQuery}':`));
431
+ results.forEach(r => console.log(index_4.theme.light(` ${r}`)));
432
+ },
382
433
  generate: async () => {
383
434
  const structure = (0, index_3.loadAST)();
384
435
  (0, validator_1.validateConstraints)(structure.root, structure.constraints);
@@ -9,8 +9,8 @@ const path_1 = __importDefault(require("path"));
9
9
  const data_1 = require("../data");
10
10
  function buildASTFromFS(dir, ignoreList = []) {
11
11
  if (!fs_1.default.existsSync(dir)) {
12
- throw new Error(`${data_1.icons.error} ${data_1.theme.error('Directory not found:')} ${data_1.theme.highlight(dir)}\n` +
13
- `${data_1.theme.info('Tip:')} Make sure the directory exists or create it with ${data_1.theme.primary(`mkdir -p "${dir}"`)}`);
12
+ throw new Error(`${data_1.icons.error} ${data_1.theme.error("Directory not found:")} ${data_1.theme.highlight(dir)}\n` +
13
+ `${data_1.theme.info("Tip:")} Make sure the directory exists or create it with ${data_1.theme.primary(`mkdir -p "${dir}"`)}`);
14
14
  }
15
15
  const root = {
16
16
  type: "folder",
@@ -23,26 +23,46 @@ function buildASTFromFS(dir, ignoreList = []) {
23
23
  };
24
24
  function scan(folderPath, node) {
25
25
  const items = fs_1.default.readdirSync(folderPath);
26
- for (const item of items) {
27
- if (ignoreList.includes(item))
28
- continue;
26
+ // 🔥 SORTING LOGIC (folders first, then alphabetical)
27
+ const sortedItems = items
28
+ .map((item) => {
29
29
  const itemPath = path_1.default.join(folderPath, item);
30
- if (isScaffoldriteInternal(itemPath))
31
- continue;
32
30
  const stat = fs_1.default.statSync(itemPath);
33
- if (stat.isDirectory()) {
31
+ return {
32
+ name: item,
33
+ path: itemPath,
34
+ isDirectory: stat.isDirectory(),
35
+ };
36
+ })
37
+ .sort((a, b) => {
38
+ // 1️⃣ Folders first
39
+ if (a.isDirectory && !b.isDirectory)
40
+ return -1;
41
+ if (!a.isDirectory && b.isDirectory)
42
+ return 1;
43
+ // 2️⃣ Alphabetical (case-insensitive)
44
+ return a.name.localeCompare(b.name, undefined, {
45
+ sensitivity: "base",
46
+ });
47
+ });
48
+ for (const item of sortedItems) {
49
+ if (ignoreList.includes(item.name))
50
+ continue;
51
+ if (isScaffoldriteInternal(item.path))
52
+ continue;
53
+ if (item.isDirectory) {
34
54
  const childFolder = {
35
55
  type: "folder",
36
- name: item,
56
+ name: item.name,
37
57
  children: [],
38
58
  };
39
59
  node.children.push(childFolder);
40
- scan(itemPath, childFolder);
60
+ scan(item.path, childFolder);
41
61
  }
42
62
  else {
43
63
  const childFile = {
44
64
  type: "file",
45
- name: item,
65
+ name: item.name,
46
66
  };
47
67
  node.children.push(childFile);
48
68
  }
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env ts-node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.ai = void 0;
8
+ const child_process_1 = require("child_process");
9
+ const data_1 = require("../../data");
10
+ const path_1 = __importDefault(require("path"));
11
+ const fs_1 = __importDefault(require("fs"));
12
+ require("dotenv/config");
13
+ const ai_workflow_1 = require("@scaffoldrite/ai-workflow");
14
+ const readline_1 = __importDefault(require("readline"));
15
+ // ─────────────────────────────────────────────
16
+ // 1️⃣ LLM Adapter Setup
17
+ // ─────────────────────────────────────────────
18
+ const llmAdapter = (0, ai_workflow_1.createAdapter)("groq", {
19
+ apiKey: process.env.GROQ_API_KEY || "",
20
+ model: "llama-3.1-8b-instant",
21
+ maxTokens: 1200,
22
+ temperature: 0.2,
23
+ });
24
+ // ─────────────────────────────────────────────
25
+ // 2️⃣ sanitize AI output
26
+ // ─────────────────────────────────────────────
27
+ function sanitizeStructureSR(input) {
28
+ return input
29
+ .split("\n")
30
+ .filter((line) => {
31
+ const t = line.trim();
32
+ return (t === "" ||
33
+ t === "{" ||
34
+ t === "}" ||
35
+ /^(folder|file|constraints)/.test(t));
36
+ })
37
+ .join("\n")
38
+ .trim();
39
+ }
40
+ // ─────────────────────────────────────────────
41
+ // 3️⃣ create readline
42
+ // ─────────────────────────────────────────────
43
+ function createRL() {
44
+ return readline_1.default.createInterface({
45
+ input: process.stdin,
46
+ output: process.stdout,
47
+ terminal: true,
48
+ });
49
+ }
50
+ // ─────────────────────────────────────────────
51
+ // 4️⃣ collect multi-line input
52
+ // ─────────────────────────────────────────────
53
+ async function collectInput(rl) {
54
+ console.log(`
55
+ 📝 Describe what you want.
56
+
57
+ ENTER → new line
58
+ CTRL + D → submit
59
+ CTRL + C → exit
60
+ `);
61
+ const lines = [];
62
+ return new Promise((resolve) => {
63
+ const onLine = (line) => {
64
+ lines.push(line);
65
+ };
66
+ rl.on("line", onLine);
67
+ // CTRL+D → readline closes → we capture and resolve
68
+ rl.once("close", () => {
69
+ rl.removeListener("line", onLine);
70
+ console.log("\n🚀 Submitting...\n");
71
+ resolve(lines.join(" "));
72
+ });
73
+ });
74
+ }
75
+ // ─────────────────────────────────────────────
76
+ // 5️⃣ Main AI CLI
77
+ // ─────────────────────────────────────────────
78
+ const ai = async () => {
79
+ try {
80
+ const projectPath = path_1.default.resolve(process.cwd());
81
+ (0, child_process_1.execSync)("sr init --from-fs . --force", {
82
+ cwd: projectPath,
83
+ stdio: "inherit",
84
+ shell: process.platform === "win32" ? "cmd.exe" : "/bin/sh",
85
+ });
86
+ // exit nicely
87
+ process.on("SIGINT", () => {
88
+ console.log("\n👋 Exiting AI assistant...");
89
+ process.exit(0);
90
+ });
91
+ // 🔥 infinite assistant loop
92
+ while (true) {
93
+ const rl = createRL(); // recreate every round (EOF safe)
94
+ const description = await collectInput(rl);
95
+ if (!description.trim()) {
96
+ console.log("⚠️ Please enter a request.\n");
97
+ continue;
98
+ }
99
+ const structurePath = path_1.default.join(projectPath, ".scaffoldrite", "structure.sr");
100
+ const existingStructure = fs_1.default.readFileSync(structurePath, "utf-8");
101
+ const result = await (0, ai_workflow_1.runWorkflow)({
102
+ existingStructure,
103
+ userRequest: description,
104
+ llmAdapter,
105
+ onProgress: (step, message) => console.log(`Step ${step}: ${message}`),
106
+ });
107
+ // ─────────────────────────
108
+ // Handle results
109
+ // ─────────────────────────
110
+ if (result.type === "clarify") {
111
+ console.log("\n🤖 AI requires clarification:\n");
112
+ console.log(result.message);
113
+ console.log("Options:", result.options.join(", "));
114
+ continue;
115
+ }
116
+ if (result.type === "answer") {
117
+ console.log("\n🤖 AI Answer:\n");
118
+ console.log(result.message);
119
+ continue;
120
+ }
121
+ if (result.type === "improve") {
122
+ const suggestions = result.suggestions.map((s) => {
123
+ if (typeof s === "string")
124
+ return s;
125
+ if (s.description && s.path)
126
+ return `${s.description} (${s.path})`;
127
+ if (s.description)
128
+ return s.description;
129
+ return JSON.stringify(s);
130
+ });
131
+ console.log("\n💡 AI Suggestions for improvement:");
132
+ suggestions.forEach((s, i) => console.log(`${i + 1}. ${s}`));
133
+ continue;
134
+ }
135
+ if (["create", "delete", "move", "rename"].includes(result.type)) {
136
+ const clean = sanitizeStructureSR(JSON.stringify(result, null, 2));
137
+ fs_1.default.writeFileSync(structurePath, clean);
138
+ (0, child_process_1.execSync)("sr merge --from-fs .", { cwd: projectPath, stdio: "inherit" });
139
+ (0, child_process_1.execSync)("sr generate .", { cwd: projectPath, stdio: "inherit" });
140
+ (0, child_process_1.execSync)("sr list --sr --with-icon", { cwd: projectPath, stdio: "inherit" });
141
+ }
142
+ }
143
+ }
144
+ catch (err) {
145
+ console.error(data_1.theme.error(`❌ Failed: ${err.message}`));
146
+ }
147
+ };
148
+ exports.ai = ai;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.callAI = callAI;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ const axios_retry_1 = __importDefault(require("axios-retry"));
9
+ const client = axios_1.default.create({
10
+ baseURL: "https://api.groq.com/openai/v1",
11
+ timeout: 25000,
12
+ headers: {
13
+ Authorization: `Bearer ${process.env.GROQ_API_KEY}`,
14
+ "Content-Type": "application/json",
15
+ },
16
+ });
17
+ (0, axios_retry_1.default)(client, {
18
+ retries: 3,
19
+ retryDelay: axios_retry_1.default.exponentialDelay,
20
+ retryCondition: (error) => {
21
+ return (axios_retry_1.default.isNetworkOrIdempotentRequestError(error) ||
22
+ error.code === "ECONNRESET");
23
+ },
24
+ });
25
+ async function callAI(system, user, temperature) {
26
+ const res = await client.post("/chat/completions", {
27
+ model: "llama-3.1-8b-instant",
28
+ temperature,
29
+ max_tokens: 1200,
30
+ messages: [
31
+ { role: "system", content: system },
32
+ { role: "user", content: user },
33
+ ],
34
+ });
35
+ return res.data.choices[0].message.content.trim();
36
+ }
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateStructureStream = generateStructureStream;
4
+ const eventsource_1 = require("eventsource");
5
+ function generateStructureStream({ existingStructure, description, framework, language, }) {
6
+ return new Promise((resolve, reject) => {
7
+ let finalResult = "";
8
+ const url = `http://localhost:3000/generate-structure-stream?existingStructure=${encodeURIComponent(existingStructure)}&description=${encodeURIComponent(description)}&framework=${encodeURIComponent(framework)}&language=${encodeURIComponent(language)}`;
9
+ const es = new eventsource_1.EventSource(url);
10
+ es.onmessage = (event) => {
11
+ const data = JSON.parse(event.data);
12
+ if (data.message) {
13
+ console.log(data.message);
14
+ }
15
+ if (data.done) {
16
+ finalResult = data.result;
17
+ es.close();
18
+ resolve(finalResult);
19
+ }
20
+ if (data.error) {
21
+ es.close();
22
+ reject(new Error(data.error));
23
+ }
24
+ };
25
+ es.onerror = (err) => {
26
+ es.close();
27
+ reject(err || new Error("Unknown SSE error"));
28
+ };
29
+ });
30
+ }
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ai = void 0;
7
+ const child_process_1 = require("child_process");
8
+ const data_1 = require("../../data");
9
+ const path_1 = __importDefault(require("path"));
10
+ const fs_1 = __importDefault(require("fs"));
11
+ const generateStructure_1 = require("./generateStructure");
12
+ const readline_1 = __importDefault(require("readline"));
13
+ // ─────────────────────────────────────────────
14
+ // HELPER: readline wrapper
15
+ // ─────────────────────────────────────────────
16
+ function createAsk() {
17
+ const rl = readline_1.default.createInterface({
18
+ input: process.stdin,
19
+ output: process.stdout,
20
+ });
21
+ const ask = (q) => new Promise((res) => rl.question(q, res));
22
+ return { ask, close: () => rl.close(), rl };
23
+ }
24
+ // ─────────────────────────────────────────────
25
+ // HELPER: sanitize AI output for ScaffoldRite
26
+ // ─────────────────────────────────────────────
27
+ function sanitizeStructureSR(input) {
28
+ return input
29
+ .split("\n")
30
+ .filter((line) => {
31
+ const t = line.trim();
32
+ return (t === "" ||
33
+ t === "{" ||
34
+ t === "}" ||
35
+ /^(folder|file|constraints)/.test(t));
36
+ })
37
+ .join("\n")
38
+ .trim();
39
+ }
40
+ // ─────────────────────────────────────────────
41
+ // MAIN AI FUNCTION
42
+ // ─────────────────────────────────────────────
43
+ const ai = async () => {
44
+ try {
45
+ // ─────────────────────────────────────────────
46
+ // 1️⃣ INITIAL QUESTIONS
47
+ // ─────────────────────────────────────────────
48
+ let { ask, close, rl } = createAsk();
49
+ const projectName = await ask("Project name: ");
50
+ const framework = await ask("Framework (react, vue): ");
51
+ const language = await ask("Language (js, ts): ");
52
+ close(); // ❗ Close before running execSync
53
+ // ─────────────────────────────────────────────
54
+ // 2️⃣ CREATE VITE PROJECT
55
+ // ─────────────────────────────────────────────
56
+ let template = framework.toLowerCase();
57
+ if (framework === "react" && language === "ts")
58
+ template = "react-ts";
59
+ if (framework === "vue" && language === "ts")
60
+ template = "vue-ts";
61
+ (0, child_process_1.execSync)(`npx create-vite@latest "${projectName}" --template ${template} --no-rolldown --no-immediate`, {
62
+ stdio: "inherit",
63
+ shell: process.platform === "win32" ? "cmd.exe" : "/bin/sh",
64
+ });
65
+ const projectPath = path_1.default.resolve(process.cwd(), projectName);
66
+ // ─────────────────────────────────────────────
67
+ // 3️⃣ INIT SCAFFOLDRITE
68
+ // ─────────────────────────────────────────────
69
+ (0, child_process_1.execSync)("sr init --from-fs .", {
70
+ cwd: projectPath,
71
+ stdio: "inherit",
72
+ shell: process.platform === "win32" ? "cmd.exe" : "/bin/sh",
73
+ });
74
+ // ─────────────────────────────────────────────
75
+ // 4️⃣ ASK FOR AI ASSISTANCE
76
+ // ─────────────────────────────────────────────
77
+ ({ ask, close, rl } = createAsk());
78
+ const wantAI = await ask("\n🤖 Do you want AI assistance in scaffolding the structure of your app? (yes/no): ");
79
+ if (wantAI.toLowerCase() === "yes") {
80
+ while (true) {
81
+ console.log("\n📝 Paste your project description below. Press ENTER on an empty line when done:\n");
82
+ const descriptionLines = [];
83
+ // 🧠 FIX: controlled single listener, auto removed
84
+ await new Promise((res) => {
85
+ const onLine = (line) => {
86
+ if (line.trim() === "") {
87
+ rl.removeListener("line", onLine);
88
+ res();
89
+ }
90
+ else {
91
+ descriptionLines.push(line);
92
+ }
93
+ };
94
+ // extra safety
95
+ rl.removeAllListeners("line");
96
+ rl.on("line", onLine);
97
+ });
98
+ const description = descriptionLines.join(" ");
99
+ const structurePath = path_1.default.join(projectPath, ".scaffoldrite", "structure.sr");
100
+ const existingStructure = fs_1.default.readFileSync(structurePath, "utf-8");
101
+ // Stream AI output with progress
102
+ const result = await (0, generateStructure_1.generateStructureStream)({
103
+ existingStructure,
104
+ description,
105
+ framework,
106
+ language,
107
+ });
108
+ // 🧠 CLARIFICATION MODE
109
+ if (result.startsWith("CLARIFICATION_REQUIRED")) {
110
+ console.log("\n🤖 I need clarification:\n");
111
+ console.log(result);
112
+ const confirm = await ask("\nIs this what you meant? (yes/no): ");
113
+ if (confirm.toLowerCase() === "yes") {
114
+ console.log("\n✏️ Please rephrase clearly what you want:\n");
115
+ continue;
116
+ }
117
+ else {
118
+ console.log("\n🔁 Okay, please describe what you want again.\n");
119
+ continue;
120
+ }
121
+ }
122
+ // ✅ STRUCTURE MODE
123
+ const clean = sanitizeStructureSR(result);
124
+ fs_1.default.writeFileSync(structurePath, clean);
125
+ // SAFETY STEP — NON-NEGOTIABLE
126
+ (0, child_process_1.execSync)("sr merge --from-fs .", {
127
+ cwd: projectPath,
128
+ stdio: "inherit",
129
+ });
130
+ (0, child_process_1.execSync)("sr generate .", {
131
+ cwd: projectPath,
132
+ stdio: "inherit",
133
+ });
134
+ (0, child_process_1.execSync)("sr list --sr --with-icon", {
135
+ cwd: projectPath,
136
+ stdio: "inherit",
137
+ });
138
+ close(); // 🔥 close the previous interface first
139
+ ({ ask, close, rl } = createAsk());
140
+ const satisfied = await ask("\n✅ Are you satisfied with the structure? (yes/no): ");
141
+ if (satisfied.toLowerCase() === "yes")
142
+ break;
143
+ }
144
+ }
145
+ close();
146
+ // ─────────────────────────────────────────────
147
+ // 5️⃣ FINISH
148
+ // ─────────────────────────────────────────────
149
+ console.log(data_1.theme.success(`\n🎉 Project ${projectName} is ready!\n`));
150
+ console.log(data_1.theme.muted(` cd ${projectName}`));
151
+ console.log(data_1.theme.muted(" npm install"));
152
+ console.log(data_1.theme.muted(" npm run dev"));
153
+ }
154
+ catch (err) {
155
+ console.error(data_1.theme.error(`❌ Failed: ${err.message}`));
156
+ }
157
+ };
158
+ exports.ai = ai;