larats 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -2
- package/dist/index.js +258 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,8 +18,8 @@ When codebases grow large, developers often struggle to understand the architect
|
|
|
18
18
|
|
|
19
19
|
1. 🔍 **Architecture Scanner & Visualizer (`scan`)**: Instantly scans your repository, categorizes layers (Models, Controllers, Services, Actions, Filament Resources, Routes, etc.), and generates a clean `ARCHITECTURE.md` with an interactive **Mermaid.js flowchart** and directory tree.
|
|
20
20
|
2. 🏗️ **Contextual Module Scaffolder (`scaffold` / `make`)**: Quickly generates cohesive feature modules with clean, production-ready boilerplate code tailored to your exact stack (Laravel or TypeScript).
|
|
21
|
-
3.
|
|
22
|
-
|
|
21
|
+
3. 🤖 **AI Architecture Context Packager (`ai`)**: Exports a token-efficient system prompt teaching Cursor AI, Claude, Copilot, or ChatGPT your exact layered boundaries (`--copy`, `--cursor`, `--claude`).
|
|
22
|
+
4. 🔒 **100% Local & Secure**: No data ever leaves your computer. Sensitive files (`.env`, private keys, vendor folders) are strictly excluded.
|
|
23
23
|
---
|
|
24
24
|
|
|
25
25
|
## 🚀 Quick Start
|
|
@@ -36,6 +36,9 @@ npx larats scan
|
|
|
36
36
|
# Open interactive Mermaid diagram in your browser
|
|
37
37
|
npx larats view
|
|
38
38
|
|
|
39
|
+
# Export architecture context rules for AI (Cursor, Claude, Copilot)
|
|
40
|
+
npx larats ai --copy
|
|
41
|
+
|
|
39
42
|
# Or scaffold a new module directly
|
|
40
43
|
npx larats scaffold Payment
|
|
41
44
|
```
|
|
@@ -118,6 +121,13 @@ Create new features in seconds with consistent boilerplate:
|
|
|
118
121
|
### 3. 🛡️ Anti-Overwrite Protection
|
|
119
122
|
LaraTS will **NEVER** silently overwrite your existing code. If a target file already exists, LaraTS skips it and alerts you. You can only overwrite files by explicitly providing `--force`.
|
|
120
123
|
|
|
124
|
+
|
|
125
|
+
### 4. 🤖 AI Architecture Context Packager (`ai`)
|
|
126
|
+
Teach AI coding assistants (Cursor, Claude Code, GitHub Copilot, ChatGPT) your exact project architecture rules so they never write misplaced code again:
|
|
127
|
+
- **Copy to Clipboard**: `larats ai --copy` — Copies clean, compressed architecture instructions (< 500 tokens) ready to paste into ChatGPT or Claude.
|
|
128
|
+
- **Cursor AI Rules**: `larats ai --cursor` — Injects architecture boundaries and directory maps directly into `.cursorrules`.
|
|
129
|
+
- **Claude Code Rules**: `larats ai --claude` — Injects architecture conventions into `CLAUDE.md`.
|
|
130
|
+
*(Safe merge: uses `<!-- larats-rules:start -->` tags so existing user instructions are never overwritten).*
|
|
121
131
|
---
|
|
122
132
|
|
|
123
133
|
## 🔒 Strict Security & Safety Guarantees
|
|
@@ -142,6 +152,7 @@ USAGE:
|
|
|
142
152
|
COMMANDS:
|
|
143
153
|
scan Scan repository & generate ARCHITECTURE.md
|
|
144
154
|
view, preview Open interactive architecture diagram in local browser
|
|
155
|
+
ai Export architecture context prompt for AI (Cursor, Claude, Copilot)
|
|
145
156
|
scaffold, make [name] Scaffold a new module or feature with clean boilerplate
|
|
146
157
|
info Show detected project type & architectural flavor
|
|
147
158
|
help Display help message
|
|
@@ -149,6 +160,9 @@ COMMANDS:
|
|
|
149
160
|
OPTIONS:
|
|
150
161
|
-o, --output <file> Output path for scan (default: ARCHITECTURE.md)
|
|
151
162
|
--view Open local browser viewer immediately after scanning
|
|
163
|
+
--copy Copy AI architecture context prompt to clipboard
|
|
164
|
+
--cursor Inject architecture rules into .cursorrules
|
|
165
|
+
--claude Inject architecture rules into CLAUDE.md
|
|
152
166
|
-t, --tree Print directory tree in terminal
|
|
153
167
|
--html Also export standalone ARCHITECTURE.html
|
|
154
168
|
-m, --modular Enforce modular directory structure
|
package/dist/index.js
CHANGED
|
@@ -1091,6 +1091,135 @@ function generateArchitectureHtml(scanResult) {
|
|
|
1091
1091
|
</html>`;
|
|
1092
1092
|
}
|
|
1093
1093
|
|
|
1094
|
+
// src/generator/ai-context.ts
|
|
1095
|
+
var AI_RULES_START_TAG = "<!-- larats-rules:start -->";
|
|
1096
|
+
var AI_RULES_END_TAG = "<!-- larats-rules:end -->";
|
|
1097
|
+
function generateAiContext(scanResult) {
|
|
1098
|
+
const { projectInfo, components, modules } = scanResult;
|
|
1099
|
+
const lines = [];
|
|
1100
|
+
lines.push("# Codebase Architecture Rules");
|
|
1101
|
+
lines.push(`> Project: ${projectInfo.name} | Stack: ${projectInfo.summary}`);
|
|
1102
|
+
lines.push("");
|
|
1103
|
+
lines.push("## Architectural Boundaries & Principles");
|
|
1104
|
+
lines.push("- Maintain strict separation of concerns across active architectural layers.");
|
|
1105
|
+
lines.push("- Keep Controllers thin: handle HTTP requests, invoke Services, and return responses.");
|
|
1106
|
+
lines.push("- Encapsulate all business rules, calculations, and mutations inside Services/Actions.");
|
|
1107
|
+
lines.push("- Isolate data storage and queries to Repositories or Model entities.");
|
|
1108
|
+
lines.push("- Validate all incoming user inputs with dedicated DTOs or Form Requests before reaching domain logic.");
|
|
1109
|
+
lines.push("");
|
|
1110
|
+
lines.push("## Project Structure & Layer Conventions");
|
|
1111
|
+
if (projectInfo.type === "laravel") {
|
|
1112
|
+
const isModular = Boolean(projectInfo.laravel?.isModular);
|
|
1113
|
+
const modDir = projectInfo.laravel?.modulesDir || "app/Modules";
|
|
1114
|
+
if (isModular) {
|
|
1115
|
+
lines.push(`- **Architecture Style**: Modular Domain Architecture`);
|
|
1116
|
+
lines.push(`- **Module Root**: \`${modDir}/<FeatureName>/\``);
|
|
1117
|
+
lines.push(`- **Controllers**: \`${modDir}/<FeatureName>/Controllers/\` (HTTP handlers)`);
|
|
1118
|
+
lines.push(`- **Services / Actions**: \`${modDir}/<FeatureName>/Services/\` or \`Actions/\` (Business logic)`);
|
|
1119
|
+
lines.push(`- **Models**: \`${modDir}/<FeatureName>/Models/\` (Eloquent entities)`);
|
|
1120
|
+
lines.push(`- **Validation**: \`${modDir}/<FeatureName>/Requests/\` (FormRequest classes)`);
|
|
1121
|
+
lines.push(`- **Routes**: \`${modDir}/<FeatureName>/routes.php\` or root \`routes/\``);
|
|
1122
|
+
} else {
|
|
1123
|
+
lines.push(`- **Architecture Style**: Standard Laravel Layered Architecture`);
|
|
1124
|
+
lines.push(`- **Controllers**: \`app/Http/Controllers/\``);
|
|
1125
|
+
lines.push(`- **Services / Actions**: \`app/Services/\` or \`app/Actions/\``);
|
|
1126
|
+
lines.push(`- **Models**: \`app/Models/\``);
|
|
1127
|
+
lines.push(`- **Validation**: \`app/Http/Requests/\``);
|
|
1128
|
+
lines.push(`- **Routes**: \`routes/web.php\`, \`routes/api.php\``);
|
|
1129
|
+
lines.push(`- **Migrations**: \`database/migrations/\``);
|
|
1130
|
+
}
|
|
1131
|
+
if (projectInfo.laravel?.hasFilament) {
|
|
1132
|
+
lines.push(`- **Admin Panel**: Filament Resources live in \`app/Filament/Resources/\``);
|
|
1133
|
+
}
|
|
1134
|
+
} else if (projectInfo.type === "typescript") {
|
|
1135
|
+
const isModular = Boolean(projectInfo.typescript?.isModular);
|
|
1136
|
+
const srcDir = projectInfo.typescript?.srcDir || "src";
|
|
1137
|
+
const orm = projectInfo.typescript?.hasPrisma ? "Prisma" : projectInfo.typescript?.hasTypeORM ? "TypeORM" : projectInfo.typescript?.hasDrizzle ? "Drizzle" : null;
|
|
1138
|
+
if (isModular) {
|
|
1139
|
+
lines.push(`- **Architecture Style**: Feature Module Architecture`);
|
|
1140
|
+
lines.push(`- **Module Root**: \`${srcDir}/modules/<feature>/\``);
|
|
1141
|
+
lines.push(`- **Controllers**: \`${srcDir}/modules/<feature>/<feature>.controller.ts\``);
|
|
1142
|
+
lines.push(`- **Services**: \`${srcDir}/modules/<feature>/<feature>.service.ts\``);
|
|
1143
|
+
lines.push(`- **Repositories**: \`${srcDir}/modules/<feature>/<feature>.repository.ts\``);
|
|
1144
|
+
lines.push(`- **DTOs / Schemas**: \`${srcDir}/modules/<feature>/<feature>.dto.ts\``);
|
|
1145
|
+
lines.push(`- **Routes**: \`${srcDir}/modules/<feature>/<feature>.routes.ts\``);
|
|
1146
|
+
lines.push(`- **Module Index**: \`${srcDir}/modules/<feature>/index.ts\` (barrel export)`);
|
|
1147
|
+
} else {
|
|
1148
|
+
lines.push(`- **Architecture Style**: Layered Architecture`);
|
|
1149
|
+
lines.push(`- **Controllers**: \`${srcDir}/controllers/\``);
|
|
1150
|
+
lines.push(`- **Services**: \`${srcDir}/services/\``);
|
|
1151
|
+
lines.push(`- **Repositories / Data**: \`${srcDir}/repositories/\``);
|
|
1152
|
+
lines.push(`- **DTOs / Schemas**: \`${srcDir}/dtos/\` or \`${srcDir}/schemas/\``);
|
|
1153
|
+
lines.push(`- **Routes**: \`${srcDir}/routes/\``);
|
|
1154
|
+
}
|
|
1155
|
+
if (orm) {
|
|
1156
|
+
lines.push(`- **Database ORM**: ${orm} is used for data persistence.`);
|
|
1157
|
+
}
|
|
1158
|
+
} else {
|
|
1159
|
+
lines.push(`- **Source Code**: \`src/\``);
|
|
1160
|
+
lines.push(`- **Automated Tests**: \`tests/\``);
|
|
1161
|
+
}
|
|
1162
|
+
if (modules.length > 0) {
|
|
1163
|
+
lines.push("");
|
|
1164
|
+
lines.push(`## Active Feature Modules (${modules.length})`);
|
|
1165
|
+
lines.push(`Existing domains: ${modules.map((m) => `\`${m}\``).join(", ")}`);
|
|
1166
|
+
lines.push("When creating new features, follow this established domain partition.");
|
|
1167
|
+
}
|
|
1168
|
+
lines.push("");
|
|
1169
|
+
lines.push("## AI Coding Guardrails");
|
|
1170
|
+
lines.push("1. NEVER place raw queries or database transactions inside Controller files.");
|
|
1171
|
+
lines.push("2. When implementing a new feature, always create cohesive layers: Route -> Controller -> Service -> Repository/Model.");
|
|
1172
|
+
lines.push("3. Keep naming conventions consistent with existing files in the repository.");
|
|
1173
|
+
lines.push("4. Write automated unit/feature tests for new domain services.");
|
|
1174
|
+
return lines.join("\n");
|
|
1175
|
+
}
|
|
1176
|
+
function mergeAiRules(existingContent, newRules) {
|
|
1177
|
+
const block = `${AI_RULES_START_TAG}
|
|
1178
|
+
${newRules}
|
|
1179
|
+
${AI_RULES_END_TAG}`;
|
|
1180
|
+
if (!existingContent || existingContent.trim() === "") {
|
|
1181
|
+
return block + "\n";
|
|
1182
|
+
}
|
|
1183
|
+
const regex = new RegExp(`${AI_RULES_START_TAG}[\\s\\S]*?${AI_RULES_END_TAG}`, "m");
|
|
1184
|
+
if (regex.test(existingContent)) {
|
|
1185
|
+
return existingContent.replace(regex, block);
|
|
1186
|
+
}
|
|
1187
|
+
return existingContent.trimEnd() + "\n\n" + block + "\n";
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// src/utils/clipboard.ts
|
|
1191
|
+
import { spawn } from "child_process";
|
|
1192
|
+
async function copyToClipboard(text2) {
|
|
1193
|
+
const platform = process.platform;
|
|
1194
|
+
let command;
|
|
1195
|
+
let args = [];
|
|
1196
|
+
if (platform === "win32") {
|
|
1197
|
+
command = "clip";
|
|
1198
|
+
} else if (platform === "darwin") {
|
|
1199
|
+
command = "pbcopy";
|
|
1200
|
+
} else {
|
|
1201
|
+
command = process.env.WAYLAND_DISPLAY ? "wl-copy" : "xclip";
|
|
1202
|
+
if (command === "xclip") {
|
|
1203
|
+
args = ["-selection", "clipboard"];
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
return new Promise((resolve) => {
|
|
1207
|
+
try {
|
|
1208
|
+
const child = spawn(command, args, { stdio: ["pipe", "ignore", "ignore"] });
|
|
1209
|
+
child.on("error", () => {
|
|
1210
|
+
resolve(false);
|
|
1211
|
+
});
|
|
1212
|
+
child.on("close", (code) => {
|
|
1213
|
+
resolve(code === 0);
|
|
1214
|
+
});
|
|
1215
|
+
child.stdin.write(text2);
|
|
1216
|
+
child.stdin.end();
|
|
1217
|
+
} catch {
|
|
1218
|
+
resolve(false);
|
|
1219
|
+
}
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1094
1223
|
// src/generator/scaffolder.ts
|
|
1095
1224
|
import fs2 from "fs";
|
|
1096
1225
|
import path3 from "path";
|
|
@@ -2032,7 +2161,7 @@ async function scaffoldModule(projectInfo, options) {
|
|
|
2032
2161
|
}
|
|
2033
2162
|
|
|
2034
2163
|
// src/index.ts
|
|
2035
|
-
var VERSION = true ? "1.1.
|
|
2164
|
+
var VERSION = true ? "1.1.1" : "1.1.1";
|
|
2036
2165
|
function showHelp() {
|
|
2037
2166
|
console.log(`
|
|
2038
2167
|
${pc.bold(pc.cyan("LaraTS CLI"))} ${pc.gray(`v${VERSION}`)}
|
|
@@ -2045,6 +2174,7 @@ ${pc.bold("USAGE:")}
|
|
|
2045
2174
|
${pc.bold("COMMANDS:")}
|
|
2046
2175
|
${pc.green("scan")} Scan repository & generate ARCHITECTURE.md
|
|
2047
2176
|
${pc.green("view")}, ${pc.green("preview")} Open interactive architecture diagram in local browser
|
|
2177
|
+
${pc.green("ai")} Export architecture context prompt for AI (Cursor, Claude, Copilot)
|
|
2048
2178
|
${pc.green("scaffold")}, ${pc.green("make")} [name] Scaffold a new module or feature with clean boilerplate
|
|
2049
2179
|
${pc.green("info")} Show detected project type & architectural flavor
|
|
2050
2180
|
${pc.green("help")} Display this help message
|
|
@@ -2052,6 +2182,9 @@ ${pc.bold("COMMANDS:")}
|
|
|
2052
2182
|
${pc.bold("OPTIONS:")}
|
|
2053
2183
|
${pc.yellow("-o, --output")} <file> Output path for scan (default: ARCHITECTURE.md)
|
|
2054
2184
|
${pc.yellow("--view")} Open local browser viewer immediately after scanning
|
|
2185
|
+
${pc.yellow("--copy")} Copy AI architecture context prompt to clipboard
|
|
2186
|
+
${pc.yellow("--cursor")} Inject architecture rules into .cursorrules
|
|
2187
|
+
${pc.yellow("--claude")} Inject architecture rules into CLAUDE.md
|
|
2055
2188
|
${pc.yellow("-t, --tree")} Print directory tree in terminal
|
|
2056
2189
|
${pc.yellow("--html")} Also export standalone ARCHITECTURE.html
|
|
2057
2190
|
${pc.yellow("-m, --modular")} Enforce modular directory structure
|
|
@@ -2072,12 +2205,12 @@ function handleCancel() {
|
|
|
2072
2205
|
p.cancel(pc.yellow("Operation cancelled. See you next time! \u{1F44B}"));
|
|
2073
2206
|
process.exit(0);
|
|
2074
2207
|
}
|
|
2075
|
-
function checkAndPromptStar() {
|
|
2208
|
+
async function checkAndPromptStar(customStatsPath, forceAnswer) {
|
|
2076
2209
|
try {
|
|
2077
2210
|
const configDir = path4.join(os2.homedir(), ".larats");
|
|
2078
|
-
const statsFile = path4.join(configDir, "stats.json");
|
|
2079
|
-
if (!fs3.existsSync(
|
|
2080
|
-
fs3.mkdirSync(
|
|
2211
|
+
const statsFile = customStatsPath || path4.join(configDir, "stats.json");
|
|
2212
|
+
if (!fs3.existsSync(path4.dirname(statsFile))) {
|
|
2213
|
+
fs3.mkdirSync(path4.dirname(statsFile), { recursive: true });
|
|
2081
2214
|
}
|
|
2082
2215
|
let stats = {};
|
|
2083
2216
|
if (fs3.existsSync(statsFile)) {
|
|
@@ -2085,15 +2218,30 @@ function checkAndPromptStar() {
|
|
|
2085
2218
|
}
|
|
2086
2219
|
const runs = (stats.runs || 0) + 1;
|
|
2087
2220
|
stats.runs = runs;
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2221
|
+
if (runs >= 3 && !stats.hasPromptedStar) {
|
|
2222
|
+
stats.hasPromptedStar = true;
|
|
2223
|
+
const repoUrl = "https://github.com/SidiqFatuh187/larats-cli";
|
|
2224
|
+
if (process.stdin.isTTY || typeof forceAnswer === "boolean") {
|
|
2225
|
+
const shouldStar = typeof forceAnswer === "boolean" ? forceAnswer : await p.confirm({
|
|
2226
|
+
message: "\u2B50 Enjoying LaraTS? Would you like to support the project with a star on GitHub?",
|
|
2227
|
+
initialValue: true
|
|
2228
|
+
});
|
|
2229
|
+
if (shouldStar && !p.isCancel(shouldStar)) {
|
|
2230
|
+
openViewer(repoUrl);
|
|
2231
|
+
p.log.success(pc.green("Thank you so much for your support! \u2B50 Opening GitHub repository in your browser..."));
|
|
2232
|
+
} else {
|
|
2233
|
+
p.log.message(pc.gray("No problem, enjoy building with LaraTS! \u{1F44D}"));
|
|
2234
|
+
}
|
|
2235
|
+
} else {
|
|
2236
|
+
console.log(
|
|
2237
|
+
`
|
|
2092
2238
|
${pc.yellow("\u2B50 Enjoying LaraTS?")} If you find this CLI useful, please consider giving it a star on GitHub:
|
|
2093
|
-
${pc.bold(pc.cyan("\u{1F449}
|
|
2239
|
+
${pc.bold(pc.cyan("\u{1F449} " + repoUrl))}
|
|
2094
2240
|
`
|
|
2095
|
-
|
|
2241
|
+
);
|
|
2242
|
+
}
|
|
2096
2243
|
}
|
|
2244
|
+
fs3.writeFileSync(statsFile, JSON.stringify(stats, null, 2), "utf-8");
|
|
2097
2245
|
} catch {
|
|
2098
2246
|
}
|
|
2099
2247
|
}
|
|
@@ -2109,6 +2257,61 @@ function openViewer(destination) {
|
|
|
2109
2257
|
p.log.warn(`Could not open browser automatically. Please open ${pc.cyan(destination)} manually.`);
|
|
2110
2258
|
}
|
|
2111
2259
|
}
|
|
2260
|
+
async function runAi(projectInfo, options = {}) {
|
|
2261
|
+
const spinner2 = p.spinner();
|
|
2262
|
+
spinner2.start("Scanning codebase & formulating AI architecture context...");
|
|
2263
|
+
try {
|
|
2264
|
+
const scanResult = await scanProject(projectInfo);
|
|
2265
|
+
spinner2.stop(pc.green(`Architecture context ready (${scanResult.scanDurationMs}ms)`));
|
|
2266
|
+
const aiContext = generateAiContext(scanResult);
|
|
2267
|
+
let handled = false;
|
|
2268
|
+
if (options.cursor) {
|
|
2269
|
+
const cursorPath = path4.resolve(projectInfo.rootPath, ".cursorrules");
|
|
2270
|
+
const existing = fs3.existsSync(cursorPath) ? fs3.readFileSync(cursorPath, "utf-8") : "";
|
|
2271
|
+
const updated = mergeAiRules(existing, aiContext);
|
|
2272
|
+
fs3.writeFileSync(cursorPath, updated, "utf-8");
|
|
2273
|
+
p.log.success(pc.green(`\u2714 Architecture rules injected into ${pc.bold(".cursorrules")}`));
|
|
2274
|
+
handled = true;
|
|
2275
|
+
}
|
|
2276
|
+
if (options.claude) {
|
|
2277
|
+
const claudePath = path4.resolve(projectInfo.rootPath, "CLAUDE.md");
|
|
2278
|
+
const existing = fs3.existsSync(claudePath) ? fs3.readFileSync(claudePath, "utf-8") : "";
|
|
2279
|
+
const updated = mergeAiRules(existing, aiContext);
|
|
2280
|
+
fs3.writeFileSync(claudePath, updated, "utf-8");
|
|
2281
|
+
p.log.success(pc.green(`\u2714 Architecture rules injected into ${pc.bold("CLAUDE.md")}`));
|
|
2282
|
+
handled = true;
|
|
2283
|
+
}
|
|
2284
|
+
if (options.copy) {
|
|
2285
|
+
const copied = await copyToClipboard(aiContext);
|
|
2286
|
+
if (copied) {
|
|
2287
|
+
p.log.success(pc.green("\u2714 Architecture context copied to clipboard! Ready to paste into ChatGPT or Claude."));
|
|
2288
|
+
} else {
|
|
2289
|
+
p.log.warn("Could not access system clipboard. Printing prompt to console instead.");
|
|
2290
|
+
console.log(`
|
|
2291
|
+
${aiContext}
|
|
2292
|
+
`);
|
|
2293
|
+
}
|
|
2294
|
+
handled = true;
|
|
2295
|
+
}
|
|
2296
|
+
if (!handled) {
|
|
2297
|
+
console.log(`
|
|
2298
|
+
${pc.bold(pc.cyan("--- AI Codebase Architecture Prompt ---"))}
|
|
2299
|
+
${aiContext}
|
|
2300
|
+
${pc.bold(pc.cyan("---------------------------------------"))}
|
|
2301
|
+
`);
|
|
2302
|
+
p.note(
|
|
2303
|
+
`Tip: Run with ${pc.yellow("--copy")} to copy directly to clipboard,
|
|
2304
|
+
or ${pc.yellow("--cursor")} / ${pc.yellow("--claude")} to inject into rules files automatically!`,
|
|
2305
|
+
pc.green("Prompt Ready for AI \u{1F916}")
|
|
2306
|
+
);
|
|
2307
|
+
}
|
|
2308
|
+
p.outro(pc.cyan("Done!"));
|
|
2309
|
+
} catch (err) {
|
|
2310
|
+
spinner2.stop(pc.red("Failed to generate AI context"));
|
|
2311
|
+
p.log.error(err instanceof Error ? err.message : String(err));
|
|
2312
|
+
process.exitCode = 1;
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2112
2315
|
async function runScan(projectInfo, outputPath = "ARCHITECTURE.md", options = {}) {
|
|
2113
2316
|
const spinner2 = p.spinner();
|
|
2114
2317
|
spinner2.start("Scanning codebase structure & components (strict ignore list active)...");
|
|
@@ -2432,13 +2635,16 @@ async function main() {
|
|
|
2432
2635
|
tree: { type: "boolean", short: "t" },
|
|
2433
2636
|
html: { type: "boolean" },
|
|
2434
2637
|
version: { type: "boolean", short: "v" },
|
|
2638
|
+
copy: { type: "boolean" },
|
|
2639
|
+
cursor: { type: "boolean" },
|
|
2640
|
+
claude: { type: "boolean" },
|
|
2435
2641
|
help: { type: "boolean", short: "h" }
|
|
2436
2642
|
},
|
|
2437
2643
|
allowPositionals: true,
|
|
2438
2644
|
strict: true
|
|
2439
2645
|
});
|
|
2440
2646
|
} catch (err) {
|
|
2441
|
-
console.error(pc.red(`Error: ${err.message}`));
|
|
2647
|
+
console.error(pc.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
2442
2648
|
showHelp();
|
|
2443
2649
|
process.exitCode = 1;
|
|
2444
2650
|
return;
|
|
@@ -2459,6 +2665,7 @@ async function main() {
|
|
|
2459
2665
|
scaffold: true,
|
|
2460
2666
|
make: true,
|
|
2461
2667
|
info: true,
|
|
2668
|
+
ai: true,
|
|
2462
2669
|
help: true
|
|
2463
2670
|
};
|
|
2464
2671
|
if (command && !VALID_COMMANDS[command]) {
|
|
@@ -2486,7 +2693,7 @@ async function main() {
|
|
|
2486
2693
|
tree: isTreeFlag,
|
|
2487
2694
|
html: isHtmlFlag
|
|
2488
2695
|
});
|
|
2489
|
-
checkAndPromptStar();
|
|
2696
|
+
await checkAndPromptStar();
|
|
2490
2697
|
return;
|
|
2491
2698
|
}
|
|
2492
2699
|
if (command === "view" || command === "preview") {
|
|
@@ -2506,7 +2713,18 @@ Double-click ${pc.cyan("ARCHITECTURE.html")} anytime to view offline!`,
|
|
|
2506
2713
|
pc.green("Opened in Browser! \u{1F310}")
|
|
2507
2714
|
);
|
|
2508
2715
|
p.outro(pc.cyan("Done!"));
|
|
2509
|
-
checkAndPromptStar();
|
|
2716
|
+
await checkAndPromptStar();
|
|
2717
|
+
return;
|
|
2718
|
+
}
|
|
2719
|
+
if (command === "ai") {
|
|
2720
|
+
p.intro(`${pc.bgCyan(pc.black(" LaraTS "))} ${pc.gray(`v${VERSION}`)} - AI Architecture Context`);
|
|
2721
|
+
p.log.info(`Target: ${pc.green(projectInfo.summary)}`);
|
|
2722
|
+
await runAi(projectInfo, {
|
|
2723
|
+
copy: parsed.values.copy,
|
|
2724
|
+
cursor: parsed.values.cursor,
|
|
2725
|
+
claude: parsed.values.claude
|
|
2726
|
+
});
|
|
2727
|
+
await checkAndPromptStar();
|
|
2510
2728
|
return;
|
|
2511
2729
|
}
|
|
2512
2730
|
if (command === "scaffold" || command === "make") {
|
|
@@ -2519,14 +2737,14 @@ Double-click ${pc.cyan("ARCHITECTURE.html")} anytime to view offline!`,
|
|
|
2519
2737
|
force: isForceFlag,
|
|
2520
2738
|
dryRun: isDryRunFlag
|
|
2521
2739
|
});
|
|
2522
|
-
checkAndPromptStar();
|
|
2740
|
+
await checkAndPromptStar();
|
|
2523
2741
|
return;
|
|
2524
2742
|
}
|
|
2525
2743
|
if (command === "info") {
|
|
2526
2744
|
p.intro(`${pc.bgCyan(pc.black(" LaraTS "))} ${pc.gray(`v${VERSION}`)}`);
|
|
2527
2745
|
showProjectInfo(projectInfo);
|
|
2528
2746
|
p.outro(pc.cyan("Done."));
|
|
2529
|
-
checkAndPromptStar();
|
|
2747
|
+
await checkAndPromptStar();
|
|
2530
2748
|
return;
|
|
2531
2749
|
}
|
|
2532
2750
|
if (!process.stdin.isTTY) {
|
|
@@ -2544,6 +2762,11 @@ Double-click ${pc.cyan("ARCHITECTURE.html")} anytime to view offline!`,
|
|
|
2544
2762
|
label: "\u{1F50D} Scan Architecture & Generate ARCHITECTURE.md",
|
|
2545
2763
|
hint: "Creates component stats, directory tree & Mermaid flowchart"
|
|
2546
2764
|
},
|
|
2765
|
+
{
|
|
2766
|
+
value: "ai",
|
|
2767
|
+
label: "\u{1F916} Export Architecture Context for AI (Cursor, Claude, Copilot)",
|
|
2768
|
+
hint: "Generate architecture rules for AI coding agents"
|
|
2769
|
+
},
|
|
2547
2770
|
{
|
|
2548
2771
|
value: "view",
|
|
2549
2772
|
label: "\u{1F310} Open Interactive Architecture Viewer in Browser",
|
|
@@ -2576,6 +2799,24 @@ Double-click ${pc.cyan("ARCHITECTURE.html")} anytime to view offline!`,
|
|
|
2576
2799
|
tree: isTreeFlag,
|
|
2577
2800
|
html: isHtmlFlag
|
|
2578
2801
|
});
|
|
2802
|
+
} else if (action === "ai") {
|
|
2803
|
+
const aiTarget = await p.select({
|
|
2804
|
+
message: "How would you like to export the AI architecture context?",
|
|
2805
|
+
options: [
|
|
2806
|
+
{ value: "copy", label: "\u{1F4CB} Copy directly to Clipboard", hint: "Ready to paste into ChatGPT or Claude web" },
|
|
2807
|
+
{ value: "cursor", label: "\u{1F5B1}\uFE0F Save into .cursorrules", hint: "Inject rules for Cursor AI" },
|
|
2808
|
+
{ value: "claude", label: "\u{1F9E0} Save into CLAUDE.md", hint: "Inject rules for Claude Code" },
|
|
2809
|
+
{ value: "print", label: "\u{1F5A5}\uFE0F Display prompt in terminal", hint: "View formatted markdown text" }
|
|
2810
|
+
]
|
|
2811
|
+
});
|
|
2812
|
+
if (p.isCancel(aiTarget)) {
|
|
2813
|
+
handleCancel();
|
|
2814
|
+
}
|
|
2815
|
+
await runAi(projectInfo, {
|
|
2816
|
+
copy: aiTarget === "copy",
|
|
2817
|
+
cursor: aiTarget === "cursor",
|
|
2818
|
+
claude: aiTarget === "claude"
|
|
2819
|
+
});
|
|
2579
2820
|
} else if (action === "view") {
|
|
2580
2821
|
const spinner2 = p.spinner();
|
|
2581
2822
|
spinner2.start("Scanning repository & generating architecture map...");
|
|
@@ -2596,7 +2837,7 @@ Double-click ${pc.cyan("ARCHITECTURE.html")} anytime to view offline!`,
|
|
|
2596
2837
|
showProjectInfo(projectInfo);
|
|
2597
2838
|
p.outro(pc.cyan("Done!"));
|
|
2598
2839
|
}
|
|
2599
|
-
checkAndPromptStar();
|
|
2840
|
+
await checkAndPromptStar();
|
|
2600
2841
|
}
|
|
2601
2842
|
main().catch((err) => {
|
|
2602
2843
|
p.log.error(pc.red(`Fatal error: ${err.message || String(err)}`));
|