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 +4 -1
- package/dist/library/commandHandler.js +90 -39
- package/dist/library/fsToAst.js +31 -11
- package/dist/library/plot/ai.js +148 -0
- package/dist/library/plot/aiClient.js +36 -0
- package/dist/library/plot/generateStructure.js +30 -0
- package/dist/library/plot/lis.js +158 -0
- package/dist/library/validateFS.js +16 -7
- package/dist/utils/index.js +16 -8
- package/package.json +2 -2
- package/readme.md +272 -386
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
|
|
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
|
-
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
|
|
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);
|
package/dist/library/fsToAst.js
CHANGED
|
@@ -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(
|
|
13
|
-
`${data_1.theme.info(
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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(
|
|
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;
|