@jvittechs/jai1-cli 0.1.106 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +434 -356
- package/dist/cli.js.map +1 -1
- package/package.json +12 -12
- package/scripts/redmine-sync-issue.sh +0 -0
package/dist/cli.js
CHANGED
|
@@ -33,7 +33,7 @@ var NetworkError = class extends Jai1Error {
|
|
|
33
33
|
// package.json
|
|
34
34
|
var package_default = {
|
|
35
35
|
name: "@jvittechs/jai1-cli",
|
|
36
|
-
version: "0.1
|
|
36
|
+
version: "1.0.1",
|
|
37
37
|
description: "A unified CLI tool for JV-IT TECHS developers to manage Jai1 Framework. Please contact TeamAI for usage instructions.",
|
|
38
38
|
type: "module",
|
|
39
39
|
bin: {
|
|
@@ -480,6 +480,34 @@ var ComponentsService = class {
|
|
|
480
480
|
this.cacheDir = join2(homedir3(), ".jai1", "cache");
|
|
481
481
|
this.manifestFile = join2(projectRoot, ".jai1", "manifest.json");
|
|
482
482
|
}
|
|
483
|
+
/**
|
|
484
|
+
* Expand component paths with special prefixes
|
|
485
|
+
*
|
|
486
|
+
* Supported formats:
|
|
487
|
+
* - Standard paths: "rule-presets/react-spa-zustand", "rules/jai1.md", "workflows/commit-it.md"
|
|
488
|
+
* - Package prefix: "package:core" -> expands to all components in that package
|
|
489
|
+
*
|
|
490
|
+
* @param config - Jai1 config
|
|
491
|
+
* @param paths - Array of component paths (may include special prefixes)
|
|
492
|
+
* @returns Array of expanded component paths
|
|
493
|
+
*/
|
|
494
|
+
async expandPaths(config, paths) {
|
|
495
|
+
const expandedPaths = [];
|
|
496
|
+
for (const path13 of paths) {
|
|
497
|
+
if (path13.startsWith("package:")) {
|
|
498
|
+
const packageName = path13.substring("package:".length);
|
|
499
|
+
const components = await this.list(config);
|
|
500
|
+
if (packageName === "core") {
|
|
501
|
+
expandedPaths.push(...components.map((c) => c.filepath));
|
|
502
|
+
} else {
|
|
503
|
+
console.warn(`Warning: Unknown package '${packageName}', skipping`);
|
|
504
|
+
}
|
|
505
|
+
} else {
|
|
506
|
+
expandedPaths.push(path13);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return expandedPaths;
|
|
510
|
+
}
|
|
483
511
|
/**
|
|
484
512
|
* List components from API
|
|
485
513
|
*/
|
|
@@ -547,9 +575,6 @@ var ComponentsService = class {
|
|
|
547
575
|
if (!component.content) {
|
|
548
576
|
throw new Error(`Component ${filepath} has no content`);
|
|
549
577
|
}
|
|
550
|
-
const targetPath = join2(targetDir, filepath);
|
|
551
|
-
const targetFolder = join2(targetPath, "..");
|
|
552
|
-
await fs2.mkdir(targetFolder, { recursive: true });
|
|
553
578
|
let checksumContent = component.content;
|
|
554
579
|
if (component.contentType === "bundle" || component.contentType === "zip") {
|
|
555
580
|
let bundleJson;
|
|
@@ -594,6 +619,9 @@ var ComponentsService = class {
|
|
|
594
619
|
if (component.contentType === "markdown") {
|
|
595
620
|
checksumContent = component.content;
|
|
596
621
|
}
|
|
622
|
+
const targetPath = join2(targetDir, filepath);
|
|
623
|
+
const targetFolder = join2(targetPath, "..");
|
|
624
|
+
await fs2.mkdir(targetFolder, { recursive: true });
|
|
597
625
|
await fs2.writeFile(targetPath, component.content);
|
|
598
626
|
}
|
|
599
627
|
await this.markInstalled(filepath, component.version, this.calculateChecksum(checksumContent));
|
|
@@ -1330,6 +1358,7 @@ var UnifiedApplyApp = ({
|
|
|
1330
1358
|
const [cursorIndex, setCursorIndex] = useState(0);
|
|
1331
1359
|
const [focusArea, setFocusArea] = useState("packages");
|
|
1332
1360
|
const [selectedPackageIndex, setSelectedPackageIndex] = useState(0);
|
|
1361
|
+
const [activePackageFilter, setActivePackageFilter] = useState(null);
|
|
1333
1362
|
const [installProgress, setInstallProgress] = useState([]);
|
|
1334
1363
|
const [installStats, setInstallStats] = useState({ total: 0, completed: 0, added: 0, updated: 0, failed: 0 });
|
|
1335
1364
|
const [availableIdes, setAvailableIdes] = useState([]);
|
|
@@ -1349,6 +1378,9 @@ var UnifiedApplyApp = ({
|
|
|
1349
1378
|
setComponents(comps);
|
|
1350
1379
|
setTags(tagList);
|
|
1351
1380
|
setInstalledPaths(new Set(Object.keys(installed)));
|
|
1381
|
+
if (tagList.length > 0) {
|
|
1382
|
+
setActivePackageFilter(tagList[0].tag);
|
|
1383
|
+
}
|
|
1352
1384
|
setLoading(false);
|
|
1353
1385
|
} catch (err) {
|
|
1354
1386
|
setError(err instanceof Error ? err.message : "Failed to load");
|
|
@@ -1359,12 +1391,17 @@ var UnifiedApplyApp = ({
|
|
|
1359
1391
|
setAvailableIdes(getMigrationIDEs());
|
|
1360
1392
|
}, []);
|
|
1361
1393
|
const filteredComponents = useMemo(() => {
|
|
1362
|
-
if (
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1394
|
+
if (activePackageFilter) {
|
|
1395
|
+
return components.filter((c) => c.tags?.includes(activePackageFilter));
|
|
1396
|
+
}
|
|
1397
|
+
if (searchQuery.trim()) {
|
|
1398
|
+
const query = searchQuery.toLowerCase();
|
|
1399
|
+
return components.filter(
|
|
1400
|
+
(c) => c.filepath.toLowerCase().includes(query) || c.tags?.some((t) => t.toLowerCase().includes(query))
|
|
1401
|
+
);
|
|
1402
|
+
}
|
|
1403
|
+
return components;
|
|
1404
|
+
}, [components, searchQuery, activePackageFilter]);
|
|
1368
1405
|
useInput((input5, key) => {
|
|
1369
1406
|
if (viewState === "done") {
|
|
1370
1407
|
if (key.return || input5 === "q" || key.escape) {
|
|
@@ -1418,9 +1455,16 @@ var UnifiedApplyApp = ({
|
|
|
1418
1455
|
return;
|
|
1419
1456
|
}
|
|
1420
1457
|
if (key.tab) {
|
|
1421
|
-
if (focusArea === "packages")
|
|
1422
|
-
|
|
1423
|
-
else
|
|
1458
|
+
if (focusArea === "packages") {
|
|
1459
|
+
setFocusArea("components");
|
|
1460
|
+
} else if (focusArea === "components") {
|
|
1461
|
+
setFocusArea("search");
|
|
1462
|
+
setActivePackageFilter(null);
|
|
1463
|
+
} else {
|
|
1464
|
+
setFocusArea("packages");
|
|
1465
|
+
const tag = tags[selectedPackageIndex];
|
|
1466
|
+
if (tag) setActivePackageFilter(tag.tag);
|
|
1467
|
+
}
|
|
1424
1468
|
return;
|
|
1425
1469
|
}
|
|
1426
1470
|
if (key.escape || input5 === "q") {
|
|
@@ -1468,9 +1512,19 @@ var UnifiedApplyApp = ({
|
|
|
1468
1512
|
}
|
|
1469
1513
|
if (focusArea === "packages") {
|
|
1470
1514
|
if (key.leftArrow) {
|
|
1471
|
-
setSelectedPackageIndex((prev) =>
|
|
1515
|
+
setSelectedPackageIndex((prev) => {
|
|
1516
|
+
const newIndex = Math.max(0, prev - 1);
|
|
1517
|
+
const tag = tags[newIndex];
|
|
1518
|
+
if (tag) setActivePackageFilter(tag.tag);
|
|
1519
|
+
return newIndex;
|
|
1520
|
+
});
|
|
1472
1521
|
} else if (key.rightArrow) {
|
|
1473
|
-
setSelectedPackageIndex((prev) =>
|
|
1522
|
+
setSelectedPackageIndex((prev) => {
|
|
1523
|
+
const newIndex = Math.min(tags.length - 1, prev + 1);
|
|
1524
|
+
const tag = tags[newIndex];
|
|
1525
|
+
if (tag) setActivePackageFilter(tag.tag);
|
|
1526
|
+
return newIndex;
|
|
1527
|
+
});
|
|
1474
1528
|
} else if (input5 === " " || key.return) {
|
|
1475
1529
|
const tag = tags[selectedPackageIndex];
|
|
1476
1530
|
if (tag) {
|
|
@@ -1480,7 +1534,6 @@ var UnifiedApplyApp = ({
|
|
|
1480
1534
|
packageComponents.forEach((c) => next.add(c.filepath));
|
|
1481
1535
|
return next;
|
|
1482
1536
|
});
|
|
1483
|
-
setSearchQuery(tag.tag);
|
|
1484
1537
|
}
|
|
1485
1538
|
}
|
|
1486
1539
|
}
|
|
@@ -1601,7 +1654,7 @@ var UnifiedApplyApp = ({
|
|
|
1601
1654
|
onChange: setSearchQuery,
|
|
1602
1655
|
placeholder: "Type to filter..."
|
|
1603
1656
|
}
|
|
1604
|
-
) : /* @__PURE__ */ React3.createElement(Text3, null, searchQuery || /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "Press Tab to search"))), /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1, flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true, dimColor: true }, "Quick Apply (Packages):"), /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1, flexWrap: "wrap" }, tags.
|
|
1657
|
+
) : /* @__PURE__ */ React3.createElement(Text3, null, searchQuery || /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "Press Tab to search"))), /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1, flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true, dimColor: true }, "Quick Apply (Packages):"), /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1, flexWrap: "wrap" }, tags.map((tag, i) => {
|
|
1605
1658
|
const isSelected = focusArea === "packages" && i === selectedPackageIndex;
|
|
1606
1659
|
return /* @__PURE__ */ React3.createElement(Box2, { key: tag.tag, marginRight: 1 }, /* @__PURE__ */ React3.createElement(
|
|
1607
1660
|
Text3,
|
|
@@ -1615,12 +1668,12 @@ var UnifiedApplyApp = ({
|
|
|
1615
1668
|
tag.count,
|
|
1616
1669
|
"]"
|
|
1617
1670
|
));
|
|
1618
|
-
}))), /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", borderStyle: "round", borderColor: focusArea === "components" ? "cyan" : "gray", padding: 1 }, /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Components "), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "(", filteredComponents.length, " shown", hasMore ? `, scroll for more` : "", ")")), visibleComponents.map((comp, i) => {
|
|
1671
|
+
})), focusArea === "packages" && tags.length > 0 && /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "\u2190 \u2192 to browse packages \xB7 Space/Enter to select package"))), /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", borderStyle: "round", borderColor: focusArea === "components" ? "cyan" : "gray", padding: 1 }, /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Components "), activePackageFilter && /* @__PURE__ */ React3.createElement(Text3, { color: "cyan" }, "[", activePackageFilter, "] "), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "(", filteredComponents.length, " shown", hasMore ? `, scroll for more` : "", ")")), visibleComponents.map((comp, i) => {
|
|
1619
1672
|
const isCursor = i === cursorIndex && focusArea === "components";
|
|
1620
1673
|
const isChecked = selectedPaths.has(comp.filepath);
|
|
1621
1674
|
const isInstalled = installedPaths.has(comp.filepath);
|
|
1622
1675
|
return /* @__PURE__ */ React3.createElement(Box2, { key: comp.filepath }, /* @__PURE__ */ React3.createElement(Text3, { color: isCursor ? "cyan" : "white" }, isCursor ? "\u276F " : " ", isChecked ? "[\u2713]" : "[ ]", " ", comp.filepath), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, " ", isInstalled ? "\u2713 installed" : "\u25CB new"));
|
|
1623
|
-
}), filteredComponents.length === 0 && /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "No components match your search")), selectedPaths.size > 0 && /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Selected: ", selectedPaths.size, " components"), /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", marginLeft: 2 }, Array.from(selectedPaths).slice(0, 4).map((fp) => /* @__PURE__ */ React3.createElement(Text3, { key: fp, dimColor: true }, "\u{1F4CC} ", fp)), selectedPaths.size > 4 && /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, " ... and ", selectedPaths.size - 4, " more"))), /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "[
|
|
1676
|
+
}), filteredComponents.length === 0 && /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "No components match your search")), selectedPaths.size > 0 && /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Selected: ", selectedPaths.size, " components"), /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", marginLeft: 2 }, Array.from(selectedPaths).slice(0, 4).map((fp) => /* @__PURE__ */ React3.createElement(Text3, { key: fp, dimColor: true }, "\u{1F4CC} ", fp)), selectedPaths.size > 4 && /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, " ... and ", selectedPaths.size - 4, " more"))), /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, focusArea === "packages" && "[\u2190\u2192] Browse packages \xB7 [\u2423/Enter] Select package", focusArea === "components" && "[\u2191\u2193] Navigate \xB7 [\u2423] Toggle", focusArea === "search" && "Type to search...", " \xB7 [Tab] Switch area \xB7 [A] All \xB7 [C] Clear \xB7 [Enter] Apply \xB7 [Q] Quit")));
|
|
1624
1677
|
};
|
|
1625
1678
|
|
|
1626
1679
|
// src/commands/apply.ts
|
|
@@ -1967,7 +2020,7 @@ var MainMenuView = ({ ideContexts, onSelect }) => {
|
|
|
1967
2020
|
{
|
|
1968
2021
|
ide: "antigravity",
|
|
1969
2022
|
icon: "\u{1F680}",
|
|
1970
|
-
title: "Antigravity
|
|
2023
|
+
title: "Antigravity",
|
|
1971
2024
|
itemCount: ideContexts.find((ctx) => ctx.ide === "antigravity")?.stats.totalItems || 0,
|
|
1972
2025
|
available: ideContexts.some((ctx) => ctx.ide === "antigravity")
|
|
1973
2026
|
},
|
|
@@ -2294,7 +2347,7 @@ var IDE_CONFIGS = {
|
|
|
2294
2347
|
},
|
|
2295
2348
|
antigravity: {
|
|
2296
2349
|
id: "antigravity",
|
|
2297
|
-
name: "Antigravity
|
|
2350
|
+
name: "Antigravity",
|
|
2298
2351
|
icon: "\u{1F680}",
|
|
2299
2352
|
basePath: ".agent",
|
|
2300
2353
|
contentPaths: {
|
|
@@ -2795,6 +2848,25 @@ import { checkbox, confirm as confirm2, select } from "@inquirer/prompts";
|
|
|
2795
2848
|
import fs6 from "fs/promises";
|
|
2796
2849
|
import path3 from "path";
|
|
2797
2850
|
import { existsSync } from "fs";
|
|
2851
|
+
|
|
2852
|
+
// src/utils/prompt-theme.ts
|
|
2853
|
+
var keysHelpTipWithQuit = (keys) => {
|
|
2854
|
+
const keyStrings = keys.map(([key, action]) => `${key} ${action}`);
|
|
2855
|
+
keyStrings.push("Ctrl+C quit");
|
|
2856
|
+
return keyStrings.join(" \u2022 ");
|
|
2857
|
+
};
|
|
2858
|
+
var checkboxTheme = {
|
|
2859
|
+
style: {
|
|
2860
|
+
keysHelpTip: keysHelpTipWithQuit
|
|
2861
|
+
}
|
|
2862
|
+
};
|
|
2863
|
+
var selectTheme = {
|
|
2864
|
+
style: {
|
|
2865
|
+
keysHelpTip: keysHelpTipWithQuit
|
|
2866
|
+
}
|
|
2867
|
+
};
|
|
2868
|
+
|
|
2869
|
+
// src/commands/ide/setup.ts
|
|
2798
2870
|
var PERFORMANCE_GROUPS = {
|
|
2799
2871
|
telemetry: {
|
|
2800
2872
|
name: "Telemetry",
|
|
@@ -2981,7 +3053,8 @@ async function interactiveMode() {
|
|
|
2981
3053
|
{ name: "\u274C Disable optimization groups", value: "disable" },
|
|
2982
3054
|
{ name: "\u{1F680} Max Performance (enable all)", value: "max" },
|
|
2983
3055
|
{ name: "\u{1F504} Reset to defaults", value: "reset" }
|
|
2984
|
-
]
|
|
3056
|
+
],
|
|
3057
|
+
theme: selectTheme
|
|
2985
3058
|
});
|
|
2986
3059
|
if (action === "max") {
|
|
2987
3060
|
const allGroups = Object.keys(PERFORMANCE_GROUPS);
|
|
@@ -3000,7 +3073,8 @@ async function selectGroupsToApply(action) {
|
|
|
3000
3073
|
try {
|
|
3001
3074
|
const selectedGroups = await checkbox({
|
|
3002
3075
|
message: `Select groups to ${action} (SPACE to select, ENTER to confirm):`,
|
|
3003
|
-
choices
|
|
3076
|
+
choices,
|
|
3077
|
+
theme: checkboxTheme
|
|
3004
3078
|
});
|
|
3005
3079
|
if (selectedGroups.length === 0) {
|
|
3006
3080
|
console.log("\n\u26A0\uFE0F No groups selected!");
|
|
@@ -3136,7 +3210,8 @@ async function runSync(options) {
|
|
|
3136
3210
|
});
|
|
3137
3211
|
selectedIdes = await checkbox2({
|
|
3138
3212
|
message: "Select target IDE(s) (SPACE to select, ENTER to confirm):",
|
|
3139
|
-
choices: ideChoices
|
|
3213
|
+
choices: ideChoices,
|
|
3214
|
+
theme: checkboxTheme
|
|
3140
3215
|
});
|
|
3141
3216
|
if (selectedIdes.length === 0) {
|
|
3142
3217
|
console.log("\n\u26A0\uFE0F No IDE selected!");
|
|
@@ -3155,7 +3230,8 @@ async function runSync(options) {
|
|
|
3155
3230
|
];
|
|
3156
3231
|
selectedTypes = await checkbox2({
|
|
3157
3232
|
message: "Select content types to sync:",
|
|
3158
|
-
choices: typeChoices
|
|
3233
|
+
choices: typeChoices,
|
|
3234
|
+
theme: checkboxTheme
|
|
3159
3235
|
});
|
|
3160
3236
|
if (selectedTypes.length === 0) {
|
|
3161
3237
|
console.log("\n\u26A0\uFE0F No content type selected!");
|
|
@@ -3281,78 +3357,6 @@ var IDE_FORMATS = {
|
|
|
3281
3357
|
// Gemini requires AGENTS.md
|
|
3282
3358
|
}
|
|
3283
3359
|
};
|
|
3284
|
-
var JAI1_BASE_RULE = `# Jai1 Framework Agent
|
|
3285
|
-
|
|
3286
|
-
You are an AI coding assistant integrated with the Jai1 Framework.
|
|
3287
|
-
|
|
3288
|
-
## Skills Reference
|
|
3289
|
-
|
|
3290
|
-
The Jai1 Framework provides specialized skills that you can reference and apply:
|
|
3291
|
-
|
|
3292
|
-
### Pattern Detection
|
|
3293
|
-
|
|
3294
|
-
When you encounter:
|
|
3295
|
-
- **Component creation**: Reference \`@jai1-component-patterns\`
|
|
3296
|
-
- **API development**: Reference \`@jai1-api-patterns\`
|
|
3297
|
-
- **Database operations**: Reference \`@jai1-database-patterns\`
|
|
3298
|
-
- **Testing**: Reference \`@jai1-testing-patterns\`
|
|
3299
|
-
- **Deployment**: Reference \`@jai1-deployment-patterns\`
|
|
3300
|
-
|
|
3301
|
-
### Skill Loading Procedure
|
|
3302
|
-
|
|
3303
|
-
1. **Detect the task type** from user request
|
|
3304
|
-
2. **Load relevant skill** using \`@jai1-[skill-name]\` syntax
|
|
3305
|
-
3. **Apply patterns** from the skill to your response
|
|
3306
|
-
4. **Follow conventions** defined in the skill
|
|
3307
|
-
|
|
3308
|
-
Example:
|
|
3309
|
-
\`\`\`
|
|
3310
|
-
User: "Create a new API endpoint for users"
|
|
3311
|
-
Assistant:
|
|
3312
|
-
1. Loading @jai1-api-patterns
|
|
3313
|
-
2. Applying RESTful conventions
|
|
3314
|
-
3. Following project structure from @jai1-component-patterns
|
|
3315
|
-
4. Implementing validation using @jai1-testing-patterns
|
|
3316
|
-
\`\`\`
|
|
3317
|
-
|
|
3318
|
-
### Application
|
|
3319
|
-
|
|
3320
|
-
- **Always reference skills** before implementing patterns
|
|
3321
|
-
- **Follow naming conventions** from loaded skills
|
|
3322
|
-
- **Apply best practices** defined in skills
|
|
3323
|
-
- **Maintain consistency** with existing codebase patterns
|
|
3324
|
-
|
|
3325
|
-
### Naming Convention
|
|
3326
|
-
|
|
3327
|
-
Skills are named using the pattern: \`@jai1-[domain]-patterns\`
|
|
3328
|
-
|
|
3329
|
-
Examples:
|
|
3330
|
-
- \`@jai1-component-patterns\`
|
|
3331
|
-
- \`@jai1-api-patterns\`
|
|
3332
|
-
- \`@jai1-database-patterns\`
|
|
3333
|
-
- \`@jai1-testing-patterns\`
|
|
3334
|
-
- \`@jai1-deployment-patterns\`
|
|
3335
|
-
|
|
3336
|
-
## Best Practices
|
|
3337
|
-
|
|
3338
|
-
1. **Load before applying**: Always reference the appropriate skill before implementing
|
|
3339
|
-
2. **Follow project structure**: Use patterns from \`@jai1-component-patterns\`
|
|
3340
|
-
3. **Maintain consistency**: Apply conventions consistently across the codebase
|
|
3341
|
-
4. **Document decisions**: Explain why specific patterns are used
|
|
3342
|
-
|
|
3343
|
-
## Integration
|
|
3344
|
-
|
|
3345
|
-
This base rule works in conjunction with:
|
|
3346
|
-
- Project-specific rules (e.g., 01-project.md)
|
|
3347
|
-
- Technology-specific rules (e.g., 03-frontend.md)
|
|
3348
|
-
- Custom rules (e.g., 09-custom.md)
|
|
3349
|
-
- AGENTS.md (if present)
|
|
3350
|
-
|
|
3351
|
-
When conflicts arise, prioritize:
|
|
3352
|
-
1. Custom rules (09-custom.*)
|
|
3353
|
-
2. Project rules (01-project.*)
|
|
3354
|
-
3. This base rule (00-jai1.*)
|
|
3355
|
-
`;
|
|
3356
3360
|
|
|
3357
3361
|
// src/services/ide-detection.service.ts
|
|
3358
3362
|
var IdeDetectionService = class {
|
|
@@ -7830,7 +7834,8 @@ async function handleInteractiveFeedback(config) {
|
|
|
7830
7834
|
name: "\u{1F4DD} Suggestion - Share ideas or improvements",
|
|
7831
7835
|
value: "suggestion"
|
|
7832
7836
|
}
|
|
7833
|
-
]
|
|
7837
|
+
],
|
|
7838
|
+
theme: selectTheme
|
|
7834
7839
|
});
|
|
7835
7840
|
const title = await input({
|
|
7836
7841
|
message: "Title (max 200 characters):",
|
|
@@ -11145,7 +11150,7 @@ ${"\u2501".repeat(80)}`));
|
|
|
11145
11150
|
} else {
|
|
11146
11151
|
try {
|
|
11147
11152
|
const selected = await checkbox3({
|
|
11148
|
-
message: "Ch\u1ECDn packages \u0111\u1EC3 upgrade (\u2191\u2193 di chuy\u1EC3n \u2022 space ch\u1ECDn \u2022 a t\u1EA5t c\u1EA3 \u2022 i \u0111\u1EA3o \u2022 \u23CE x\xE1c nh\u1EADn
|
|
11153
|
+
message: "Ch\u1ECDn packages \u0111\u1EC3 upgrade (\u2191\u2193 di chuy\u1EC3n \u2022 space ch\u1ECDn \u2022 a t\u1EA5t c\u1EA3 \u2022 i \u0111\u1EA3o \u2022 \u23CE x\xE1c nh\u1EADn):",
|
|
11149
11154
|
choices: packages.map((pkg) => {
|
|
11150
11155
|
const icon = pkg.upgradeType === "major" ? "\u{1F534}" : pkg.upgradeType === "minor" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
11151
11156
|
return {
|
|
@@ -11154,7 +11159,8 @@ ${"\u2501".repeat(80)}`));
|
|
|
11154
11159
|
checked: true
|
|
11155
11160
|
};
|
|
11156
11161
|
}),
|
|
11157
|
-
pageSize: 15
|
|
11162
|
+
pageSize: 15,
|
|
11163
|
+
theme: checkboxTheme
|
|
11158
11164
|
});
|
|
11159
11165
|
if (selected.length === 0) {
|
|
11160
11166
|
console.log(chalk12.yellow("\u23F8\uFE0F Kh\xF4ng c\xF3 packages n\xE0o \u0111\u01B0\u1EE3c ch\u1ECDn\n"));
|
|
@@ -11590,7 +11596,8 @@ function createKitCreateCommand() {
|
|
|
11590
11596
|
const answer = await select3({
|
|
11591
11597
|
message: v.prompt,
|
|
11592
11598
|
choices: v.options.map((opt) => ({ name: opt, value: opt })),
|
|
11593
|
-
default: v.default
|
|
11599
|
+
default: v.default,
|
|
11600
|
+
theme: selectTheme
|
|
11594
11601
|
});
|
|
11595
11602
|
variables[v.name] = answer;
|
|
11596
11603
|
} else {
|
|
@@ -11609,9 +11616,13 @@ function createKitCreateCommand() {
|
|
|
11609
11616
|
if (!options.skipFramework && kit.config.framework?.apply) {
|
|
11610
11617
|
console.log("\u{1F916} Applying jai1 framework...");
|
|
11611
11618
|
const componentsService = new ComponentsService(targetDir);
|
|
11612
|
-
|
|
11619
|
+
const expandedPaths = await componentsService.expandPaths(
|
|
11620
|
+
config,
|
|
11621
|
+
kit.config.framework.components
|
|
11622
|
+
);
|
|
11623
|
+
for (const filepath of expandedPaths) {
|
|
11613
11624
|
console.log(` \u{1F4E5} Installing ${filepath}...`);
|
|
11614
|
-
await componentsService.install(config, filepath);
|
|
11625
|
+
await componentsService.install(config, filepath, join8(targetDir, ".jai1"));
|
|
11615
11626
|
}
|
|
11616
11627
|
console.log(" \u2713 Framework components applied");
|
|
11617
11628
|
}
|
|
@@ -11649,7 +11660,8 @@ function createKitCreateCommand() {
|
|
|
11649
11660
|
choices: ideChoices.map((c) => ({
|
|
11650
11661
|
...c,
|
|
11651
11662
|
checked: defaultTargets.includes(c.value)
|
|
11652
|
-
}))
|
|
11663
|
+
})),
|
|
11664
|
+
theme: checkboxTheme
|
|
11653
11665
|
});
|
|
11654
11666
|
selectedIdes = answer;
|
|
11655
11667
|
}
|
|
@@ -11793,7 +11805,7 @@ function createRulesListCommand() {
|
|
|
11793
11805
|
console.log(table.toString());
|
|
11794
11806
|
console.log();
|
|
11795
11807
|
}
|
|
11796
|
-
console.log(chalk16.dim('\u{1F4A1} Ch\u1EA1y "jai1 rules
|
|
11808
|
+
console.log(chalk16.dim('\u{1F4A1} Ch\u1EA1y "jai1 rules apply <name>" \u0111\u1EC3 \xE1p d\u1EE5ng preset'));
|
|
11797
11809
|
} catch (error) {
|
|
11798
11810
|
throw new Error(
|
|
11799
11811
|
`L\u1ED7i khi t\u1EA3i presets: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -11804,9 +11816,129 @@ function createRulesListCommand() {
|
|
|
11804
11816
|
|
|
11805
11817
|
// src/commands/rules/init.ts
|
|
11806
11818
|
import { Command as Command44 } from "commander";
|
|
11819
|
+
import { promises as fs20 } from "fs";
|
|
11820
|
+
import { join as join10 } from "path";
|
|
11821
|
+
import { select as select4, confirm as confirm7 } from "@inquirer/prompts";
|
|
11822
|
+
|
|
11823
|
+
// src/services/project-config.service.ts
|
|
11807
11824
|
import { promises as fs19 } from "fs";
|
|
11808
11825
|
import { join as join9 } from "path";
|
|
11809
|
-
|
|
11826
|
+
var ProjectConfigService = class {
|
|
11827
|
+
projectRoot;
|
|
11828
|
+
configDir;
|
|
11829
|
+
configPath;
|
|
11830
|
+
constructor(projectRoot = process.cwd()) {
|
|
11831
|
+
this.projectRoot = projectRoot;
|
|
11832
|
+
this.configDir = join9(this.projectRoot, ".jai1");
|
|
11833
|
+
this.configPath = join9(this.configDir, "project.json");
|
|
11834
|
+
}
|
|
11835
|
+
/**
|
|
11836
|
+
* Check if config file exists
|
|
11837
|
+
*/
|
|
11838
|
+
async exists() {
|
|
11839
|
+
try {
|
|
11840
|
+
await fs19.access(this.configPath);
|
|
11841
|
+
return true;
|
|
11842
|
+
} catch {
|
|
11843
|
+
return false;
|
|
11844
|
+
}
|
|
11845
|
+
}
|
|
11846
|
+
/**
|
|
11847
|
+
* Load full project configuration
|
|
11848
|
+
* @returns Config object or null if not found
|
|
11849
|
+
*/
|
|
11850
|
+
async load() {
|
|
11851
|
+
if (!await this.exists()) {
|
|
11852
|
+
return null;
|
|
11853
|
+
}
|
|
11854
|
+
try {
|
|
11855
|
+
const content = await fs19.readFile(this.configPath, "utf-8");
|
|
11856
|
+
return JSON.parse(content);
|
|
11857
|
+
} catch (error) {
|
|
11858
|
+
throw new Error(
|
|
11859
|
+
`Failed to load project config: ${error instanceof Error ? error.message : String(error)}`
|
|
11860
|
+
);
|
|
11861
|
+
}
|
|
11862
|
+
}
|
|
11863
|
+
/**
|
|
11864
|
+
* Save full project configuration
|
|
11865
|
+
* Creates .jai1 directory if it doesn't exist
|
|
11866
|
+
*/
|
|
11867
|
+
async save(config) {
|
|
11868
|
+
try {
|
|
11869
|
+
await fs19.mkdir(this.configDir, { recursive: true });
|
|
11870
|
+
await fs19.writeFile(this.configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
11871
|
+
} catch (error) {
|
|
11872
|
+
throw new Error(
|
|
11873
|
+
`Failed to save project config: ${error instanceof Error ? error.message : String(error)}`
|
|
11874
|
+
);
|
|
11875
|
+
}
|
|
11876
|
+
}
|
|
11877
|
+
/**
|
|
11878
|
+
* Load rules configuration only
|
|
11879
|
+
* @returns RulesConfig or null if not found
|
|
11880
|
+
*/
|
|
11881
|
+
async loadRules() {
|
|
11882
|
+
const config = await this.load();
|
|
11883
|
+
return config?.rules ?? null;
|
|
11884
|
+
}
|
|
11885
|
+
/**
|
|
11886
|
+
* Save rules configuration
|
|
11887
|
+
* Merges with existing config, preserving other sections
|
|
11888
|
+
*/
|
|
11889
|
+
async saveRules(rulesConfig) {
|
|
11890
|
+
const existingConfig = await this.load() ?? {};
|
|
11891
|
+
existingConfig.rules = rulesConfig;
|
|
11892
|
+
await this.save(existingConfig);
|
|
11893
|
+
}
|
|
11894
|
+
/**
|
|
11895
|
+
* Update rules configuration partially
|
|
11896
|
+
* Merges with existing rules config
|
|
11897
|
+
*/
|
|
11898
|
+
async updateRules(partialRulesConfig) {
|
|
11899
|
+
const existingRules = await this.loadRules();
|
|
11900
|
+
if (!existingRules) {
|
|
11901
|
+
throw new Error('No rules configuration found. Run "jai1 rules apply" first.');
|
|
11902
|
+
}
|
|
11903
|
+
const updatedRules = {
|
|
11904
|
+
...existingRules,
|
|
11905
|
+
...partialRulesConfig
|
|
11906
|
+
};
|
|
11907
|
+
await this.saveRules(updatedRules);
|
|
11908
|
+
}
|
|
11909
|
+
/**
|
|
11910
|
+
* Add a backup entry to rules config
|
|
11911
|
+
* Keeps only the last 5 backups
|
|
11912
|
+
*/
|
|
11913
|
+
async addBackup(backup) {
|
|
11914
|
+
const rules = await this.loadRules();
|
|
11915
|
+
if (!rules) {
|
|
11916
|
+
throw new Error("No rules configuration found.");
|
|
11917
|
+
}
|
|
11918
|
+
const backups = [backup, ...rules.backups ?? []].slice(0, 5);
|
|
11919
|
+
await this.updateRules({ backups });
|
|
11920
|
+
}
|
|
11921
|
+
/**
|
|
11922
|
+
* Get config file path
|
|
11923
|
+
*/
|
|
11924
|
+
getConfigPath() {
|
|
11925
|
+
return this.configPath;
|
|
11926
|
+
}
|
|
11927
|
+
/**
|
|
11928
|
+
* Get config directory path (.jai1/)
|
|
11929
|
+
*/
|
|
11930
|
+
getConfigDir() {
|
|
11931
|
+
return this.configDir;
|
|
11932
|
+
}
|
|
11933
|
+
/**
|
|
11934
|
+
* Get project root path
|
|
11935
|
+
*/
|
|
11936
|
+
getProjectRoot() {
|
|
11937
|
+
return this.projectRoot;
|
|
11938
|
+
}
|
|
11939
|
+
};
|
|
11940
|
+
|
|
11941
|
+
// src/commands/rules/init.ts
|
|
11810
11942
|
function createRulesInitCommand() {
|
|
11811
11943
|
return new Command44("init").description("Apply rule preset to project").option("--preset <slug>", "Preset slug to apply").option("--output <format>", "Output format: cursor, agents-md, both (default: cursor)", "cursor").option("-y, --yes", "Skip confirmations").action(async (options) => {
|
|
11812
11944
|
const configService = new ConfigService();
|
|
@@ -11836,7 +11968,8 @@ function createRulesInitCommand() {
|
|
|
11836
11968
|
name: `${p.name} - ${p.description}`,
|
|
11837
11969
|
value: p.slug,
|
|
11838
11970
|
description: `v${p.version} | ${p.tags.join(", ")}`
|
|
11839
|
-
}))
|
|
11971
|
+
})),
|
|
11972
|
+
theme: selectTheme
|
|
11840
11973
|
});
|
|
11841
11974
|
}
|
|
11842
11975
|
console.log(`
|
|
@@ -11864,7 +11997,8 @@ function createRulesInitCommand() {
|
|
|
11864
11997
|
{ name: "Cursor (.cursor/rules/)", value: "cursor" },
|
|
11865
11998
|
{ name: "AGENTS.md (single file)", value: "agents-md" },
|
|
11866
11999
|
{ name: "Both", value: "both" }
|
|
11867
|
-
]
|
|
12000
|
+
],
|
|
12001
|
+
theme: selectTheme
|
|
11868
12002
|
});
|
|
11869
12003
|
}
|
|
11870
12004
|
if (!options.yes) {
|
|
@@ -11884,14 +12018,16 @@ function createRulesInitCommand() {
|
|
|
11884
12018
|
if (outputFormat === "agents-md" || outputFormat === "both") {
|
|
11885
12019
|
await applyAgentsMdFormat(bundle);
|
|
11886
12020
|
}
|
|
11887
|
-
const
|
|
12021
|
+
const projectConfigService = new ProjectConfigService();
|
|
12022
|
+
const rulesConfig = {
|
|
11888
12023
|
preset: bundle.preset.slug,
|
|
11889
12024
|
version: bundle.preset.version,
|
|
11890
12025
|
appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12026
|
+
ides: [],
|
|
11891
12027
|
customContext: "09-custom.mdc"
|
|
11892
12028
|
};
|
|
11893
|
-
await
|
|
11894
|
-
console.log("\u2713 Created jai1
|
|
12029
|
+
await projectConfigService.saveRules(rulesConfig);
|
|
12030
|
+
console.log("\u2713 Created .jai1/project.json");
|
|
11895
12031
|
console.log("\n\u2705 Preset applied successfully!\n");
|
|
11896
12032
|
console.log("Next steps:");
|
|
11897
12033
|
console.log(" 1. Edit 09-custom.mdc to add project-specific rules");
|
|
@@ -11900,11 +12036,11 @@ function createRulesInitCommand() {
|
|
|
11900
12036
|
});
|
|
11901
12037
|
}
|
|
11902
12038
|
async function applyCursorFormat(bundle) {
|
|
11903
|
-
const rulesDir =
|
|
11904
|
-
await
|
|
12039
|
+
const rulesDir = join10(process.cwd(), ".cursor", "rules");
|
|
12040
|
+
await fs20.mkdir(rulesDir, { recursive: true });
|
|
11905
12041
|
for (const [filename, content] of Object.entries(bundle.files)) {
|
|
11906
|
-
const filePath =
|
|
11907
|
-
await
|
|
12042
|
+
const filePath = join10(rulesDir, filename);
|
|
12043
|
+
await fs20.writeFile(filePath, content, "utf-8");
|
|
11908
12044
|
console.log(`\u2713 Created .cursor/rules/${filename}`);
|
|
11909
12045
|
}
|
|
11910
12046
|
}
|
|
@@ -11931,14 +12067,14 @@ async function applyAgentsMdFormat(bundle) {
|
|
|
11931
12067
|
}
|
|
11932
12068
|
}
|
|
11933
12069
|
const agentsMd = sections.join("\n");
|
|
11934
|
-
await
|
|
12070
|
+
await fs20.writeFile("AGENTS.md", agentsMd, "utf-8");
|
|
11935
12071
|
console.log("\u2713 Created AGENTS.md");
|
|
11936
12072
|
}
|
|
11937
12073
|
|
|
11938
12074
|
// src/commands/rules/apply.ts
|
|
11939
12075
|
import { Command as Command45 } from "commander";
|
|
11940
|
-
import { promises as
|
|
11941
|
-
import { join as
|
|
12076
|
+
import { promises as fs22 } from "fs";
|
|
12077
|
+
import { join as join12 } from "path";
|
|
11942
12078
|
import { select as select5, confirm as confirm8, checkbox as checkbox5 } from "@inquirer/prompts";
|
|
11943
12079
|
|
|
11944
12080
|
// src/services/rules-generator.service.ts
|
|
@@ -11973,11 +12109,6 @@ var RulesGeneratorService = class {
|
|
|
11973
12109
|
*/
|
|
11974
12110
|
generateCursorFiles(bundle, format) {
|
|
11975
12111
|
const files = [];
|
|
11976
|
-
files.push({
|
|
11977
|
-
path: `${format.rulesPath}/00-jai1${format.fileExtension}`,
|
|
11978
|
-
content: this.generateCursorJai1Rule(),
|
|
11979
|
-
description: "Jai1 Framework base rule"
|
|
11980
|
-
});
|
|
11981
12112
|
for (const [filename, content] of Object.entries(bundle.files)) {
|
|
11982
12113
|
files.push({
|
|
11983
12114
|
path: `${format.rulesPath}/${filename}`,
|
|
@@ -11988,28 +12119,11 @@ var RulesGeneratorService = class {
|
|
|
11988
12119
|
}
|
|
11989
12120
|
return files;
|
|
11990
12121
|
}
|
|
11991
|
-
/**
|
|
11992
|
-
* Generate Cursor jai1 base rule
|
|
11993
|
-
*/
|
|
11994
|
-
generateCursorJai1Rule() {
|
|
11995
|
-
return `---
|
|
11996
|
-
description: Jai1 Framework integration and skill loading
|
|
11997
|
-
globs: ["**/*"]
|
|
11998
|
-
alwaysApply: true
|
|
11999
|
-
---
|
|
12000
|
-
|
|
12001
|
-
${JAI1_BASE_RULE}`;
|
|
12002
|
-
}
|
|
12003
12122
|
/**
|
|
12004
12123
|
* Generate Windsurf format (.md files with trigger metadata)
|
|
12005
12124
|
*/
|
|
12006
12125
|
generateWindsurfFiles(bundle, format) {
|
|
12007
12126
|
const files = [];
|
|
12008
|
-
files.push({
|
|
12009
|
-
path: `${format.rulesPath}/00-jai1${format.fileExtension}`,
|
|
12010
|
-
content: this.generateWindsurfJai1Rule(),
|
|
12011
|
-
description: "Jai1 Framework base rule"
|
|
12012
|
-
});
|
|
12013
12127
|
for (const [filename, content] of Object.entries(bundle.files)) {
|
|
12014
12128
|
const converted = this.convertToWindsurf(content, filename);
|
|
12015
12129
|
const newFilename = filename.replace(/\.mdc$/, ".md");
|
|
@@ -12044,27 +12158,12 @@ trigger: ${trigger}
|
|
|
12044
12158
|
|
|
12045
12159
|
${body.trim()}
|
|
12046
12160
|
`;
|
|
12047
|
-
}
|
|
12048
|
-
/**
|
|
12049
|
-
* Generate Windsurf jai1 base rule
|
|
12050
|
-
*/
|
|
12051
|
-
generateWindsurfJai1Rule() {
|
|
12052
|
-
return `---
|
|
12053
|
-
trigger: always
|
|
12054
|
-
---
|
|
12055
|
-
|
|
12056
|
-
${JAI1_BASE_RULE}`;
|
|
12057
12161
|
}
|
|
12058
12162
|
/**
|
|
12059
12163
|
* Generate Antigravity format (.md files with trigger metadata + @AGENTS.md reference)
|
|
12060
12164
|
*/
|
|
12061
12165
|
generateAntigravityFiles(bundle, format) {
|
|
12062
12166
|
const files = [];
|
|
12063
|
-
files.push({
|
|
12064
|
-
path: `${format.rulesPath}/00-jai1${format.fileExtension}`,
|
|
12065
|
-
content: this.generateAntigravityJai1Rule(),
|
|
12066
|
-
description: "Jai1 Framework base rule"
|
|
12067
|
-
});
|
|
12068
12167
|
for (const [filename, content] of Object.entries(bundle.files)) {
|
|
12069
12168
|
const converted = this.convertToAntigravity(content, filename);
|
|
12070
12169
|
const newFilename = filename.replace(/\.mdc$/, ".md");
|
|
@@ -12099,29 +12198,12 @@ trigger: ${trigger}
|
|
|
12099
12198
|
|
|
12100
12199
|
${body.trim()}
|
|
12101
12200
|
`;
|
|
12102
|
-
}
|
|
12103
|
-
/**
|
|
12104
|
-
* Generate Antigravity jai1 base rule
|
|
12105
|
-
*/
|
|
12106
|
-
generateAntigravityJai1Rule() {
|
|
12107
|
-
return `---
|
|
12108
|
-
trigger: always_on
|
|
12109
|
-
---
|
|
12110
|
-
|
|
12111
|
-
@AGENTS.md
|
|
12112
|
-
|
|
12113
|
-
${JAI1_BASE_RULE}`;
|
|
12114
12201
|
}
|
|
12115
12202
|
/**
|
|
12116
12203
|
* Generate Claude Code format (.md files with paths metadata)
|
|
12117
12204
|
*/
|
|
12118
12205
|
generateClaudeFiles(bundle, format) {
|
|
12119
12206
|
const files = [];
|
|
12120
|
-
files.push({
|
|
12121
|
-
path: `${format.rulesPath}/00-jai1${format.fileExtension}`,
|
|
12122
|
-
content: this.generateClaudeJai1Rule(),
|
|
12123
|
-
description: "Jai1 Framework base rule"
|
|
12124
|
-
});
|
|
12125
12207
|
for (const [filename, content] of Object.entries(bundle.files)) {
|
|
12126
12208
|
const converted = this.convertToClaude(content, filename);
|
|
12127
12209
|
const newFilename = filename.replace(/\.mdc$/, ".md");
|
|
@@ -12155,17 +12237,6 @@ paths: ${paths}`;
|
|
|
12155
12237
|
|
|
12156
12238
|
${body.trim()}
|
|
12157
12239
|
`;
|
|
12158
|
-
}
|
|
12159
|
-
/**
|
|
12160
|
-
* Generate Claude jai1 base rule
|
|
12161
|
-
*/
|
|
12162
|
-
generateClaudeJai1Rule() {
|
|
12163
|
-
return `---
|
|
12164
|
-
description: Jai1 Framework integration and skill loading
|
|
12165
|
-
paths: **/*
|
|
12166
|
-
---
|
|
12167
|
-
|
|
12168
|
-
${JAI1_BASE_RULE}`;
|
|
12169
12240
|
}
|
|
12170
12241
|
/**
|
|
12171
12242
|
* Generate AGENTS.md file (single merged file)
|
|
@@ -12175,10 +12246,8 @@ ${JAI1_BASE_RULE}`;
|
|
|
12175
12246
|
sections.push("# AGENTS.md\n");
|
|
12176
12247
|
sections.push(`<!-- Generated by jai1 rules - Preset: ${bundle.preset.slug} v${bundle.preset.version} -->
|
|
12177
12248
|
`);
|
|
12178
|
-
sections.push("## Jai1 Framework Integration\n");
|
|
12179
|
-
sections.push(JAI1_BASE_RULE.trim());
|
|
12180
|
-
sections.push("\n---\n");
|
|
12181
12249
|
const fileOrder = [
|
|
12250
|
+
"00-jai1.mdc",
|
|
12182
12251
|
"01-project.mdc",
|
|
12183
12252
|
"02-standards.mdc",
|
|
12184
12253
|
"03-frontend.mdc",
|
|
@@ -12298,8 +12367,8 @@ Follow all instructions and patterns defined in AGENTS.md above.
|
|
|
12298
12367
|
};
|
|
12299
12368
|
|
|
12300
12369
|
// src/services/backup.service.ts
|
|
12301
|
-
import { promises as
|
|
12302
|
-
import { join as
|
|
12370
|
+
import { promises as fs21 } from "fs";
|
|
12371
|
+
import { join as join11, dirname } from "path";
|
|
12303
12372
|
var BackupService = class {
|
|
12304
12373
|
backupDir = ".jai1/backups";
|
|
12305
12374
|
/**
|
|
@@ -12307,7 +12376,7 @@ var BackupService = class {
|
|
|
12307
12376
|
*/
|
|
12308
12377
|
async createBackup(ides, presetSlug) {
|
|
12309
12378
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
12310
|
-
const backupPath =
|
|
12379
|
+
const backupPath = join11(this.backupDir, timestamp);
|
|
12311
12380
|
const backedUpFiles = [];
|
|
12312
12381
|
let hasContent = false;
|
|
12313
12382
|
for (const ideId of ides) {
|
|
@@ -12316,7 +12385,7 @@ var BackupService = class {
|
|
|
12316
12385
|
console.warn(`Unknown IDE format: ${ideId}, skipping backup`);
|
|
12317
12386
|
continue;
|
|
12318
12387
|
}
|
|
12319
|
-
const rulesPath = format.rulesPath === "." ? process.cwd() :
|
|
12388
|
+
const rulesPath = format.rulesPath === "." ? process.cwd() : join11(process.cwd(), format.rulesPath);
|
|
12320
12389
|
try {
|
|
12321
12390
|
const exists = await this.pathExists(rulesPath);
|
|
12322
12391
|
if (!exists) {
|
|
@@ -12329,19 +12398,19 @@ var BackupService = class {
|
|
|
12329
12398
|
await this.backupSingleFile("GEMINI.md", backupPath, ideId, backedUpFiles);
|
|
12330
12399
|
hasContent = true;
|
|
12331
12400
|
} else {
|
|
12332
|
-
const stats = await
|
|
12401
|
+
const stats = await fs21.stat(rulesPath);
|
|
12333
12402
|
if (stats.isDirectory()) {
|
|
12334
|
-
const files = await
|
|
12403
|
+
const files = await fs21.readdir(rulesPath);
|
|
12335
12404
|
for (const file of files) {
|
|
12336
12405
|
if (file.endsWith(format.fileExtension)) {
|
|
12337
|
-
const originalPath =
|
|
12338
|
-
const relativePath =
|
|
12339
|
-
const destPath =
|
|
12340
|
-
await
|
|
12341
|
-
await
|
|
12406
|
+
const originalPath = join11(rulesPath, file);
|
|
12407
|
+
const relativePath = join11(format.rulesPath, file);
|
|
12408
|
+
const destPath = join11(backupPath, ideId, file);
|
|
12409
|
+
await fs21.mkdir(dirname(destPath), { recursive: true });
|
|
12410
|
+
await fs21.copyFile(originalPath, destPath);
|
|
12342
12411
|
backedUpFiles.push({
|
|
12343
12412
|
originalPath: relativePath,
|
|
12344
|
-
backupPath:
|
|
12413
|
+
backupPath: join11(ideId, file),
|
|
12345
12414
|
ide: ideId
|
|
12346
12415
|
});
|
|
12347
12416
|
hasContent = true;
|
|
@@ -12362,9 +12431,9 @@ var BackupService = class {
|
|
|
12362
12431
|
ides,
|
|
12363
12432
|
files: backedUpFiles
|
|
12364
12433
|
};
|
|
12365
|
-
await
|
|
12366
|
-
await
|
|
12367
|
-
|
|
12434
|
+
await fs21.mkdir(backupPath, { recursive: true });
|
|
12435
|
+
await fs21.writeFile(
|
|
12436
|
+
join11(backupPath, "metadata.json"),
|
|
12368
12437
|
JSON.stringify(metadata, null, 2),
|
|
12369
12438
|
"utf-8"
|
|
12370
12439
|
);
|
|
@@ -12374,18 +12443,18 @@ var BackupService = class {
|
|
|
12374
12443
|
* Backup a single file (for AGENTS.md, GEMINI.md)
|
|
12375
12444
|
*/
|
|
12376
12445
|
async backupSingleFile(filename, backupPath, ideId, backedUpFiles) {
|
|
12377
|
-
const originalPath =
|
|
12446
|
+
const originalPath = join11(process.cwd(), filename);
|
|
12378
12447
|
try {
|
|
12379
12448
|
const exists = await this.pathExists(originalPath);
|
|
12380
12449
|
if (!exists) {
|
|
12381
12450
|
return;
|
|
12382
12451
|
}
|
|
12383
|
-
const destPath =
|
|
12384
|
-
await
|
|
12385
|
-
await
|
|
12452
|
+
const destPath = join11(backupPath, ideId, filename);
|
|
12453
|
+
await fs21.mkdir(dirname(destPath), { recursive: true });
|
|
12454
|
+
await fs21.copyFile(originalPath, destPath);
|
|
12386
12455
|
backedUpFiles.push({
|
|
12387
12456
|
originalPath: filename,
|
|
12388
|
-
backupPath:
|
|
12457
|
+
backupPath: join11(ideId, filename),
|
|
12389
12458
|
ide: ideId
|
|
12390
12459
|
});
|
|
12391
12460
|
} catch (error) {
|
|
@@ -12395,16 +12464,16 @@ var BackupService = class {
|
|
|
12395
12464
|
* Restore files from a backup
|
|
12396
12465
|
*/
|
|
12397
12466
|
async restoreBackup(backupPath) {
|
|
12398
|
-
const metadataPath =
|
|
12399
|
-
const metadataContent = await
|
|
12467
|
+
const metadataPath = join11(backupPath, "metadata.json");
|
|
12468
|
+
const metadataContent = await fs21.readFile(metadataPath, "utf-8");
|
|
12400
12469
|
const metadata = JSON.parse(metadataContent);
|
|
12401
12470
|
console.log(`
|
|
12402
12471
|
Restoring backup from ${metadata.timestamp}...`);
|
|
12403
12472
|
for (const file of metadata.files) {
|
|
12404
|
-
const sourcePath =
|
|
12405
|
-
const destPath =
|
|
12406
|
-
await
|
|
12407
|
-
await
|
|
12473
|
+
const sourcePath = join11(backupPath, file.backupPath);
|
|
12474
|
+
const destPath = join11(process.cwd(), file.originalPath);
|
|
12475
|
+
await fs21.mkdir(dirname(destPath), { recursive: true });
|
|
12476
|
+
await fs21.copyFile(sourcePath, destPath);
|
|
12408
12477
|
console.log(`\u2713 Restored ${file.originalPath}`);
|
|
12409
12478
|
}
|
|
12410
12479
|
console.log("\n\u2705 Backup restored successfully!");
|
|
@@ -12414,18 +12483,18 @@ Restoring backup from ${metadata.timestamp}...`);
|
|
|
12414
12483
|
*/
|
|
12415
12484
|
async listBackups() {
|
|
12416
12485
|
try {
|
|
12417
|
-
const backupDirPath =
|
|
12486
|
+
const backupDirPath = join11(process.cwd(), this.backupDir);
|
|
12418
12487
|
const exists = await this.pathExists(backupDirPath);
|
|
12419
12488
|
if (!exists) {
|
|
12420
12489
|
return [];
|
|
12421
12490
|
}
|
|
12422
|
-
const entries = await
|
|
12491
|
+
const entries = await fs21.readdir(backupDirPath, { withFileTypes: true });
|
|
12423
12492
|
const backups = [];
|
|
12424
12493
|
for (const entry of entries) {
|
|
12425
12494
|
if (entry.isDirectory()) {
|
|
12426
|
-
const metadataPath =
|
|
12495
|
+
const metadataPath = join11(backupDirPath, entry.name, "metadata.json");
|
|
12427
12496
|
try {
|
|
12428
|
-
const metadataContent = await
|
|
12497
|
+
const metadataContent = await fs21.readFile(metadataPath, "utf-8");
|
|
12429
12498
|
const metadata = JSON.parse(metadataContent);
|
|
12430
12499
|
backups.push(metadata);
|
|
12431
12500
|
} catch {
|
|
@@ -12442,7 +12511,7 @@ Restoring backup from ${metadata.timestamp}...`);
|
|
|
12442
12511
|
* Delete a specific backup
|
|
12443
12512
|
*/
|
|
12444
12513
|
async deleteBackup(timestamp) {
|
|
12445
|
-
const backupPath =
|
|
12514
|
+
const backupPath = join11(process.cwd(), this.backupDir, timestamp);
|
|
12446
12515
|
await this.deleteDirectory(backupPath);
|
|
12447
12516
|
}
|
|
12448
12517
|
/**
|
|
@@ -12474,7 +12543,7 @@ Restoring backup from ${metadata.timestamp}...`);
|
|
|
12474
12543
|
*/
|
|
12475
12544
|
async pathExists(path13) {
|
|
12476
12545
|
try {
|
|
12477
|
-
await
|
|
12546
|
+
await fs21.access(path13);
|
|
12478
12547
|
return true;
|
|
12479
12548
|
} catch {
|
|
12480
12549
|
return false;
|
|
@@ -12489,16 +12558,16 @@ Restoring backup from ${metadata.timestamp}...`);
|
|
|
12489
12558
|
if (!exists) {
|
|
12490
12559
|
return;
|
|
12491
12560
|
}
|
|
12492
|
-
const entries = await
|
|
12561
|
+
const entries = await fs21.readdir(path13, { withFileTypes: true });
|
|
12493
12562
|
for (const entry of entries) {
|
|
12494
|
-
const fullPath =
|
|
12563
|
+
const fullPath = join11(path13, entry.name);
|
|
12495
12564
|
if (entry.isDirectory()) {
|
|
12496
12565
|
await this.deleteDirectory(fullPath);
|
|
12497
12566
|
} else {
|
|
12498
|
-
await
|
|
12567
|
+
await fs21.unlink(fullPath);
|
|
12499
12568
|
}
|
|
12500
12569
|
}
|
|
12501
|
-
await
|
|
12570
|
+
await fs21.rmdir(path13);
|
|
12502
12571
|
} catch (error) {
|
|
12503
12572
|
}
|
|
12504
12573
|
}
|
|
@@ -12506,14 +12575,14 @@ Restoring backup from ${metadata.timestamp}...`);
|
|
|
12506
12575
|
* Get backup directory path
|
|
12507
12576
|
*/
|
|
12508
12577
|
getBackupDir() {
|
|
12509
|
-
return
|
|
12578
|
+
return join11(process.cwd(), this.backupDir);
|
|
12510
12579
|
}
|
|
12511
12580
|
/**
|
|
12512
12581
|
* Ensure backup directory exists
|
|
12513
12582
|
*/
|
|
12514
12583
|
async ensureBackupDir() {
|
|
12515
|
-
const backupDirPath =
|
|
12516
|
-
await
|
|
12584
|
+
const backupDirPath = join11(process.cwd(), this.backupDir);
|
|
12585
|
+
await fs21.mkdir(backupDirPath, { recursive: true });
|
|
12517
12586
|
}
|
|
12518
12587
|
};
|
|
12519
12588
|
|
|
@@ -12557,7 +12626,8 @@ function createRulesApplyCommand() {
|
|
|
12557
12626
|
name: `${p.name} - ${p.description}`,
|
|
12558
12627
|
value: p.slug,
|
|
12559
12628
|
description: `v${p.version} | ${p.tags.join(", ")}`
|
|
12560
|
-
}))
|
|
12629
|
+
})),
|
|
12630
|
+
theme: selectTheme
|
|
12561
12631
|
});
|
|
12562
12632
|
}
|
|
12563
12633
|
console.log(`
|
|
@@ -12627,7 +12697,8 @@ function createRulesApplyCommand() {
|
|
|
12627
12697
|
selectedIdes = await checkbox5({
|
|
12628
12698
|
message: "Select IDE formats to generate (pre-selected are recommended):",
|
|
12629
12699
|
choices,
|
|
12630
|
-
required: true
|
|
12700
|
+
required: true,
|
|
12701
|
+
theme: checkboxTheme
|
|
12631
12702
|
});
|
|
12632
12703
|
}
|
|
12633
12704
|
}
|
|
@@ -12669,21 +12740,21 @@ function createRulesApplyCommand() {
|
|
|
12669
12740
|
}
|
|
12670
12741
|
}
|
|
12671
12742
|
console.log("\n\u{1F4DD} Applying preset...\n");
|
|
12672
|
-
const rulePresetDir =
|
|
12743
|
+
const rulePresetDir = join12(process.cwd(), ".jai1", "rule-preset");
|
|
12673
12744
|
try {
|
|
12674
|
-
await
|
|
12745
|
+
await fs22.rm(rulePresetDir, { recursive: true, force: true });
|
|
12675
12746
|
} catch {
|
|
12676
12747
|
}
|
|
12677
|
-
await
|
|
12678
|
-
await
|
|
12679
|
-
|
|
12748
|
+
await fs22.mkdir(rulePresetDir, { recursive: true });
|
|
12749
|
+
await fs22.writeFile(
|
|
12750
|
+
join12(rulePresetDir, "preset.json"),
|
|
12680
12751
|
JSON.stringify(bundle.preset, null, 2),
|
|
12681
12752
|
"utf-8"
|
|
12682
12753
|
);
|
|
12683
12754
|
for (const [filename, content] of Object.entries(bundle.files)) {
|
|
12684
|
-
const filePath =
|
|
12685
|
-
await
|
|
12686
|
-
await
|
|
12755
|
+
const filePath = join12(rulePresetDir, filename);
|
|
12756
|
+
await fs22.mkdir(join12(filePath, ".."), { recursive: true });
|
|
12757
|
+
await fs22.writeFile(filePath, content, "utf-8");
|
|
12687
12758
|
}
|
|
12688
12759
|
console.log(`\u2713 Saved preset to .jai1/rule-preset/`);
|
|
12689
12760
|
const allGeneratedFiles = [];
|
|
@@ -12691,9 +12762,9 @@ function createRulesApplyCommand() {
|
|
|
12691
12762
|
try {
|
|
12692
12763
|
const files = generatorService.generateForIde(bundle, ideId);
|
|
12693
12764
|
for (const file of files) {
|
|
12694
|
-
const fullPath =
|
|
12695
|
-
await
|
|
12696
|
-
await
|
|
12765
|
+
const fullPath = join12(process.cwd(), file.path);
|
|
12766
|
+
await fs22.mkdir(join12(fullPath, ".."), { recursive: true });
|
|
12767
|
+
await fs22.writeFile(fullPath, file.content, "utf-8");
|
|
12697
12768
|
console.log(`\u2713 [${ideId}] ${file.path}`);
|
|
12698
12769
|
allGeneratedFiles.push({
|
|
12699
12770
|
ide: ideId,
|
|
@@ -12705,7 +12776,8 @@ function createRulesApplyCommand() {
|
|
|
12705
12776
|
console.error(`\u2717 Failed to generate ${ideId} files:`, error);
|
|
12706
12777
|
}
|
|
12707
12778
|
}
|
|
12708
|
-
const
|
|
12779
|
+
const projectConfigService = new ProjectConfigService();
|
|
12780
|
+
const rulesConfig = {
|
|
12709
12781
|
preset: bundle.preset.slug,
|
|
12710
12782
|
version: bundle.preset.version,
|
|
12711
12783
|
appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -12720,24 +12792,18 @@ function createRulesApplyCommand() {
|
|
|
12720
12792
|
] : []
|
|
12721
12793
|
};
|
|
12722
12794
|
try {
|
|
12723
|
-
const
|
|
12724
|
-
|
|
12725
|
-
|
|
12726
|
-
|
|
12727
|
-
|
|
12728
|
-
...projectConfig.backups,
|
|
12729
|
-
...existingConfig.backups.slice(0, 5)
|
|
12795
|
+
const existingRules = await projectConfigService.loadRules();
|
|
12796
|
+
if (existingRules?.backups && existingRules.backups.length > 0) {
|
|
12797
|
+
rulesConfig.backups = [
|
|
12798
|
+
...rulesConfig.backups,
|
|
12799
|
+
...existingRules.backups.slice(0, 5)
|
|
12730
12800
|
// Keep last 5 backups
|
|
12731
12801
|
];
|
|
12732
12802
|
}
|
|
12733
12803
|
} catch {
|
|
12734
12804
|
}
|
|
12735
|
-
await
|
|
12736
|
-
|
|
12737
|
-
JSON.stringify(projectConfig, null, 2),
|
|
12738
|
-
"utf-8"
|
|
12739
|
-
);
|
|
12740
|
-
console.log("\n\u2713 Updated jai1-rules.json");
|
|
12805
|
+
await projectConfigService.saveRules(rulesConfig);
|
|
12806
|
+
console.log("\n\u2713 Updated .jai1/project.json");
|
|
12741
12807
|
console.log("\n\u2705 Preset applied successfully!\n");
|
|
12742
12808
|
console.log("\u{1F4CA} Summary:");
|
|
12743
12809
|
console.log(` Generated ${allGeneratedFiles.length} files across ${resolvedIdes.length} IDE(s)`);
|
|
@@ -12765,7 +12831,7 @@ function createRulesApplyCommand() {
|
|
|
12765
12831
|
|
|
12766
12832
|
// src/commands/rules/restore.ts
|
|
12767
12833
|
import { Command as Command46 } from "commander";
|
|
12768
|
-
import { join as
|
|
12834
|
+
import { join as join13 } from "path";
|
|
12769
12835
|
import { select as select6, confirm as confirm9 } from "@inquirer/prompts";
|
|
12770
12836
|
function createRulesRestoreCommand() {
|
|
12771
12837
|
return new Command46("restore").description("Restore rules from a backup").option("--latest", "Restore the most recent backup").option("-y, --yes", "Skip confirmation").action(async (options) => {
|
|
@@ -12787,7 +12853,8 @@ function createRulesRestoreCommand() {
|
|
|
12787
12853
|
name: formatBackupInfo(backup),
|
|
12788
12854
|
value: backup.timestamp,
|
|
12789
12855
|
description: `${backup.files.length} files | IDEs: ${backup.ides.join(", ")}`
|
|
12790
|
-
}))
|
|
12856
|
+
})),
|
|
12857
|
+
theme: selectTheme
|
|
12791
12858
|
});
|
|
12792
12859
|
selectedBackup = backups.find((b) => b.timestamp === backupTimestamp);
|
|
12793
12860
|
if (!selectedBackup) {
|
|
@@ -12811,7 +12878,7 @@ function createRulesRestoreCommand() {
|
|
|
12811
12878
|
}
|
|
12812
12879
|
console.log("\n\u{1F504} Restoring backup...\n");
|
|
12813
12880
|
try {
|
|
12814
|
-
const backupPath =
|
|
12881
|
+
const backupPath = join13(backupService.getBackupDir(), selectedBackup.timestamp);
|
|
12815
12882
|
await backupService.restoreBackup(backupPath);
|
|
12816
12883
|
console.log("\n\u2705 Backup restored successfully!\n");
|
|
12817
12884
|
console.log("\u{1F4A1} Tip: Your IDE may need to be restarted to pick up the changes.");
|
|
@@ -12837,23 +12904,41 @@ function formatTimestamp(timestamp) {
|
|
|
12837
12904
|
|
|
12838
12905
|
// src/commands/rules/sync.ts
|
|
12839
12906
|
import { Command as Command47 } from "commander";
|
|
12840
|
-
import { promises as
|
|
12841
|
-
import { join as
|
|
12842
|
-
import { checkbox as checkbox6, confirm as confirm10 } from "@inquirer/prompts";
|
|
12907
|
+
import { promises as fs23 } from "fs";
|
|
12908
|
+
import { join as join14 } from "path";
|
|
12909
|
+
import { checkbox as checkbox6, confirm as confirm10, Separator } from "@inquirer/prompts";
|
|
12843
12910
|
function createRulesSyncCommand() {
|
|
12844
12911
|
return new Command47("sync").description("Regenerate rule outputs for all configured IDEs").option("--ides <ides>", "Comma-separated list of IDEs to sync (default: all configured)").option("--detect", "Auto-detect active IDEs instead of using config").option("-y, --yes", "Skip confirmations").action(async (options) => {
|
|
12845
|
-
const
|
|
12846
|
-
|
|
12912
|
+
const rulePresetDir = join14(process.cwd(), ".jai1", "rule-preset");
|
|
12913
|
+
const presetJsonPath = join14(rulePresetDir, "preset.json");
|
|
12914
|
+
let presetExists = false;
|
|
12915
|
+
let presetData = null;
|
|
12847
12916
|
try {
|
|
12848
|
-
const
|
|
12849
|
-
|
|
12917
|
+
const presetContent = await fs23.readFile(presetJsonPath, "utf-8");
|
|
12918
|
+
presetData = JSON.parse(presetContent);
|
|
12919
|
+
presetExists = true;
|
|
12850
12920
|
} catch {
|
|
12921
|
+
}
|
|
12922
|
+
if (!presetExists) {
|
|
12851
12923
|
throw new ValidationError(
|
|
12852
|
-
'No
|
|
12924
|
+
'No rule preset found in .jai1/rule-preset/\nRun "jai1 rules apply <preset>" or create a project with "jai1 kit create <kit>" first.'
|
|
12853
12925
|
);
|
|
12854
12926
|
}
|
|
12927
|
+
const projectConfigService = new ProjectConfigService();
|
|
12928
|
+
let rulesConfig = await projectConfigService.loadRules();
|
|
12929
|
+
if (!rulesConfig) {
|
|
12930
|
+
console.log("\u26A0\uFE0F No .jai1/project.json found, creating from preset...\n");
|
|
12931
|
+
rulesConfig = {
|
|
12932
|
+
preset: presetData.slug,
|
|
12933
|
+
version: presetData.version,
|
|
12934
|
+
appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12935
|
+
ides: [],
|
|
12936
|
+
customContext: "09-custom.md",
|
|
12937
|
+
backups: []
|
|
12938
|
+
};
|
|
12939
|
+
}
|
|
12855
12940
|
console.log("\u{1F504} Syncing rules...\n");
|
|
12856
|
-
console.log(`Preset: ${
|
|
12941
|
+
console.log(`Preset: ${rulesConfig.preset} v${rulesConfig.version}`);
|
|
12857
12942
|
let idesToSync = [];
|
|
12858
12943
|
if (options.detect) {
|
|
12859
12944
|
console.log("\n\u{1F50D} Auto-detecting active IDEs...");
|
|
@@ -12884,16 +12969,16 @@ Detected ${detected.length} active IDE(s):
|
|
|
12884
12969
|
idesToSync = detected.map((d) => d.id);
|
|
12885
12970
|
} else if (options.ides) {
|
|
12886
12971
|
const requested = options.ides.split(",").map((ide) => ide.trim());
|
|
12887
|
-
const configured =
|
|
12972
|
+
const configured = rulesConfig.ides || [];
|
|
12888
12973
|
idesToSync = requested;
|
|
12889
12974
|
const notConfigured = requested.filter((ide) => !configured.includes(ide));
|
|
12890
12975
|
if (notConfigured.length > 0) {
|
|
12891
12976
|
console.log(`
|
|
12892
12977
|
\u26A0\uFE0F IDEs not in config: ${notConfigured.join(", ")}`);
|
|
12893
|
-
console.log(" They will still be synced, but may not be in jai1
|
|
12978
|
+
console.log(" They will still be synced, but may not be in .jai1/project.json");
|
|
12894
12979
|
}
|
|
12895
12980
|
} else if (!options.yes) {
|
|
12896
|
-
const currentIdes =
|
|
12981
|
+
const currentIdes = rulesConfig.ides || [];
|
|
12897
12982
|
console.log(`
|
|
12898
12983
|
Current IDE(s): ${currentIdes.join(", ") || "none"}`);
|
|
12899
12984
|
const detectionService = new IdeDetectionService();
|
|
@@ -12913,18 +12998,23 @@ Current IDE(s): ${currentIdes.join(", ") || "none"}`);
|
|
|
12913
12998
|
}
|
|
12914
12999
|
const choices = buildIdeChoices(currentIdes, detected, suggestions);
|
|
12915
13000
|
idesToSync = await checkbox6({
|
|
12916
|
-
message: "Select IDE formats to sync
|
|
13001
|
+
message: "Select IDE formats to sync:",
|
|
12917
13002
|
choices,
|
|
12918
|
-
required:
|
|
13003
|
+
required: false,
|
|
13004
|
+
theme: checkboxTheme
|
|
12919
13005
|
});
|
|
13006
|
+
if (idesToSync.includes("__quit__")) {
|
|
13007
|
+
console.log("Cancelled.");
|
|
13008
|
+
return;
|
|
13009
|
+
}
|
|
12920
13010
|
if (idesToSync.length === 0) {
|
|
12921
13011
|
console.log("Cancelled.");
|
|
12922
13012
|
return;
|
|
12923
13013
|
}
|
|
12924
13014
|
} else {
|
|
12925
|
-
idesToSync =
|
|
13015
|
+
idesToSync = rulesConfig.ides || [];
|
|
12926
13016
|
if (idesToSync.length === 0) {
|
|
12927
|
-
console.log("\n\u26A0\uFE0F No IDEs configured in jai1
|
|
13017
|
+
console.log("\n\u26A0\uFE0F No IDEs configured in .jai1/project.json");
|
|
12928
13018
|
console.log(" Detecting from existing files...\n");
|
|
12929
13019
|
const detectionService = new IdeDetectionService();
|
|
12930
13020
|
idesToSync = await detectionService.detectExistingIdes();
|
|
@@ -12945,7 +13035,7 @@ Current IDE(s): ${currentIdes.join(", ") || "none"}`);
|
|
|
12945
13035
|
if (!config) {
|
|
12946
13036
|
throw new ValidationError('Not initialized. Run "jai1 auth" first.');
|
|
12947
13037
|
}
|
|
12948
|
-
const presetResponse = await fetch(`${config.apiUrl}/api/rules/presets/${
|
|
13038
|
+
const presetResponse = await fetch(`${config.apiUrl}/api/rules/presets/${rulesConfig.preset}`, {
|
|
12949
13039
|
headers: {
|
|
12950
13040
|
"JAI1-Access-Key": config.accessKey
|
|
12951
13041
|
}
|
|
@@ -12954,21 +13044,13 @@ Current IDE(s): ${currentIdes.join(", ") || "none"}`);
|
|
|
12954
13044
|
throw new Error(`Failed to fetch preset: ${presetResponse.statusText}`);
|
|
12955
13045
|
}
|
|
12956
13046
|
const bundle = await presetResponse.json();
|
|
12957
|
-
const
|
|
12958
|
-
const
|
|
12959
|
-
|
|
12960
|
-
|
|
12961
|
-
|
|
12962
|
-
|
|
12963
|
-
const filePath = join13(rulePresetDir, file);
|
|
12964
|
-
const content = await fs22.readFile(filePath, "utf-8");
|
|
12965
|
-
bundle.files[file] = content;
|
|
12966
|
-
}
|
|
13047
|
+
const files = await fs23.readdir(rulePresetDir);
|
|
13048
|
+
for (const file of files) {
|
|
13049
|
+
if (file.endsWith(".mdc") || file.endsWith(".md")) {
|
|
13050
|
+
const filePath = join14(rulePresetDir, file);
|
|
13051
|
+
const content = await fs23.readFile(filePath, "utf-8");
|
|
13052
|
+
bundle.files[file] = content;
|
|
12967
13053
|
}
|
|
12968
|
-
} else {
|
|
12969
|
-
console.log("\n\u26A0\uFE0F No rule preset found in .jai1/rule-preset/");
|
|
12970
|
-
console.log(' Run "jai1 rules apply" first to apply a preset.\n');
|
|
12971
|
-
return;
|
|
12972
13054
|
}
|
|
12973
13055
|
const generatorService = new RulesGeneratorService();
|
|
12974
13056
|
for (const ideId of idesToSync) {
|
|
@@ -12978,25 +13060,21 @@ Current IDE(s): ${currentIdes.join(", ") || "none"}`);
|
|
|
12978
13060
|
console.log(`\u26A0\uFE0F Unknown IDE format: ${ideId}, skipping`);
|
|
12979
13061
|
continue;
|
|
12980
13062
|
}
|
|
12981
|
-
const
|
|
12982
|
-
for (const file of
|
|
12983
|
-
const fullPath =
|
|
12984
|
-
await
|
|
12985
|
-
await
|
|
13063
|
+
const files2 = generatorService.generateForIde(bundle, ideId);
|
|
13064
|
+
for (const file of files2) {
|
|
13065
|
+
const fullPath = join14(process.cwd(), file.path);
|
|
13066
|
+
await fs23.mkdir(join14(fullPath, ".."), { recursive: true });
|
|
13067
|
+
await fs23.writeFile(fullPath, file.content, "utf-8");
|
|
12986
13068
|
}
|
|
12987
|
-
console.log(`\u2713 ${format.name} - ${
|
|
13069
|
+
console.log(`\u2713 ${format.name} - ${files2.length} files regenerated`);
|
|
12988
13070
|
} catch (error) {
|
|
12989
13071
|
console.error(`\u2717 Failed to sync ${ideId}:`, error);
|
|
12990
13072
|
}
|
|
12991
13073
|
}
|
|
12992
|
-
if (JSON.stringify(
|
|
12993
|
-
|
|
12994
|
-
await
|
|
12995
|
-
|
|
12996
|
-
JSON.stringify(projectConfig, null, 2),
|
|
12997
|
-
"utf-8"
|
|
12998
|
-
);
|
|
12999
|
-
console.log("\n\u2713 Updated jai1-rules.json with synced IDEs");
|
|
13074
|
+
if (JSON.stringify(rulesConfig.ides) !== JSON.stringify(idesToSync)) {
|
|
13075
|
+
rulesConfig.ides = idesToSync;
|
|
13076
|
+
await projectConfigService.saveRules(rulesConfig);
|
|
13077
|
+
console.log("\n\u2713 Updated .jai1/project.json with synced IDEs");
|
|
13000
13078
|
}
|
|
13001
13079
|
console.log("\n\u2705 Rules synced successfully!\n");
|
|
13002
13080
|
console.log("\u{1F4A1} Next steps:");
|
|
@@ -13036,48 +13114,43 @@ function buildIdeChoices(currentIdes, detected, suggestions) {
|
|
|
13036
13114
|
name: "Gemini CLI (GEMINI.md)",
|
|
13037
13115
|
value: "gemini",
|
|
13038
13116
|
checked: currentIdes.includes("gemini") || detected.some((d) => d.id === "gemini")
|
|
13117
|
+
},
|
|
13118
|
+
new Separator(),
|
|
13119
|
+
{
|
|
13120
|
+
name: "\u2716 Quit",
|
|
13121
|
+
value: "__quit__",
|
|
13122
|
+
checked: false
|
|
13039
13123
|
}
|
|
13040
13124
|
];
|
|
13041
13125
|
return choices;
|
|
13042
13126
|
}
|
|
13043
|
-
async function checkPathExists(absolutePath) {
|
|
13044
|
-
try {
|
|
13045
|
-
await fs22.access(absolutePath);
|
|
13046
|
-
return true;
|
|
13047
|
-
} catch {
|
|
13048
|
-
return false;
|
|
13049
|
-
}
|
|
13050
|
-
}
|
|
13051
13127
|
|
|
13052
13128
|
// src/commands/rules/info.ts
|
|
13053
13129
|
import { Command as Command48 } from "commander";
|
|
13054
|
-
import { promises as
|
|
13055
|
-
import { join as
|
|
13130
|
+
import { promises as fs24 } from "fs";
|
|
13131
|
+
import { join as join15 } from "path";
|
|
13056
13132
|
function createRulesInfoCommand() {
|
|
13057
13133
|
return new Command48("info").description("Show current preset information").option("--json", "Output as JSON").action(async (options) => {
|
|
13058
|
-
const
|
|
13059
|
-
|
|
13060
|
-
|
|
13061
|
-
const configContent = await fs23.readFile(configPath, "utf-8");
|
|
13062
|
-
projectConfig = JSON.parse(configContent);
|
|
13063
|
-
} catch {
|
|
13134
|
+
const projectConfigService = new ProjectConfigService();
|
|
13135
|
+
const rulesConfig = await projectConfigService.loadRules();
|
|
13136
|
+
if (!rulesConfig) {
|
|
13064
13137
|
throw new ValidationError(
|
|
13065
|
-
'No jai1
|
|
13138
|
+
'No .jai1/project.json found. Run "jai1 rules apply" first.'
|
|
13066
13139
|
);
|
|
13067
13140
|
}
|
|
13068
13141
|
if (options.json) {
|
|
13069
|
-
console.log(JSON.stringify(
|
|
13142
|
+
console.log(JSON.stringify(rulesConfig, null, 2));
|
|
13070
13143
|
return;
|
|
13071
13144
|
}
|
|
13072
13145
|
console.log("\u{1F4CB} Current Preset Information\n");
|
|
13073
|
-
const rulePresetDir =
|
|
13074
|
-
const presetJsonPath =
|
|
13146
|
+
const rulePresetDir = join15(process.cwd(), ".jai1", "rule-preset");
|
|
13147
|
+
const presetJsonPath = join15(rulePresetDir, "preset.json");
|
|
13075
13148
|
let presetMetadata = null;
|
|
13076
13149
|
let presetFiles = [];
|
|
13077
13150
|
try {
|
|
13078
|
-
const presetContent = await
|
|
13151
|
+
const presetContent = await fs24.readFile(presetJsonPath, "utf-8");
|
|
13079
13152
|
presetMetadata = JSON.parse(presetContent);
|
|
13080
|
-
const files = await
|
|
13153
|
+
const files = await fs24.readdir(rulePresetDir);
|
|
13081
13154
|
presetFiles = files.filter((f) => f.endsWith(".mdc"));
|
|
13082
13155
|
} catch {
|
|
13083
13156
|
}
|
|
@@ -13089,10 +13162,10 @@ function createRulesInfoCommand() {
|
|
|
13089
13162
|
console.log(`Tags: ${presetMetadata.tags.join(", ")}`);
|
|
13090
13163
|
}
|
|
13091
13164
|
} else {
|
|
13092
|
-
console.log(`Preset: ${
|
|
13093
|
-
console.log(`Version: ${
|
|
13165
|
+
console.log(`Preset: ${rulesConfig.preset}`);
|
|
13166
|
+
console.log(`Version: ${rulesConfig.version}`);
|
|
13094
13167
|
}
|
|
13095
|
-
console.log(`Applied at: ${new Date(
|
|
13168
|
+
console.log(`Applied at: ${new Date(rulesConfig.appliedAt).toLocaleString()}`);
|
|
13096
13169
|
console.log(`
|
|
13097
13170
|
Source of Truth:`);
|
|
13098
13171
|
if (presetFiles.length > 0) {
|
|
@@ -13107,10 +13180,10 @@ Source of Truth:`);
|
|
|
13107
13180
|
console.log(` \u26A0\uFE0F .jai1/rule-preset/ not found`);
|
|
13108
13181
|
console.log(` Run "jai1 rules apply" to create it`);
|
|
13109
13182
|
}
|
|
13110
|
-
if (
|
|
13183
|
+
if (rulesConfig.ides && rulesConfig.ides.length > 0) {
|
|
13111
13184
|
console.log(`
|
|
13112
|
-
Configured IDEs (${
|
|
13113
|
-
for (const ideId of
|
|
13185
|
+
Configured IDEs (${rulesConfig.ides.length}):`);
|
|
13186
|
+
for (const ideId of rulesConfig.ides) {
|
|
13114
13187
|
const format = IDE_FORMATS[ideId];
|
|
13115
13188
|
if (format) {
|
|
13116
13189
|
const exists = await checkIdeFilesExist(ideId, format);
|
|
@@ -13119,15 +13192,15 @@ Configured IDEs (${projectConfig.ides.length}):`);
|
|
|
13119
13192
|
}
|
|
13120
13193
|
}
|
|
13121
13194
|
}
|
|
13122
|
-
if (
|
|
13195
|
+
if (rulesConfig.backups && rulesConfig.backups.length > 0) {
|
|
13123
13196
|
console.log(`
|
|
13124
|
-
Available Backups (${
|
|
13125
|
-
for (const backup of
|
|
13197
|
+
Available Backups (${rulesConfig.backups.length}):`);
|
|
13198
|
+
for (const backup of rulesConfig.backups.slice(0, 3)) {
|
|
13126
13199
|
const timestamp = new Date(backup.timestamp).toLocaleString();
|
|
13127
13200
|
console.log(` \u2022 ${timestamp} - IDEs: ${backup.ides.join(", ")}`);
|
|
13128
13201
|
}
|
|
13129
|
-
if (
|
|
13130
|
-
console.log(` ... and ${
|
|
13202
|
+
if (rulesConfig.backups.length > 3) {
|
|
13203
|
+
console.log(` ... and ${rulesConfig.backups.length - 3} more`);
|
|
13131
13204
|
}
|
|
13132
13205
|
}
|
|
13133
13206
|
console.log("\n\u2139\uFE0F Commands:");
|
|
@@ -13136,9 +13209,9 @@ Available Backups (${projectConfig.backups.length}):`);
|
|
|
13136
13209
|
console.log(' \u2022 "jai1 rules apply" - Apply a different preset (replaces current)');
|
|
13137
13210
|
});
|
|
13138
13211
|
}
|
|
13139
|
-
async function
|
|
13212
|
+
async function checkPathExists(path13) {
|
|
13140
13213
|
try {
|
|
13141
|
-
await
|
|
13214
|
+
await fs24.access(join15(process.cwd(), path13));
|
|
13142
13215
|
return true;
|
|
13143
13216
|
} catch {
|
|
13144
13217
|
return false;
|
|
@@ -13147,11 +13220,11 @@ async function checkPathExists2(path13) {
|
|
|
13147
13220
|
async function checkIdeFilesExist(ideId, format) {
|
|
13148
13221
|
try {
|
|
13149
13222
|
if (ideId === "agentsmd") {
|
|
13150
|
-
return await
|
|
13223
|
+
return await checkPathExists("AGENTS.md");
|
|
13151
13224
|
} else if (ideId === "gemini") {
|
|
13152
|
-
return await
|
|
13225
|
+
return await checkPathExists("GEMINI.md");
|
|
13153
13226
|
} else {
|
|
13154
|
-
return await
|
|
13227
|
+
return await checkPathExists(format.rulesPath);
|
|
13155
13228
|
}
|
|
13156
13229
|
} catch {
|
|
13157
13230
|
return false;
|
|
@@ -13167,7 +13240,7 @@ function showRulesHelp() {
|
|
|
13167
13240
|
console.log(` ${chalk17.cyan("info")} Xem chi ti\u1EBFt m\u1ED9t preset`);
|
|
13168
13241
|
console.log(` ${chalk17.cyan("init")} Kh\u1EDFi t\u1EA1o rules t\u1EEB preset`);
|
|
13169
13242
|
console.log(` ${chalk17.cyan("apply")} \xC1p d\u1EE5ng preset v\xE0o project`);
|
|
13170
|
-
console.log(` ${chalk17.cyan("sync")} \u0110\u1ED3ng b\u1ED9 rules
|
|
13243
|
+
console.log(` ${chalk17.cyan("sync")} \u0110\u1ED3ng b\u1ED9 rules sang c\xE1c \u0111\u1ECBnh d\u1EA1ng IDE`);
|
|
13171
13244
|
console.log(` ${chalk17.cyan("restore")} Kh\xF4i ph\u1EE5c rules t\u1EEB backup`);
|
|
13172
13245
|
console.log();
|
|
13173
13246
|
console.log(chalk17.bold("V\xED d\u1EE5:"));
|
|
@@ -13354,7 +13427,7 @@ function getInstallCommand(packageManager2) {
|
|
|
13354
13427
|
// src/commands/clean.ts
|
|
13355
13428
|
import { Command as Command51 } from "commander";
|
|
13356
13429
|
import { confirm as confirm12, select as select7 } from "@inquirer/prompts";
|
|
13357
|
-
import { join as
|
|
13430
|
+
import { join as join16 } from "path";
|
|
13358
13431
|
function createCleanCommand() {
|
|
13359
13432
|
return new Command51("clean").description("Clean up backups, cache, and temporary files").option("-y, --yes", "Skip confirmation").option("--backups", "Clean only backup files").option("--all", "Clean all (backups + cache)").action(async (options) => {
|
|
13360
13433
|
await handleClean(options);
|
|
@@ -13367,7 +13440,7 @@ async function handleClean(options) {
|
|
|
13367
13440
|
{
|
|
13368
13441
|
name: "Backups",
|
|
13369
13442
|
description: "Component backup files (.jai1_backup/)",
|
|
13370
|
-
path:
|
|
13443
|
+
path: join16(cwd, ".jai1_backup"),
|
|
13371
13444
|
check: async () => {
|
|
13372
13445
|
const backups = await service.listBackups(cwd);
|
|
13373
13446
|
return { exists: backups.length > 0, count: backups.length };
|
|
@@ -13426,7 +13499,8 @@ async function handleClean(options) {
|
|
|
13426
13499
|
})),
|
|
13427
13500
|
{ name: "\u{1F9F9} Clean all", value: "all" },
|
|
13428
13501
|
{ name: "\u274C Cancel", value: "cancel" }
|
|
13429
|
-
]
|
|
13502
|
+
],
|
|
13503
|
+
theme: selectTheme
|
|
13430
13504
|
});
|
|
13431
13505
|
if (action === "cancel") {
|
|
13432
13506
|
console.log("\n\u274C Operation cancelled.");
|
|
@@ -14358,8 +14432,8 @@ async function handleSyncProject(options) {
|
|
|
14358
14432
|
|
|
14359
14433
|
// src/commands/framework/info.ts
|
|
14360
14434
|
import { Command as Command55 } from "commander";
|
|
14361
|
-
import { promises as
|
|
14362
|
-
import { join as
|
|
14435
|
+
import { promises as fs25 } from "fs";
|
|
14436
|
+
import { join as join17 } from "path";
|
|
14363
14437
|
import { homedir as homedir5 } from "os";
|
|
14364
14438
|
function createInfoCommand() {
|
|
14365
14439
|
const cmd = new Command55("info").description("Show jai1-client configuration and status").option("--json", "Output as JSON").option("--verbose", "Show detailed information").action(async (options) => {
|
|
@@ -14373,7 +14447,7 @@ async function handleInfo(options) {
|
|
|
14373
14447
|
if (!config) {
|
|
14374
14448
|
throw new ValidationError('Not initialized. Run "jai1 auth" first.');
|
|
14375
14449
|
}
|
|
14376
|
-
const frameworkPath =
|
|
14450
|
+
const frameworkPath = join17(homedir5(), ".jai1", "framework");
|
|
14377
14451
|
const projectStatus = await getProjectStatus2();
|
|
14378
14452
|
const info = {
|
|
14379
14453
|
configPath: configService.getConfigPath(),
|
|
@@ -14408,9 +14482,9 @@ function maskKey3(key) {
|
|
|
14408
14482
|
return "****" + key.slice(-4);
|
|
14409
14483
|
}
|
|
14410
14484
|
async function getProjectStatus2() {
|
|
14411
|
-
const projectJai1 =
|
|
14485
|
+
const projectJai1 = join17(process.cwd(), ".jai1");
|
|
14412
14486
|
try {
|
|
14413
|
-
await
|
|
14487
|
+
await fs25.access(projectJai1);
|
|
14414
14488
|
return { exists: true, version: "Synced" };
|
|
14415
14489
|
} catch {
|
|
14416
14490
|
return { exists: false };
|
|
@@ -14600,7 +14674,7 @@ function createClearBackupsCommand() {
|
|
|
14600
14674
|
// src/commands/vscode/index.ts
|
|
14601
14675
|
import { Command as Command58 } from "commander";
|
|
14602
14676
|
import { checkbox as checkbox7, confirm as confirm15, select as select8 } from "@inquirer/prompts";
|
|
14603
|
-
import
|
|
14677
|
+
import fs26 from "fs/promises";
|
|
14604
14678
|
import path12 from "path";
|
|
14605
14679
|
import { existsSync as existsSync3 } from "fs";
|
|
14606
14680
|
var PERFORMANCE_GROUPS2 = {
|
|
@@ -14789,7 +14863,8 @@ async function interactiveMode2() {
|
|
|
14789
14863
|
{ name: "\u274C Disable c\xE1c nh\xF3m t\u1ED1i \u01B0u", value: "disable" },
|
|
14790
14864
|
{ name: "\u{1F680} Max Performance (enable t\u1EA5t c\u1EA3)", value: "max" },
|
|
14791
14865
|
{ name: "\u{1F504} Reset v\u1EC1 m\u1EB7c \u0111\u1ECBnh", value: "reset" }
|
|
14792
|
-
]
|
|
14866
|
+
],
|
|
14867
|
+
theme: selectTheme
|
|
14793
14868
|
});
|
|
14794
14869
|
if (action === "max") {
|
|
14795
14870
|
const allGroups = Object.keys(PERFORMANCE_GROUPS2);
|
|
@@ -14808,7 +14883,8 @@ async function selectGroupsToApply2(action) {
|
|
|
14808
14883
|
try {
|
|
14809
14884
|
const selectedGroups = await checkbox7({
|
|
14810
14885
|
message: `Ch\u1ECDn c\xE1c nh\xF3m \u0111\u1EC3 ${action === "enable" ? "enable" : "disable"} (SPACE \u0111\u1EC3 ch\u1ECDn, ENTER \u0111\u1EC3 x\xE1c nh\u1EADn):`,
|
|
14811
|
-
choices
|
|
14886
|
+
choices,
|
|
14887
|
+
theme: checkboxTheme
|
|
14812
14888
|
});
|
|
14813
14889
|
if (selectedGroups.length === 0) {
|
|
14814
14890
|
console.log("\n\u26A0\uFE0F B\u1EA1n ch\u01B0a ch\u1ECDn nh\xF3m n\xE0o!");
|
|
@@ -14831,13 +14907,13 @@ async function applyGroups2(groupKeys, action) {
|
|
|
14831
14907
|
return;
|
|
14832
14908
|
}
|
|
14833
14909
|
if (!existsSync3(vscodeDir)) {
|
|
14834
|
-
await
|
|
14910
|
+
await fs26.mkdir(vscodeDir, { recursive: true });
|
|
14835
14911
|
console.log("\u{1F4C1} \u0110\xE3 t\u1EA1o th\u01B0 m\u1EE5c .vscode/");
|
|
14836
14912
|
}
|
|
14837
14913
|
let currentSettings = {};
|
|
14838
14914
|
if (existsSync3(settingsPath)) {
|
|
14839
14915
|
try {
|
|
14840
|
-
const content = await
|
|
14916
|
+
const content = await fs26.readFile(settingsPath, "utf-8");
|
|
14841
14917
|
currentSettings = JSON.parse(content);
|
|
14842
14918
|
console.log("\u{1F4C4} \u0110\xE3 \u0111\u1ECDc c\xE0i \u0111\u1EB7t hi\u1EC7n t\u1EA1i t\u1EEB settings.json");
|
|
14843
14919
|
} catch {
|
|
@@ -14877,7 +14953,7 @@ async function applyGroups2(groupKeys, action) {
|
|
|
14877
14953
|
}
|
|
14878
14954
|
}
|
|
14879
14955
|
}
|
|
14880
|
-
await
|
|
14956
|
+
await fs26.writeFile(settingsPath, JSON.stringify(newSettings, null, 2));
|
|
14881
14957
|
console.log(`
|
|
14882
14958
|
\u2705 \u0110\xE3 c\u1EADp nh\u1EADt c\xE0i \u0111\u1EB7t VSCode t\u1EA1i: ${settingsPath}`);
|
|
14883
14959
|
console.log("\u{1F4A1} M\u1EB9o: Kh\u1EDFi \u0111\u1ED9ng l\u1EA1i VSCode \u0111\u1EC3 \xE1p d\u1EE5ng c\xE1c thay \u0111\u1ED5i.");
|
|
@@ -14898,7 +14974,7 @@ async function resetSettings2(groupKeys) {
|
|
|
14898
14974
|
return;
|
|
14899
14975
|
}
|
|
14900
14976
|
if (groupKeys.length === 0) {
|
|
14901
|
-
await
|
|
14977
|
+
await fs26.unlink(settingsPath);
|
|
14902
14978
|
console.log("\n\u2705 \u0110\xE3 x\xF3a file settings.json");
|
|
14903
14979
|
} else {
|
|
14904
14980
|
await applyGroups2(groupKeys, "disable");
|
|
@@ -15021,7 +15097,8 @@ async function runMigrateIde(options) {
|
|
|
15021
15097
|
});
|
|
15022
15098
|
selectedIdes = await checkbox8({
|
|
15023
15099
|
message: "Ch\u1ECDn IDE(s) \u0111\u1EC3 migrate (SPACE \u0111\u1EC3 ch\u1ECDn, ENTER \u0111\u1EC3 x\xE1c nh\u1EADn):",
|
|
15024
|
-
choices: ideChoices
|
|
15100
|
+
choices: ideChoices,
|
|
15101
|
+
theme: checkboxTheme
|
|
15025
15102
|
});
|
|
15026
15103
|
if (selectedIdes.length === 0) {
|
|
15027
15104
|
console.log("\n\u26A0\uFE0F B\u1EA1n ch\u01B0a ch\u1ECDn IDE n\xE0o!");
|
|
@@ -15040,7 +15117,8 @@ async function runMigrateIde(options) {
|
|
|
15040
15117
|
];
|
|
15041
15118
|
selectedTypes = await checkbox8({
|
|
15042
15119
|
message: "Ch\u1ECDn content types \u0111\u1EC3 migrate:",
|
|
15043
|
-
choices: typeChoices
|
|
15120
|
+
choices: typeChoices,
|
|
15121
|
+
theme: checkboxTheme
|
|
15044
15122
|
});
|
|
15045
15123
|
if (selectedTypes.length === 0) {
|
|
15046
15124
|
console.log("\n\u26A0\uFE0F B\u1EA1n ch\u01B0a ch\u1ECDn content type n\xE0o!");
|