@jvittechs/jai1-cli 0.1.86 → 0.1.88
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 +1793 -368
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command55 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/errors/index.ts
|
|
7
7
|
var Jai1Error = class extends Error {
|
|
@@ -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: "0.1.88",
|
|
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: {
|
|
@@ -895,9 +895,9 @@ var UnifiedApplyApp = ({
|
|
|
895
895
|
(c) => c.filepath.toLowerCase().includes(query) || c.name.toLowerCase().includes(query) || c.tags && c.tags.some((t) => t.toLowerCase().includes(query))
|
|
896
896
|
);
|
|
897
897
|
}, [components, searchQuery]);
|
|
898
|
-
useInput((
|
|
898
|
+
useInput((input4, key) => {
|
|
899
899
|
if (viewState === "summary") {
|
|
900
|
-
if (key.return ||
|
|
900
|
+
if (key.return || input4 === "q" || key.escape) {
|
|
901
901
|
onExit();
|
|
902
902
|
}
|
|
903
903
|
return;
|
|
@@ -911,7 +911,7 @@ var UnifiedApplyApp = ({
|
|
|
911
911
|
else setFocusArea("search");
|
|
912
912
|
return;
|
|
913
913
|
}
|
|
914
|
-
if (key.escape ||
|
|
914
|
+
if (key.escape || input4 === "q") {
|
|
915
915
|
onExit();
|
|
916
916
|
return;
|
|
917
917
|
}
|
|
@@ -926,7 +926,7 @@ var UnifiedApplyApp = ({
|
|
|
926
926
|
setCursorIndex((prev) => Math.max(0, prev - 1));
|
|
927
927
|
} else if (key.downArrow) {
|
|
928
928
|
setCursorIndex((prev) => Math.min(filteredComponents.length - 1, prev + 1));
|
|
929
|
-
} else if (
|
|
929
|
+
} else if (input4 === " ") {
|
|
930
930
|
const current = filteredComponents[cursorIndex];
|
|
931
931
|
if (current) {
|
|
932
932
|
setSelectedPaths((prev) => {
|
|
@@ -939,13 +939,13 @@ var UnifiedApplyApp = ({
|
|
|
939
939
|
return next;
|
|
940
940
|
});
|
|
941
941
|
}
|
|
942
|
-
} else if (
|
|
942
|
+
} else if (input4 === "a") {
|
|
943
943
|
setSelectedPaths((prev) => {
|
|
944
944
|
const next = new Set(prev);
|
|
945
945
|
filteredComponents.forEach((c) => next.add(c.filepath));
|
|
946
946
|
return next;
|
|
947
947
|
});
|
|
948
|
-
} else if (
|
|
948
|
+
} else if (input4 === "c") {
|
|
949
949
|
setSelectedPaths(/* @__PURE__ */ new Set());
|
|
950
950
|
}
|
|
951
951
|
}
|
|
@@ -954,7 +954,7 @@ var UnifiedApplyApp = ({
|
|
|
954
954
|
setSelectedPackageIndex((prev) => Math.max(0, prev - 1));
|
|
955
955
|
} else if (key.rightArrow) {
|
|
956
956
|
setSelectedPackageIndex((prev) => Math.min(tags.length - 1, prev + 1));
|
|
957
|
-
} else if (
|
|
957
|
+
} else if (input4 === " " || key.return) {
|
|
958
958
|
const tag = tags[selectedPackageIndex];
|
|
959
959
|
if (tag) {
|
|
960
960
|
const packageComponents = components.filter((c) => c.tags?.includes(tag.tag));
|
|
@@ -1286,7 +1286,7 @@ async function handleCheck(options) {
|
|
|
1286
1286
|
}
|
|
1287
1287
|
|
|
1288
1288
|
// src/commands/ide/index.ts
|
|
1289
|
-
import { Command as
|
|
1289
|
+
import { Command as Command10 } from "commander";
|
|
1290
1290
|
|
|
1291
1291
|
// src/commands/ide/context.ts
|
|
1292
1292
|
import React10 from "react";
|
|
@@ -1332,7 +1332,7 @@ var MainMenuView = ({ ideContexts, onSelect }) => {
|
|
|
1332
1332
|
available: ideContexts.some((ctx) => ctx.ide === "jai1")
|
|
1333
1333
|
}
|
|
1334
1334
|
];
|
|
1335
|
-
useInput2((
|
|
1335
|
+
useInput2((input4, key) => {
|
|
1336
1336
|
if (key.upArrow) {
|
|
1337
1337
|
setSelectedIndex((prev) => Math.max(0, prev - 1));
|
|
1338
1338
|
} else if (key.downArrow) {
|
|
@@ -1434,13 +1434,13 @@ var IDEOverviewView = ({
|
|
|
1434
1434
|
count: ideContext.stats.byType.context
|
|
1435
1435
|
});
|
|
1436
1436
|
}
|
|
1437
|
-
useInput3((
|
|
1437
|
+
useInput3((input4, key) => {
|
|
1438
1438
|
if (key.tab || key.rightArrow) {
|
|
1439
1439
|
setSelectedTabIndex((prev) => Math.min(tabs.length - 1, prev + 1));
|
|
1440
1440
|
} else if (key.leftArrow) {
|
|
1441
1441
|
setSelectedTabIndex((prev) => Math.max(0, prev - 1));
|
|
1442
1442
|
}
|
|
1443
|
-
const num = parseInt(
|
|
1443
|
+
const num = parseInt(input4, 10);
|
|
1444
1444
|
if (!isNaN(num) && num >= 1 && num <= tabs.length) {
|
|
1445
1445
|
setSelectedTabIndex(num - 1);
|
|
1446
1446
|
}
|
|
@@ -1502,7 +1502,7 @@ import React7, { useState as useState4 } from "react";
|
|
|
1502
1502
|
import { Box as Box5, Text as Text6, useInput as useInput4 } from "ink";
|
|
1503
1503
|
var ListView = ({ items, contentType, onSelect, onBack }) => {
|
|
1504
1504
|
const [selectedIndex, setSelectedIndex] = useState4(0);
|
|
1505
|
-
useInput4((
|
|
1505
|
+
useInput4((input4, key) => {
|
|
1506
1506
|
if (key.upArrow) {
|
|
1507
1507
|
setSelectedIndex((prev) => Math.max(0, prev - 1));
|
|
1508
1508
|
} else if (key.downArrow) {
|
|
@@ -1553,18 +1553,18 @@ var DetailView = ({ item, scrollPosition: initialScroll, onBack }) => {
|
|
|
1553
1553
|
const [scrollPosition, setScrollPosition] = useState5(initialScroll);
|
|
1554
1554
|
const maxVisibleLines = 25;
|
|
1555
1555
|
const maxScroll = Math.max(0, item.previewLines.length - maxVisibleLines);
|
|
1556
|
-
useInput5((
|
|
1557
|
-
if (key.upArrow ||
|
|
1556
|
+
useInput5((input4, key) => {
|
|
1557
|
+
if (key.upArrow || input4 === "k") {
|
|
1558
1558
|
setScrollPosition((prev) => Math.max(0, prev - 1));
|
|
1559
|
-
} else if (key.downArrow ||
|
|
1559
|
+
} else if (key.downArrow || input4 === "j") {
|
|
1560
1560
|
setScrollPosition((prev) => Math.min(maxScroll, prev + 1));
|
|
1561
|
-
} else if (key.pageDown ||
|
|
1561
|
+
} else if (key.pageDown || input4 === "d") {
|
|
1562
1562
|
setScrollPosition((prev) => Math.min(maxScroll, prev + 5));
|
|
1563
|
-
} else if (key.pageUp ||
|
|
1563
|
+
} else if (key.pageUp || input4 === "u") {
|
|
1564
1564
|
setScrollPosition((prev) => Math.max(0, prev - 5));
|
|
1565
|
-
} else if (
|
|
1565
|
+
} else if (input4 === "g") {
|
|
1566
1566
|
setScrollPosition(0);
|
|
1567
|
-
} else if (
|
|
1567
|
+
} else if (input4 === "G") {
|
|
1568
1568
|
setScrollPosition(maxScroll);
|
|
1569
1569
|
} else if (key.escape || key.backspace) {
|
|
1570
1570
|
onBack();
|
|
@@ -1960,8 +1960,8 @@ var ContextApp = ({ initialIDE, initialType, onExit }) => {
|
|
|
1960
1960
|
setLoading(false);
|
|
1961
1961
|
});
|
|
1962
1962
|
}, [initialIDE]);
|
|
1963
|
-
useInput6((
|
|
1964
|
-
if (
|
|
1963
|
+
useInput6((input4, key) => {
|
|
1964
|
+
if (input4 === "q") {
|
|
1965
1965
|
onExit();
|
|
1966
1966
|
return;
|
|
1967
1967
|
}
|
|
@@ -2823,12 +2823,471 @@ async function runSync(options) {
|
|
|
2823
2823
|
console.log("");
|
|
2824
2824
|
}
|
|
2825
2825
|
|
|
2826
|
+
// src/commands/ide/status.ts
|
|
2827
|
+
import { Command as Command9 } from "commander";
|
|
2828
|
+
|
|
2829
|
+
// src/services/ide-detection.service.ts
|
|
2830
|
+
import { promises as fs7 } from "fs";
|
|
2831
|
+
import { join as join4 } from "path";
|
|
2832
|
+
|
|
2833
|
+
// src/config/ide-formats.ts
|
|
2834
|
+
var IDE_FORMATS = {
|
|
2835
|
+
cursor: {
|
|
2836
|
+
id: "cursor",
|
|
2837
|
+
name: "Cursor",
|
|
2838
|
+
description: "Cursor IDE (.cursor/rules/)",
|
|
2839
|
+
rulesPath: ".cursor/rules",
|
|
2840
|
+
workflowsPath: ".cursor/commands",
|
|
2841
|
+
fileExtension: ".mdc",
|
|
2842
|
+
metadataFormat: "yaml-frontmatter",
|
|
2843
|
+
supportsReference: true
|
|
2844
|
+
},
|
|
2845
|
+
windsurf: {
|
|
2846
|
+
id: "windsurf",
|
|
2847
|
+
name: "Windsurf",
|
|
2848
|
+
description: "Windsurf IDE (.windsurf/rules/)",
|
|
2849
|
+
rulesPath: ".windsurf/rules",
|
|
2850
|
+
workflowsPath: ".windsurf/workflows",
|
|
2851
|
+
fileExtension: ".md",
|
|
2852
|
+
metadataFormat: "yaml-frontmatter",
|
|
2853
|
+
supportsReference: true
|
|
2854
|
+
},
|
|
2855
|
+
antigravity: {
|
|
2856
|
+
id: "antigravity",
|
|
2857
|
+
name: "Antigravity",
|
|
2858
|
+
description: "Antigravity IDE (.agent/rules/)",
|
|
2859
|
+
rulesPath: ".agent/rules",
|
|
2860
|
+
workflowsPath: ".agent/workflows",
|
|
2861
|
+
fileExtension: ".md",
|
|
2862
|
+
metadataFormat: "yaml-frontmatter",
|
|
2863
|
+
supportsReference: true
|
|
2864
|
+
},
|
|
2865
|
+
claude: {
|
|
2866
|
+
id: "claude",
|
|
2867
|
+
name: "Claude Code",
|
|
2868
|
+
description: "Claude Code (.claude/rules/)",
|
|
2869
|
+
rulesPath: ".claude/rules",
|
|
2870
|
+
fileExtension: ".md",
|
|
2871
|
+
metadataFormat: "yaml-frontmatter",
|
|
2872
|
+
supportsReference: true
|
|
2873
|
+
},
|
|
2874
|
+
agentsmd: {
|
|
2875
|
+
id: "agentsmd",
|
|
2876
|
+
name: "AGENTS.md",
|
|
2877
|
+
description: "Single AGENTS.md file",
|
|
2878
|
+
rulesPath: ".",
|
|
2879
|
+
workflowsPath: void 0,
|
|
2880
|
+
fileExtension: ".md",
|
|
2881
|
+
metadataFormat: "none",
|
|
2882
|
+
supportsReference: false
|
|
2883
|
+
},
|
|
2884
|
+
gemini: {
|
|
2885
|
+
id: "gemini",
|
|
2886
|
+
name: "Gemini CLI",
|
|
2887
|
+
description: "Gemini CLI (GEMINI.md)",
|
|
2888
|
+
rulesPath: ".",
|
|
2889
|
+
fileExtension: ".md",
|
|
2890
|
+
metadataFormat: "none",
|
|
2891
|
+
supportsReference: false,
|
|
2892
|
+
dependencies: ["agentsmd"]
|
|
2893
|
+
// Gemini requires AGENTS.md
|
|
2894
|
+
}
|
|
2895
|
+
};
|
|
2896
|
+
var JAI1_BASE_RULE = `# Jai1 Framework Agent
|
|
2897
|
+
|
|
2898
|
+
You are an AI coding assistant integrated with the Jai1 Framework.
|
|
2899
|
+
|
|
2900
|
+
## Skills Reference
|
|
2901
|
+
|
|
2902
|
+
The Jai1 Framework provides specialized skills that you can reference and apply:
|
|
2903
|
+
|
|
2904
|
+
### Pattern Detection
|
|
2905
|
+
|
|
2906
|
+
When you encounter:
|
|
2907
|
+
- **Component creation**: Reference \`@jai1-component-patterns\`
|
|
2908
|
+
- **API development**: Reference \`@jai1-api-patterns\`
|
|
2909
|
+
- **Database operations**: Reference \`@jai1-database-patterns\`
|
|
2910
|
+
- **Testing**: Reference \`@jai1-testing-patterns\`
|
|
2911
|
+
- **Deployment**: Reference \`@jai1-deployment-patterns\`
|
|
2912
|
+
|
|
2913
|
+
### Skill Loading Procedure
|
|
2914
|
+
|
|
2915
|
+
1. **Detect the task type** from user request
|
|
2916
|
+
2. **Load relevant skill** using \`@jai1-[skill-name]\` syntax
|
|
2917
|
+
3. **Apply patterns** from the skill to your response
|
|
2918
|
+
4. **Follow conventions** defined in the skill
|
|
2919
|
+
|
|
2920
|
+
Example:
|
|
2921
|
+
\`\`\`
|
|
2922
|
+
User: "Create a new API endpoint for users"
|
|
2923
|
+
Assistant:
|
|
2924
|
+
1. Loading @jai1-api-patterns
|
|
2925
|
+
2. Applying RESTful conventions
|
|
2926
|
+
3. Following project structure from @jai1-component-patterns
|
|
2927
|
+
4. Implementing validation using @jai1-testing-patterns
|
|
2928
|
+
\`\`\`
|
|
2929
|
+
|
|
2930
|
+
### Application
|
|
2931
|
+
|
|
2932
|
+
- **Always reference skills** before implementing patterns
|
|
2933
|
+
- **Follow naming conventions** from loaded skills
|
|
2934
|
+
- **Apply best practices** defined in skills
|
|
2935
|
+
- **Maintain consistency** with existing codebase patterns
|
|
2936
|
+
|
|
2937
|
+
### Naming Convention
|
|
2938
|
+
|
|
2939
|
+
Skills are named using the pattern: \`@jai1-[domain]-patterns\`
|
|
2940
|
+
|
|
2941
|
+
Examples:
|
|
2942
|
+
- \`@jai1-component-patterns\`
|
|
2943
|
+
- \`@jai1-api-patterns\`
|
|
2944
|
+
- \`@jai1-database-patterns\`
|
|
2945
|
+
- \`@jai1-testing-patterns\`
|
|
2946
|
+
- \`@jai1-deployment-patterns\`
|
|
2947
|
+
|
|
2948
|
+
## Best Practices
|
|
2949
|
+
|
|
2950
|
+
1. **Load before applying**: Always reference the appropriate skill before implementing
|
|
2951
|
+
2. **Follow project structure**: Use patterns from \`@jai1-component-patterns\`
|
|
2952
|
+
3. **Maintain consistency**: Apply conventions consistently across the codebase
|
|
2953
|
+
4. **Document decisions**: Explain why specific patterns are used
|
|
2954
|
+
|
|
2955
|
+
## Integration
|
|
2956
|
+
|
|
2957
|
+
This base rule works in conjunction with:
|
|
2958
|
+
- Project-specific rules (e.g., 01-project.md)
|
|
2959
|
+
- Technology-specific rules (e.g., 03-frontend.md)
|
|
2960
|
+
- Custom rules (e.g., 09-custom.md)
|
|
2961
|
+
- AGENTS.md (if present)
|
|
2962
|
+
|
|
2963
|
+
When conflicts arise, prioritize:
|
|
2964
|
+
1. Custom rules (09-custom.*)
|
|
2965
|
+
2. Project rules (01-project.*)
|
|
2966
|
+
3. This base rule (00-jai1.*)
|
|
2967
|
+
`;
|
|
2968
|
+
|
|
2969
|
+
// src/services/ide-detection.service.ts
|
|
2970
|
+
var IdeDetectionService = class {
|
|
2971
|
+
projectPath;
|
|
2972
|
+
constructor(projectPath = process.cwd()) {
|
|
2973
|
+
this.projectPath = projectPath;
|
|
2974
|
+
}
|
|
2975
|
+
/**
|
|
2976
|
+
* Detect all active IDEs in the project
|
|
2977
|
+
*/
|
|
2978
|
+
async detectActiveIdes() {
|
|
2979
|
+
const detected = [];
|
|
2980
|
+
for (const [ideId, format] of Object.entries(IDE_FORMATS)) {
|
|
2981
|
+
const detection = await this.detectIde(ideId, format);
|
|
2982
|
+
detected.push(detection);
|
|
2983
|
+
}
|
|
2984
|
+
return detected.filter((d) => d.detected);
|
|
2985
|
+
}
|
|
2986
|
+
/**
|
|
2987
|
+
* Detect a specific IDE
|
|
2988
|
+
*/
|
|
2989
|
+
async detectIde(ideId, format) {
|
|
2990
|
+
const detection = {
|
|
2991
|
+
id: ideId,
|
|
2992
|
+
name: format.name,
|
|
2993
|
+
detected: false,
|
|
2994
|
+
hasRules: false,
|
|
2995
|
+
hasWorkflows: false,
|
|
2996
|
+
ruleCount: 0,
|
|
2997
|
+
workflowCount: 0,
|
|
2998
|
+
confidence: "low"
|
|
2999
|
+
};
|
|
3000
|
+
if (ideId === "agentsmd") {
|
|
3001
|
+
const exists = await this.pathExists("AGENTS.md");
|
|
3002
|
+
detection.detected = exists;
|
|
3003
|
+
detection.hasRules = exists;
|
|
3004
|
+
detection.ruleCount = exists ? 1 : 0;
|
|
3005
|
+
detection.confidence = exists ? "high" : "low";
|
|
3006
|
+
return detection;
|
|
3007
|
+
}
|
|
3008
|
+
if (ideId === "gemini") {
|
|
3009
|
+
const exists = await this.pathExists("GEMINI.md");
|
|
3010
|
+
detection.detected = exists;
|
|
3011
|
+
detection.hasRules = exists;
|
|
3012
|
+
detection.ruleCount = exists ? 1 : 0;
|
|
3013
|
+
detection.confidence = exists ? "high" : "low";
|
|
3014
|
+
return detection;
|
|
3015
|
+
}
|
|
3016
|
+
const rulesPath = join4(this.projectPath, format.rulesPath);
|
|
3017
|
+
const rulesExist = await this.pathExists(rulesPath);
|
|
3018
|
+
if (rulesExist) {
|
|
3019
|
+
detection.hasRules = true;
|
|
3020
|
+
detection.ruleCount = await this.countFiles(rulesPath, format.fileExtension);
|
|
3021
|
+
if (detection.ruleCount > 0) {
|
|
3022
|
+
detection.detected = true;
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
3025
|
+
if (format.workflowsPath) {
|
|
3026
|
+
const workflowsPath = join4(this.projectPath, format.workflowsPath);
|
|
3027
|
+
const workflowsExist = await this.pathExists(workflowsPath);
|
|
3028
|
+
if (workflowsExist) {
|
|
3029
|
+
detection.hasWorkflows = true;
|
|
3030
|
+
detection.workflowCount = await this.countFiles(workflowsPath, format.fileExtension);
|
|
3031
|
+
if (detection.workflowCount > 0) {
|
|
3032
|
+
detection.detected = true;
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
if (detection.ruleCount > 3 || detection.workflowCount > 0) {
|
|
3037
|
+
detection.confidence = "high";
|
|
3038
|
+
} else if (detection.ruleCount > 0) {
|
|
3039
|
+
detection.confidence = "medium";
|
|
3040
|
+
}
|
|
3041
|
+
return detection;
|
|
3042
|
+
}
|
|
3043
|
+
/**
|
|
3044
|
+
* Get the most likely active IDE (highest confidence)
|
|
3045
|
+
*/
|
|
3046
|
+
async getPrimaryIde() {
|
|
3047
|
+
const active = await this.detectActiveIdes();
|
|
3048
|
+
if (active.length === 0) {
|
|
3049
|
+
return null;
|
|
3050
|
+
}
|
|
3051
|
+
active.sort((a, b) => {
|
|
3052
|
+
const confidenceScore = { high: 3, medium: 2, low: 1 };
|
|
3053
|
+
const aScore = confidenceScore[a.confidence] * 10 + a.ruleCount;
|
|
3054
|
+
const bScore = confidenceScore[b.confidence] * 10 + b.ruleCount;
|
|
3055
|
+
return bScore - aScore;
|
|
3056
|
+
});
|
|
3057
|
+
return active[0];
|
|
3058
|
+
}
|
|
3059
|
+
/**
|
|
3060
|
+
* Get IDE IDs that are active
|
|
3061
|
+
*/
|
|
3062
|
+
async getActiveIdeIds() {
|
|
3063
|
+
const active = await this.detectActiveIdes();
|
|
3064
|
+
return active.map((d) => d.id);
|
|
3065
|
+
}
|
|
3066
|
+
/**
|
|
3067
|
+
* Check if a specific IDE is active
|
|
3068
|
+
*/
|
|
3069
|
+
async isIdeActive(ideId) {
|
|
3070
|
+
const format = IDE_FORMATS[ideId];
|
|
3071
|
+
if (!format) {
|
|
3072
|
+
return false;
|
|
3073
|
+
}
|
|
3074
|
+
const detection = await this.detectIde(ideId, format);
|
|
3075
|
+
return detection.detected;
|
|
3076
|
+
}
|
|
3077
|
+
/**
|
|
3078
|
+
* Detect IDEs with existing rule files (for backward compatibility)
|
|
3079
|
+
*/
|
|
3080
|
+
async detectExistingIdes() {
|
|
3081
|
+
const detected = [];
|
|
3082
|
+
for (const [ideId, format] of Object.entries(IDE_FORMATS)) {
|
|
3083
|
+
if (ideId === "agentsmd") {
|
|
3084
|
+
if (await this.pathExists("AGENTS.md")) {
|
|
3085
|
+
detected.push(ideId);
|
|
3086
|
+
}
|
|
3087
|
+
} else if (ideId === "gemini") {
|
|
3088
|
+
if (await this.pathExists("GEMINI.md")) {
|
|
3089
|
+
detected.push(ideId);
|
|
3090
|
+
}
|
|
3091
|
+
} else {
|
|
3092
|
+
const rulesPath = join4(this.projectPath, format.rulesPath);
|
|
3093
|
+
if (await this.pathExists(rulesPath)) {
|
|
3094
|
+
const count = await this.countFiles(rulesPath, format.fileExtension);
|
|
3095
|
+
if (count > 0) {
|
|
3096
|
+
detected.push(ideId);
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
return detected;
|
|
3102
|
+
}
|
|
3103
|
+
/**
|
|
3104
|
+
* Get detection summary
|
|
3105
|
+
*/
|
|
3106
|
+
async getSummary() {
|
|
3107
|
+
const all = await Promise.all(
|
|
3108
|
+
Object.entries(IDE_FORMATS).map(([ideId, format]) => this.detectIde(ideId, format))
|
|
3109
|
+
);
|
|
3110
|
+
const active = all.filter((d) => d.detected);
|
|
3111
|
+
const inactive = all.filter((d) => !d.detected).map((d) => d.id);
|
|
3112
|
+
const primary = await this.getPrimaryIde();
|
|
3113
|
+
return {
|
|
3114
|
+
total: all.length,
|
|
3115
|
+
active,
|
|
3116
|
+
inactive,
|
|
3117
|
+
primary
|
|
3118
|
+
};
|
|
3119
|
+
}
|
|
3120
|
+
/**
|
|
3121
|
+
* Check if a path exists
|
|
3122
|
+
*/
|
|
3123
|
+
async pathExists(path8) {
|
|
3124
|
+
try {
|
|
3125
|
+
await fs7.access(path8);
|
|
3126
|
+
return true;
|
|
3127
|
+
} catch {
|
|
3128
|
+
return false;
|
|
3129
|
+
}
|
|
3130
|
+
}
|
|
3131
|
+
/**
|
|
3132
|
+
* Count files with specific extension in a directory
|
|
3133
|
+
*/
|
|
3134
|
+
async countFiles(dirPath, extension) {
|
|
3135
|
+
try {
|
|
3136
|
+
const entries = await fs7.readdir(dirPath, { withFileTypes: true });
|
|
3137
|
+
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(extension)).length;
|
|
3138
|
+
} catch {
|
|
3139
|
+
return 0;
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
/**
|
|
3143
|
+
* Get IDE format by ID
|
|
3144
|
+
*/
|
|
3145
|
+
getIdeFormat(ideId) {
|
|
3146
|
+
return IDE_FORMATS[ideId];
|
|
3147
|
+
}
|
|
3148
|
+
/**
|
|
3149
|
+
* Suggest IDEs to configure based on project structure
|
|
3150
|
+
*/
|
|
3151
|
+
async suggestIdes() {
|
|
3152
|
+
const suggestions = [];
|
|
3153
|
+
if (await this.pathExists(join4(this.projectPath, ".vscode"))) {
|
|
3154
|
+
suggestions.push({
|
|
3155
|
+
ideId: "cursor",
|
|
3156
|
+
name: "Cursor",
|
|
3157
|
+
reason: "VSCode configuration detected",
|
|
3158
|
+
priority: "high"
|
|
3159
|
+
});
|
|
3160
|
+
}
|
|
3161
|
+
if (await this.pathExists(join4(this.projectPath, "package.json"))) {
|
|
3162
|
+
suggestions.push({
|
|
3163
|
+
ideId: "windsurf",
|
|
3164
|
+
name: "Windsurf",
|
|
3165
|
+
reason: "JavaScript/TypeScript project detected",
|
|
3166
|
+
priority: "medium"
|
|
3167
|
+
});
|
|
3168
|
+
}
|
|
3169
|
+
suggestions.push({
|
|
3170
|
+
ideId: "agentsmd",
|
|
3171
|
+
name: "AGENTS.md",
|
|
3172
|
+
reason: "Universal format, works with all IDEs",
|
|
3173
|
+
priority: "high"
|
|
3174
|
+
});
|
|
3175
|
+
const hasClaudeConfig = await this.pathExists(join4(this.projectPath, ".claude"));
|
|
3176
|
+
if (hasClaudeConfig) {
|
|
3177
|
+
suggestions.push({
|
|
3178
|
+
ideId: "claude",
|
|
3179
|
+
name: "Claude Code",
|
|
3180
|
+
reason: ".claude directory detected",
|
|
3181
|
+
priority: "high"
|
|
3182
|
+
});
|
|
3183
|
+
}
|
|
3184
|
+
return suggestions;
|
|
3185
|
+
}
|
|
3186
|
+
};
|
|
3187
|
+
|
|
3188
|
+
// src/commands/ide/status.ts
|
|
3189
|
+
function createStatusSubcommand() {
|
|
3190
|
+
const cmd = new Command9("status").description("Show detected IDEs and their configuration status").option("--json", "Output as JSON").option("--suggestions", "Show IDE suggestions based on project structure").action(async (options) => {
|
|
3191
|
+
await runStatus(options);
|
|
3192
|
+
});
|
|
3193
|
+
return cmd;
|
|
3194
|
+
}
|
|
3195
|
+
async function runStatus(options) {
|
|
3196
|
+
const service = new IdeDetectionService();
|
|
3197
|
+
if (options.json) {
|
|
3198
|
+
const summary2 = await service.getSummary();
|
|
3199
|
+
console.log(JSON.stringify(summary2, null, 2));
|
|
3200
|
+
return;
|
|
3201
|
+
}
|
|
3202
|
+
console.log("\n\u{1F50D} IDE Detection Status\n");
|
|
3203
|
+
const summary = await service.getSummary();
|
|
3204
|
+
if (summary.active.length === 0) {
|
|
3205
|
+
console.log("\u26A0\uFE0F No active IDEs detected\n");
|
|
3206
|
+
console.log("\u{1F4A1} Tips:");
|
|
3207
|
+
console.log(' \u2022 Run "jai1 rules apply" to configure IDE rules');
|
|
3208
|
+
console.log(" \u2022 Create rules in .cursor/rules/, .windsurf/rules/, etc.");
|
|
3209
|
+
console.log(" \u2022 Create AGENTS.md for universal IDE support\n");
|
|
3210
|
+
} else {
|
|
3211
|
+
console.log(`\u{1F4CA} Found ${summary.active.length} active IDE(s):
|
|
3212
|
+
`);
|
|
3213
|
+
if (summary.primary) {
|
|
3214
|
+
const confidence = getConfidenceEmoji(summary.primary.confidence);
|
|
3215
|
+
console.log(`\u{1F3AF} Primary IDE: ${summary.primary.name} ${confidence}
|
|
3216
|
+
`);
|
|
3217
|
+
}
|
|
3218
|
+
for (const ide of summary.active) {
|
|
3219
|
+
const confidence = getConfidenceEmoji(ide.confidence);
|
|
3220
|
+
const isPrimary = summary.primary?.id === ide.id;
|
|
3221
|
+
const marker = isPrimary ? "\u{1F3AF}" : " ";
|
|
3222
|
+
console.log(`${marker} ${ide.name} ${confidence}`);
|
|
3223
|
+
if (ide.hasRules) {
|
|
3224
|
+
console.log(` Rules: ${ide.ruleCount} file(s)`);
|
|
3225
|
+
}
|
|
3226
|
+
if (ide.hasWorkflows) {
|
|
3227
|
+
console.log(` Workflows: ${ide.workflowCount} file(s)`);
|
|
3228
|
+
}
|
|
3229
|
+
const format = service.getIdeFormat(ide.id);
|
|
3230
|
+
if (format) {
|
|
3231
|
+
if (ide.id === "agentsmd") {
|
|
3232
|
+
console.log(` Location: AGENTS.md`);
|
|
3233
|
+
} else if (ide.id === "gemini") {
|
|
3234
|
+
console.log(` Location: GEMINI.md`);
|
|
3235
|
+
} else {
|
|
3236
|
+
console.log(` Location: ${format.rulesPath}/`);
|
|
3237
|
+
}
|
|
3238
|
+
}
|
|
3239
|
+
console.log("");
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
3242
|
+
if (summary.inactive.length > 0) {
|
|
3243
|
+
console.log(`\u{1F4CB} Available IDEs (not configured):
|
|
3244
|
+
`);
|
|
3245
|
+
for (const ideId of summary.inactive) {
|
|
3246
|
+
const format = service.getIdeFormat(ideId);
|
|
3247
|
+
if (format) {
|
|
3248
|
+
console.log(` \u2022 ${format.name} - ${format.description}`);
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3251
|
+
console.log("");
|
|
3252
|
+
}
|
|
3253
|
+
if (options.suggestions) {
|
|
3254
|
+
const suggestions = await service.suggestIdes();
|
|
3255
|
+
if (suggestions.length > 0) {
|
|
3256
|
+
console.log("\u{1F4A1} Suggested IDEs for this project:\n");
|
|
3257
|
+
for (const suggestion of suggestions) {
|
|
3258
|
+
const priorityEmoji = suggestion.priority === "high" ? "\u2B50" : suggestion.priority === "medium" ? "\u{1F538}" : "\u25AB\uFE0F";
|
|
3259
|
+
console.log(`${priorityEmoji} ${suggestion.name}`);
|
|
3260
|
+
console.log(` Reason: ${suggestion.reason}
|
|
3261
|
+
`);
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
}
|
|
3265
|
+
console.log("\u{1F4DD} Commands:\n");
|
|
3266
|
+
console.log(' \u2022 "jai1 rules apply" - Configure rules for IDEs');
|
|
3267
|
+
console.log(' \u2022 "jai1 rules sync" - Regenerate IDE outputs');
|
|
3268
|
+
console.log(' \u2022 "jai1 ide sync" - Sync .jai1/ content to IDEs');
|
|
3269
|
+
console.log(' \u2022 "jai1 ide status --suggestions" - Get IDE recommendations\n');
|
|
3270
|
+
}
|
|
3271
|
+
function getConfidenceEmoji(confidence) {
|
|
3272
|
+
switch (confidence) {
|
|
3273
|
+
case "high":
|
|
3274
|
+
return "\u{1F7E2}";
|
|
3275
|
+
case "medium":
|
|
3276
|
+
return "\u{1F7E1}";
|
|
3277
|
+
case "low":
|
|
3278
|
+
return "\u{1F534}";
|
|
3279
|
+
default:
|
|
3280
|
+
return "\u26AA";
|
|
3281
|
+
}
|
|
3282
|
+
}
|
|
3283
|
+
|
|
2826
3284
|
// src/commands/ide/index.ts
|
|
2827
3285
|
function createIdeCommand() {
|
|
2828
|
-
const ideCommand = new
|
|
3286
|
+
const ideCommand = new Command10("ide").description("IDE integration and configuration commands");
|
|
2829
3287
|
ideCommand.addCommand(createContextSubcommand());
|
|
2830
3288
|
ideCommand.addCommand(createSetupSubcommand());
|
|
2831
3289
|
ideCommand.addCommand(createSyncSubcommand());
|
|
3290
|
+
ideCommand.addCommand(createStatusSubcommand());
|
|
2832
3291
|
ideCommand.action(() => {
|
|
2833
3292
|
ideCommand.help();
|
|
2834
3293
|
});
|
|
@@ -2838,7 +3297,7 @@ function createIdeCommand() {
|
|
|
2838
3297
|
// src/commands/learn.ts
|
|
2839
3298
|
import React20 from "react";
|
|
2840
3299
|
import { render as render3 } from "ink";
|
|
2841
|
-
import { Command as
|
|
3300
|
+
import { Command as Command11 } from "commander";
|
|
2842
3301
|
|
|
2843
3302
|
// src/ui/guide/GuideApp.tsx
|
|
2844
3303
|
import React19, { useState as useState9, useEffect as useEffect3 } from "react";
|
|
@@ -2888,7 +3347,7 @@ var MENU_ITEMS = [
|
|
|
2888
3347
|
];
|
|
2889
3348
|
var MenuView = ({ onSelect }) => {
|
|
2890
3349
|
const [selectedIndex, setSelectedIndex] = useState7(0);
|
|
2891
|
-
useInput7((
|
|
3350
|
+
useInput7((input4, key) => {
|
|
2892
3351
|
if (key.upArrow) {
|
|
2893
3352
|
setSelectedIndex((prev) => Math.max(0, prev - 1));
|
|
2894
3353
|
} else if (key.downArrow) {
|
|
@@ -3501,7 +3960,7 @@ var AgenticGuideView = ({ onBack }) => {
|
|
|
3501
3960
|
const [isTyping, setIsTyping] = useState8(false);
|
|
3502
3961
|
const [selectedSuggestion, setSelectedSuggestion] = useState8(0);
|
|
3503
3962
|
const [focusArea, setFocusArea] = useState8("input");
|
|
3504
|
-
useInput8((
|
|
3963
|
+
useInput8((input4, key) => {
|
|
3505
3964
|
if (key.escape) {
|
|
3506
3965
|
onBack();
|
|
3507
3966
|
return;
|
|
@@ -3633,7 +4092,7 @@ var GuideApp = ({ initialTopic, onExit }) => {
|
|
|
3633
4092
|
setCurrentTopic(TOPIC_MAP[initialTopic]);
|
|
3634
4093
|
}
|
|
3635
4094
|
}, [initialTopic]);
|
|
3636
|
-
useInput9((
|
|
4095
|
+
useInput9((input4, key) => {
|
|
3637
4096
|
if (key.escape || currentTopic !== "menu" && key.backspace) {
|
|
3638
4097
|
if (currentTopic === "menu") {
|
|
3639
4098
|
onExit();
|
|
@@ -3643,7 +4102,7 @@ var GuideApp = ({ initialTopic, onExit }) => {
|
|
|
3643
4102
|
}
|
|
3644
4103
|
return;
|
|
3645
4104
|
}
|
|
3646
|
-
if (
|
|
4105
|
+
if (input4 === "q") {
|
|
3647
4106
|
onExit();
|
|
3648
4107
|
return;
|
|
3649
4108
|
}
|
|
@@ -3692,7 +4151,7 @@ var GuideApp = ({ initialTopic, onExit }) => {
|
|
|
3692
4151
|
|
|
3693
4152
|
// src/commands/learn.ts
|
|
3694
4153
|
function createLearnCommand() {
|
|
3695
|
-
const cmd = new
|
|
4154
|
+
const cmd = new Command11("learn").description("Interactive Agentic Coding learning center").option("--topic <topic>", "Open specific topic (intro, rules, workflows, prompts, skills)").action(async (options) => {
|
|
3696
4155
|
const { waitUntilExit } = render3(
|
|
3697
4156
|
React20.createElement(GuideApp, {
|
|
3698
4157
|
initialTopic: options.topic,
|
|
@@ -3707,7 +4166,7 @@ function createLearnCommand() {
|
|
|
3707
4166
|
}
|
|
3708
4167
|
|
|
3709
4168
|
// src/commands/chat.ts
|
|
3710
|
-
import { Command as
|
|
4169
|
+
import { Command as Command12 } from "commander";
|
|
3711
4170
|
import React26 from "react";
|
|
3712
4171
|
import { render as render4 } from "ink";
|
|
3713
4172
|
|
|
@@ -4142,7 +4601,7 @@ var ModelSelector = ({
|
|
|
4142
4601
|
const allowedModels = models.filter((m) => m.allowed);
|
|
4143
4602
|
const currentIndex = allowedModels.findIndex((m) => m.id === currentModel);
|
|
4144
4603
|
const [selectedIndex, setSelectedIndex] = useState12(Math.max(0, currentIndex));
|
|
4145
|
-
useInput10((
|
|
4604
|
+
useInput10((input4, key) => {
|
|
4146
4605
|
if (key.escape) {
|
|
4147
4606
|
onCancel();
|
|
4148
4607
|
return;
|
|
@@ -4268,7 +4727,7 @@ var ChatApp = ({ service, initialModel }) => {
|
|
|
4268
4727
|
}
|
|
4269
4728
|
return false;
|
|
4270
4729
|
}, [refetch, exit]);
|
|
4271
|
-
useInput11((
|
|
4730
|
+
useInput11((input4, key) => {
|
|
4272
4731
|
if (showSlashMenu) {
|
|
4273
4732
|
if (key.upArrow) {
|
|
4274
4733
|
setSlashMenuIndex((i) => Math.max(0, i - 1));
|
|
@@ -4296,7 +4755,7 @@ var ChatApp = ({ service, initialModel }) => {
|
|
|
4296
4755
|
}
|
|
4297
4756
|
return;
|
|
4298
4757
|
}
|
|
4299
|
-
if (currentView === "error" && (key.return ||
|
|
4758
|
+
if (currentView === "error" && (key.return || input4 === "r")) {
|
|
4300
4759
|
setCurrentView("loading");
|
|
4301
4760
|
refetch();
|
|
4302
4761
|
return;
|
|
@@ -4386,14 +4845,14 @@ async function handleChatCommand(options) {
|
|
|
4386
4845
|
clear();
|
|
4387
4846
|
}
|
|
4388
4847
|
function createChatCommand() {
|
|
4389
|
-
const cmd = new
|
|
4848
|
+
const cmd = new Command12("chat").description("Interactive AI chat with Jai1 LLM Proxy").option("--model <model>", "Initial model to use (e.g., gpt-4o, claude-3-opus)").action(async (options) => {
|
|
4390
4849
|
await handleChatCommand(options);
|
|
4391
4850
|
});
|
|
4392
4851
|
return cmd;
|
|
4393
4852
|
}
|
|
4394
4853
|
|
|
4395
4854
|
// src/commands/openai-keys.ts
|
|
4396
|
-
import { Command as
|
|
4855
|
+
import { Command as Command13 } from "commander";
|
|
4397
4856
|
function maskKey2(key) {
|
|
4398
4857
|
if (key.length <= 8) return "****";
|
|
4399
4858
|
return key.slice(0, 8) + "****" + key.slice(-4);
|
|
@@ -4447,14 +4906,14 @@ async function handleOpenAiKeysCommand(options) {
|
|
|
4447
4906
|
}
|
|
4448
4907
|
}
|
|
4449
4908
|
function createOpenAiKeysCommand() {
|
|
4450
|
-
const cmd = new
|
|
4909
|
+
const cmd = new Command13("openai-keys").description("Show OpenAI-compatible API credentials and info").option("--full", "Show full API key (unmasked)").action(async (options) => {
|
|
4451
4910
|
await handleOpenAiKeysCommand(options);
|
|
4452
4911
|
});
|
|
4453
4912
|
return cmd;
|
|
4454
4913
|
}
|
|
4455
4914
|
|
|
4456
4915
|
// src/commands/stats.ts
|
|
4457
|
-
import { Command as
|
|
4916
|
+
import { Command as Command14 } from "commander";
|
|
4458
4917
|
async function handleStatsCommand() {
|
|
4459
4918
|
const configService = new ConfigService();
|
|
4460
4919
|
const config = await configService.load();
|
|
@@ -4524,17 +4983,17 @@ async function handleStatsCommand() {
|
|
|
4524
4983
|
}
|
|
4525
4984
|
}
|
|
4526
4985
|
function createStatsCommand() {
|
|
4527
|
-
const cmd = new
|
|
4986
|
+
const cmd = new Command14("stats").description("Hi\u1EC3n th\u1ECB th\u1ED1ng k\xEA s\u1EED d\u1EE5ng LLM v\xE0 gi\u1EDBi h\u1EA1n").action(async () => {
|
|
4528
4987
|
await handleStatsCommand();
|
|
4529
4988
|
});
|
|
4530
4989
|
return cmd;
|
|
4531
4990
|
}
|
|
4532
4991
|
|
|
4533
4992
|
// src/commands/translate.ts
|
|
4534
|
-
import { Command as
|
|
4993
|
+
import { Command as Command15 } from "commander";
|
|
4535
4994
|
|
|
4536
4995
|
// src/services/translation.service.ts
|
|
4537
|
-
import { promises as
|
|
4996
|
+
import { promises as fs8 } from "fs";
|
|
4538
4997
|
import path4 from "path";
|
|
4539
4998
|
import pLimit from "p-limit";
|
|
4540
4999
|
import pRetry from "p-retry";
|
|
@@ -4551,9 +5010,9 @@ var TranslationService = class {
|
|
|
4551
5010
|
/**
|
|
4552
5011
|
* Detect input type
|
|
4553
5012
|
*/
|
|
4554
|
-
async detectInputType(
|
|
5013
|
+
async detectInputType(input4) {
|
|
4555
5014
|
try {
|
|
4556
|
-
const stat = await
|
|
5015
|
+
const stat = await fs8.stat(input4);
|
|
4557
5016
|
if (stat.isDirectory()) return "folder";
|
|
4558
5017
|
if (stat.isFile()) return "file";
|
|
4559
5018
|
} catch {
|
|
@@ -4590,13 +5049,13 @@ var TranslationService = class {
|
|
|
4590
5049
|
*/
|
|
4591
5050
|
async translateFile(filePath) {
|
|
4592
5051
|
try {
|
|
4593
|
-
const content = await
|
|
5052
|
+
const content = await fs8.readFile(filePath, "utf-8");
|
|
4594
5053
|
const ext = path4.extname(filePath).toLowerCase();
|
|
4595
5054
|
const fileType = this.getFileType(ext);
|
|
4596
5055
|
const translatedContent = await this.translateWithRetry(content, fileType);
|
|
4597
5056
|
const outputPath = this.generateOutputPath(filePath);
|
|
4598
5057
|
if (!this.options.dryRun) {
|
|
4599
|
-
await
|
|
5058
|
+
await fs8.writeFile(outputPath, translatedContent, "utf-8");
|
|
4600
5059
|
}
|
|
4601
5060
|
return {
|
|
4602
5061
|
inputPath: filePath,
|
|
@@ -4661,7 +5120,7 @@ var TranslationService = class {
|
|
|
4661
5120
|
* Discover translatable files in folder recursively
|
|
4662
5121
|
*/
|
|
4663
5122
|
async discoverFiles(folderPath) {
|
|
4664
|
-
const entries = await
|
|
5123
|
+
const entries = await fs8.readdir(folderPath, { withFileTypes: true });
|
|
4665
5124
|
const files = [];
|
|
4666
5125
|
for (const entry of entries) {
|
|
4667
5126
|
const fullPath = path4.join(folderPath, entry.name);
|
|
@@ -4785,7 +5244,7 @@ Return ONLY the translated content, no explanations.`;
|
|
|
4785
5244
|
};
|
|
4786
5245
|
|
|
4787
5246
|
// src/commands/translate.ts
|
|
4788
|
-
async function handleTranslate(
|
|
5247
|
+
async function handleTranslate(input4, options) {
|
|
4789
5248
|
const configService = new ConfigService();
|
|
4790
5249
|
const config = await configService.load();
|
|
4791
5250
|
if (!config) {
|
|
@@ -4793,16 +5252,16 @@ async function handleTranslate(input3, options) {
|
|
|
4793
5252
|
}
|
|
4794
5253
|
const llmService = new LlmProxyService(config);
|
|
4795
5254
|
const translationService = new TranslationService(llmService, options, options.model);
|
|
4796
|
-
const inputType = await translationService.detectInputType(
|
|
5255
|
+
const inputType = await translationService.detectInputType(input4);
|
|
4797
5256
|
switch (inputType) {
|
|
4798
5257
|
case "text":
|
|
4799
|
-
await handleTextTranslation(translationService,
|
|
5258
|
+
await handleTextTranslation(translationService, input4, options);
|
|
4800
5259
|
break;
|
|
4801
5260
|
case "file":
|
|
4802
|
-
await handleFileTranslation(translationService,
|
|
5261
|
+
await handleFileTranslation(translationService, input4, options);
|
|
4803
5262
|
break;
|
|
4804
5263
|
case "folder":
|
|
4805
|
-
await handleFolderTranslation(translationService,
|
|
5264
|
+
await handleFolderTranslation(translationService, input4, options);
|
|
4806
5265
|
break;
|
|
4807
5266
|
}
|
|
4808
5267
|
}
|
|
@@ -4879,17 +5338,17 @@ async function handleFolderTranslation(service, folderPath, options) {
|
|
|
4879
5338
|
}
|
|
4880
5339
|
}
|
|
4881
5340
|
function createTranslateCommand() {
|
|
4882
|
-
const cmd = new
|
|
4883
|
-
await handleTranslate(
|
|
5341
|
+
const cmd = new Command15("translate").description("D\u1ECBch v\u0103n b\u1EA3n, file ho\u1EB7c th\u01B0 m\u1EE5c b\u1EB1ng AI").argument("<input>", "Chu\u1ED7i v\u0103n b\u1EA3n, \u0111\u01B0\u1EDDng d\u1EABn file ho\u1EB7c th\u01B0 m\u1EE5c").option("--to <language>", "M\xE3 ng\xF4n ng\u1EEF \u0111\xEDch (m\u1EB7c \u0111\u1ECBnh: vi)", "vi").option("-o, --output <path>", "\u0110\u01B0\u1EDDng d\u1EABn output (file/th\u01B0 m\u1EE5c)").option("--dry-run", "Xem tr\u01B0\u1EDBc kh\xF4ng l\u01B0u file").option("--concurrency <number>", "S\u1ED1 file x\u1EED l\xFD song song (m\u1EB7c \u0111\u1ECBnh: 3)", "3").option("--model <model>", "Model LLM (m\u1EB7c \u0111\u1ECBnh: gpt-5.1-codex-mini)").option("--json", "Xu\u1EA5t k\u1EBFt qu\u1EA3 d\u1EA1ng JSON").action(async (input4, options) => {
|
|
5342
|
+
await handleTranslate(input4, options);
|
|
4884
5343
|
});
|
|
4885
5344
|
return cmd;
|
|
4886
5345
|
}
|
|
4887
5346
|
|
|
4888
5347
|
// src/commands/utils/index.ts
|
|
4889
|
-
import { Command as
|
|
5348
|
+
import { Command as Command29 } from "commander";
|
|
4890
5349
|
|
|
4891
5350
|
// src/commands/utils/password.ts
|
|
4892
|
-
import { Command as
|
|
5351
|
+
import { Command as Command16 } from "commander";
|
|
4893
5352
|
|
|
4894
5353
|
// src/services/utils.service.ts
|
|
4895
5354
|
import crypto from "crypto";
|
|
@@ -4940,12 +5399,12 @@ var UtilsService = class {
|
|
|
4940
5399
|
/**
|
|
4941
5400
|
* Hash text or file using specified algorithm
|
|
4942
5401
|
*/
|
|
4943
|
-
async hash(
|
|
5402
|
+
async hash(input4, algorithm) {
|
|
4944
5403
|
if (algorithm === "bcrypt") {
|
|
4945
5404
|
throw new Error("Use hashBcrypt for bcrypt algorithm");
|
|
4946
5405
|
}
|
|
4947
5406
|
const hash = crypto.createHash(algorithm);
|
|
4948
|
-
hash.update(
|
|
5407
|
+
hash.update(input4);
|
|
4949
5408
|
return hash.digest("hex");
|
|
4950
5409
|
}
|
|
4951
5410
|
/**
|
|
@@ -4958,14 +5417,14 @@ var UtilsService = class {
|
|
|
4958
5417
|
/**
|
|
4959
5418
|
* Hash using bcrypt
|
|
4960
5419
|
*/
|
|
4961
|
-
async hashBcrypt(
|
|
4962
|
-
return bcrypt.hash(
|
|
5420
|
+
async hashBcrypt(input4, rounds = 10) {
|
|
5421
|
+
return bcrypt.hash(input4, rounds);
|
|
4963
5422
|
}
|
|
4964
5423
|
/**
|
|
4965
5424
|
* Base64 encode
|
|
4966
5425
|
*/
|
|
4967
|
-
base64Encode(
|
|
4968
|
-
const buffer = typeof
|
|
5426
|
+
base64Encode(input4, urlSafe = false) {
|
|
5427
|
+
const buffer = typeof input4 === "string" ? Buffer.from(input4, "utf-8") : input4;
|
|
4969
5428
|
let encoded = buffer.toString("base64");
|
|
4970
5429
|
if (urlSafe) {
|
|
4971
5430
|
encoded = encoded.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
@@ -4975,8 +5434,8 @@ var UtilsService = class {
|
|
|
4975
5434
|
/**
|
|
4976
5435
|
* Base64 decode
|
|
4977
5436
|
*/
|
|
4978
|
-
base64Decode(
|
|
4979
|
-
let normalized =
|
|
5437
|
+
base64Decode(input4) {
|
|
5438
|
+
let normalized = input4.replace(/-/g, "+").replace(/_/g, "/");
|
|
4980
5439
|
while (normalized.length % 4) {
|
|
4981
5440
|
normalized += "=";
|
|
4982
5441
|
}
|
|
@@ -5105,14 +5564,14 @@ var UtilsService = class {
|
|
|
5105
5564
|
/**
|
|
5106
5565
|
* URL encode
|
|
5107
5566
|
*/
|
|
5108
|
-
urlEncode(
|
|
5109
|
-
return full ? encodeURI(
|
|
5567
|
+
urlEncode(input4, full = false) {
|
|
5568
|
+
return full ? encodeURI(input4) : encodeURIComponent(input4);
|
|
5110
5569
|
}
|
|
5111
5570
|
/**
|
|
5112
5571
|
* URL decode
|
|
5113
5572
|
*/
|
|
5114
|
-
urlDecode(
|
|
5115
|
-
return full ? decodeURI(
|
|
5573
|
+
urlDecode(input4, full = false) {
|
|
5574
|
+
return full ? decodeURI(input4) : decodeURIComponent(input4);
|
|
5116
5575
|
}
|
|
5117
5576
|
/**
|
|
5118
5577
|
* Format bytes to human-readable size
|
|
@@ -5158,7 +5617,7 @@ async function handlePasswordGeneration(options) {
|
|
|
5158
5617
|
}
|
|
5159
5618
|
}
|
|
5160
5619
|
function createPasswordCommand() {
|
|
5161
|
-
const cmd = new
|
|
5620
|
+
const cmd = new Command16("password").description("Generate secure random password").option("-l, --length <number>", "Password length", "16").option("--no-lowercase", "Exclude lowercase letters").option("--no-uppercase", "Exclude uppercase letters").option("--no-digits", "Exclude digits").option("--no-symbols", "Exclude symbols").option("--symbol-chars <chars>", "Custom symbol characters").option("-c, --count <number>", "Number of passwords to generate", "1").addHelpText("after", `
|
|
5162
5621
|
Examples:
|
|
5163
5622
|
$ jai1 utils password
|
|
5164
5623
|
$ jai1 utils password --length 24
|
|
@@ -5176,7 +5635,7 @@ Examples:
|
|
|
5176
5635
|
}
|
|
5177
5636
|
|
|
5178
5637
|
// src/commands/utils/uuid.ts
|
|
5179
|
-
import { Command as
|
|
5638
|
+
import { Command as Command17 } from "commander";
|
|
5180
5639
|
async function handleUuidGeneration(options) {
|
|
5181
5640
|
const service = new UtilsService();
|
|
5182
5641
|
try {
|
|
@@ -5204,7 +5663,7 @@ async function handleUuidGeneration(options) {
|
|
|
5204
5663
|
}
|
|
5205
5664
|
}
|
|
5206
5665
|
function createUuidCommand() {
|
|
5207
|
-
const cmd = new
|
|
5666
|
+
const cmd = new Command17("uuid").description("Generate UUID v4 identifier").option("-c, --count <number>", "Number of UUIDs to generate", "1").option("--uppercase", "Output in uppercase").option("--no-hyphens", "Remove hyphens from output").addHelpText("after", `
|
|
5208
5667
|
Examples:
|
|
5209
5668
|
$ jai1 utils uuid
|
|
5210
5669
|
$ jai1 utils uuid --count 10
|
|
@@ -5221,8 +5680,8 @@ Examples:
|
|
|
5221
5680
|
}
|
|
5222
5681
|
|
|
5223
5682
|
// src/commands/utils/hash.ts
|
|
5224
|
-
import { Command as
|
|
5225
|
-
async function handleHashGeneration(
|
|
5683
|
+
import { Command as Command18 } from "commander";
|
|
5684
|
+
async function handleHashGeneration(input4, options) {
|
|
5226
5685
|
const service = new UtilsService();
|
|
5227
5686
|
try {
|
|
5228
5687
|
let hash;
|
|
@@ -5233,14 +5692,14 @@ async function handleHashGeneration(input3, options) {
|
|
|
5233
5692
|
}
|
|
5234
5693
|
hash = await service.hashFile(options.file, options.algorithm);
|
|
5235
5694
|
} else {
|
|
5236
|
-
if (!
|
|
5695
|
+
if (!input4) {
|
|
5237
5696
|
console.error("\u274C Please provide input text or use --file option");
|
|
5238
5697
|
process.exit(1);
|
|
5239
5698
|
}
|
|
5240
5699
|
if (options.algorithm === "bcrypt") {
|
|
5241
|
-
hash = await service.hashBcrypt(
|
|
5700
|
+
hash = await service.hashBcrypt(input4, options.rounds);
|
|
5242
5701
|
} else {
|
|
5243
|
-
hash = await service.hash(
|
|
5702
|
+
hash = await service.hash(input4, options.algorithm);
|
|
5244
5703
|
}
|
|
5245
5704
|
}
|
|
5246
5705
|
console.log("\u{1F512} Hash Result:");
|
|
@@ -5261,7 +5720,7 @@ async function handleHashGeneration(input3, options) {
|
|
|
5261
5720
|
}
|
|
5262
5721
|
}
|
|
5263
5722
|
function createHashCommand() {
|
|
5264
|
-
const cmd = new
|
|
5723
|
+
const cmd = new Command18("hash").description("Generate hash (MD5, SHA, bcrypt)").argument("[input]", "Text to hash").option(
|
|
5265
5724
|
"-a, --algorithm <algorithm>",
|
|
5266
5725
|
"Hash algorithm (md5, sha1, sha256, sha512, bcrypt)",
|
|
5267
5726
|
"sha256"
|
|
@@ -5273,8 +5732,8 @@ Examples:
|
|
|
5273
5732
|
$ jai1 utils hash "password" --algorithm bcrypt --rounds 12
|
|
5274
5733
|
$ jai1 utils hash --file ./myfile.txt
|
|
5275
5734
|
$ jai1 utils hash --file ./image.png --algorithm sha512
|
|
5276
|
-
`).action(async (
|
|
5277
|
-
await handleHashGeneration(
|
|
5735
|
+
`).action(async (input4, options) => {
|
|
5736
|
+
await handleHashGeneration(input4, {
|
|
5278
5737
|
...options,
|
|
5279
5738
|
rounds: parseInt(options.rounds, 10)
|
|
5280
5739
|
});
|
|
@@ -5283,9 +5742,9 @@ Examples:
|
|
|
5283
5742
|
}
|
|
5284
5743
|
|
|
5285
5744
|
// src/commands/utils/base64-encode.ts
|
|
5286
|
-
import { Command as
|
|
5745
|
+
import { Command as Command19 } from "commander";
|
|
5287
5746
|
import { readFile as readFile2 } from "fs/promises";
|
|
5288
|
-
async function handleBase64Encode(
|
|
5747
|
+
async function handleBase64Encode(input4, options) {
|
|
5289
5748
|
const service = new UtilsService();
|
|
5290
5749
|
try {
|
|
5291
5750
|
let encoded;
|
|
@@ -5293,11 +5752,11 @@ async function handleBase64Encode(input3, options) {
|
|
|
5293
5752
|
const content = await readFile2(options.file);
|
|
5294
5753
|
encoded = service.base64Encode(content, options.urlSafe);
|
|
5295
5754
|
} else {
|
|
5296
|
-
if (!
|
|
5755
|
+
if (!input4) {
|
|
5297
5756
|
console.error("\u274C Please provide input text or use --file option");
|
|
5298
5757
|
process.exit(1);
|
|
5299
5758
|
}
|
|
5300
|
-
encoded = service.base64Encode(
|
|
5759
|
+
encoded = service.base64Encode(input4, options.urlSafe);
|
|
5301
5760
|
}
|
|
5302
5761
|
console.log("\u{1F4DD} Base64 Encoded:");
|
|
5303
5762
|
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
@@ -5316,33 +5775,33 @@ async function handleBase64Encode(input3, options) {
|
|
|
5316
5775
|
}
|
|
5317
5776
|
}
|
|
5318
5777
|
function createBase64EncodeCommand() {
|
|
5319
|
-
const cmd = new
|
|
5778
|
+
const cmd = new Command19("base64-encode").description("Encode text or file to Base64").argument("[input]", "Text to encode").option("-f, --file <path>", "Encode file contents").option("--url-safe", "Use URL-safe Base64 encoding").addHelpText("after", `
|
|
5320
5779
|
Examples:
|
|
5321
5780
|
$ jai1 utils base64-encode "hello world"
|
|
5322
5781
|
$ jai1 utils base64-encode "hello world" --url-safe
|
|
5323
5782
|
$ jai1 utils base64-encode --file ./document.txt
|
|
5324
5783
|
$ jai1 utils base64-encode --file ./image.png
|
|
5325
|
-
`).action(async (
|
|
5326
|
-
await handleBase64Encode(
|
|
5784
|
+
`).action(async (input4, options) => {
|
|
5785
|
+
await handleBase64Encode(input4, options);
|
|
5327
5786
|
});
|
|
5328
5787
|
return cmd;
|
|
5329
5788
|
}
|
|
5330
5789
|
|
|
5331
5790
|
// src/commands/utils/base64-decode.ts
|
|
5332
|
-
import { Command as
|
|
5791
|
+
import { Command as Command20 } from "commander";
|
|
5333
5792
|
import { readFile as readFile3, writeFile } from "fs/promises";
|
|
5334
|
-
async function handleBase64Decode(
|
|
5793
|
+
async function handleBase64Decode(input4, options) {
|
|
5335
5794
|
const service = new UtilsService();
|
|
5336
5795
|
try {
|
|
5337
5796
|
let encodedInput;
|
|
5338
5797
|
if (options.file) {
|
|
5339
5798
|
encodedInput = await readFile3(options.file, "utf-8");
|
|
5340
5799
|
} else {
|
|
5341
|
-
if (!
|
|
5800
|
+
if (!input4) {
|
|
5342
5801
|
console.error("\u274C Please provide Base64 input or use --file option");
|
|
5343
5802
|
process.exit(1);
|
|
5344
5803
|
}
|
|
5345
|
-
encodedInput =
|
|
5804
|
+
encodedInput = input4;
|
|
5346
5805
|
}
|
|
5347
5806
|
const decoded = service.base64Decode(encodedInput.trim());
|
|
5348
5807
|
if (options.output) {
|
|
@@ -5361,20 +5820,20 @@ async function handleBase64Decode(input3, options) {
|
|
|
5361
5820
|
}
|
|
5362
5821
|
}
|
|
5363
5822
|
function createBase64DecodeCommand() {
|
|
5364
|
-
const cmd = new
|
|
5823
|
+
const cmd = new Command20("base64-decode").description("Decode Base64 string").argument("[input]", "Base64 string to decode").option("-f, --file <path>", "Decode from file").option("-o, --output <path>", "Write decoded output to file").addHelpText("after", `
|
|
5365
5824
|
Examples:
|
|
5366
5825
|
$ jai1 utils base64-decode "aGVsbG8gd29ybGQ="
|
|
5367
5826
|
$ jai1 utils base64-decode --file ./encoded.txt
|
|
5368
5827
|
$ jai1 utils base64-decode "aGVsbG8=" --output ./decoded.txt
|
|
5369
5828
|
$ jai1 utils base64-decode --file ./encoded.txt --output ./image.png
|
|
5370
|
-
`).action(async (
|
|
5371
|
-
await handleBase64Decode(
|
|
5829
|
+
`).action(async (input4, options) => {
|
|
5830
|
+
await handleBase64Decode(input4, options);
|
|
5372
5831
|
});
|
|
5373
5832
|
return cmd;
|
|
5374
5833
|
}
|
|
5375
5834
|
|
|
5376
5835
|
// src/commands/utils/http.ts
|
|
5377
|
-
import { Command as
|
|
5836
|
+
import { Command as Command21 } from "commander";
|
|
5378
5837
|
import { readFile as readFile4 } from "fs/promises";
|
|
5379
5838
|
async function handleHttpRequest(url, options) {
|
|
5380
5839
|
const service = new UtilsService();
|
|
@@ -5440,7 +5899,7 @@ async function handleHttpRequest(url, options) {
|
|
|
5440
5899
|
}
|
|
5441
5900
|
}
|
|
5442
5901
|
function createHttpCommand() {
|
|
5443
|
-
const cmd = new
|
|
5902
|
+
const cmd = new Command21("http").description("Make HTTP request with formatted output").argument("<url>", "Target URL").option("-X, --method <method>", "HTTP method", "GET").option("-H, --header <header...>", "Request headers (repeatable)").option("-d, --data <data>", "Request body (JSON string)").option("--file <path>", "Read body from file").option("--timeout <ms>", "Request timeout in milliseconds", "30000").option("--no-follow", "Do not follow redirects").option("--only-headers", "Show only response headers").option("--only-body", "Show only response body").addHelpText("after", `
|
|
5444
5903
|
Examples:
|
|
5445
5904
|
$ jai1 utils http https://api.example.com/users
|
|
5446
5905
|
$ jai1 utils http https://api.example.com/users -X POST -d '{"name":"John"}'
|
|
@@ -5458,7 +5917,7 @@ Examples:
|
|
|
5458
5917
|
}
|
|
5459
5918
|
|
|
5460
5919
|
// src/commands/utils/jwt.ts
|
|
5461
|
-
import { Command as
|
|
5920
|
+
import { Command as Command22 } from "commander";
|
|
5462
5921
|
async function handleJwtDecode(token) {
|
|
5463
5922
|
const service = new UtilsService();
|
|
5464
5923
|
try {
|
|
@@ -5497,7 +5956,7 @@ async function handleJwtEncode(options) {
|
|
|
5497
5956
|
}
|
|
5498
5957
|
}
|
|
5499
5958
|
function createJwtCommand() {
|
|
5500
|
-
const jwtCommand = new
|
|
5959
|
+
const jwtCommand = new Command22("jwt").description("Decode and encode JWT tokens");
|
|
5501
5960
|
jwtCommand.command("decode").description("Decode JWT token").argument("<token>", "JWT token to decode").addHelpText("after", `
|
|
5502
5961
|
Examples:
|
|
5503
5962
|
$ jai1 utils jwt decode "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
|
|
@@ -5518,11 +5977,11 @@ Examples:
|
|
|
5518
5977
|
}
|
|
5519
5978
|
|
|
5520
5979
|
// src/commands/utils/unix-time.ts
|
|
5521
|
-
import { Command as
|
|
5522
|
-
async function handleUnixTime(
|
|
5980
|
+
import { Command as Command23 } from "commander";
|
|
5981
|
+
async function handleUnixTime(input4, options) {
|
|
5523
5982
|
const service = new UtilsService();
|
|
5524
5983
|
try {
|
|
5525
|
-
if (!
|
|
5984
|
+
if (!input4) {
|
|
5526
5985
|
const now = /* @__PURE__ */ new Date();
|
|
5527
5986
|
const timestamp = service.dateToUnix(now, options.ms);
|
|
5528
5987
|
console.log("\u{1F552} Current Unix Timestamp:");
|
|
@@ -5532,8 +5991,8 @@ async function handleUnixTime(input3, options) {
|
|
|
5532
5991
|
console.log(` ISO: ${now.toISOString()}`);
|
|
5533
5992
|
console.log(` Local: ${now.toLocaleString()}`);
|
|
5534
5993
|
console.log();
|
|
5535
|
-
} else if (/^\d+$/.test(
|
|
5536
|
-
const timestamp = parseInt(
|
|
5994
|
+
} else if (/^\d+$/.test(input4)) {
|
|
5995
|
+
const timestamp = parseInt(input4, 10);
|
|
5537
5996
|
const date = service.unixToDate(timestamp, options.ms);
|
|
5538
5997
|
console.log("\u{1F552} Unix Timestamp to Date:");
|
|
5539
5998
|
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
@@ -5546,7 +6005,7 @@ async function handleUnixTime(input3, options) {
|
|
|
5546
6005
|
}
|
|
5547
6006
|
console.log();
|
|
5548
6007
|
} else {
|
|
5549
|
-
const date = new Date(
|
|
6008
|
+
const date = new Date(input4);
|
|
5550
6009
|
if (isNaN(date.getTime())) {
|
|
5551
6010
|
console.error("\u274C Invalid date format");
|
|
5552
6011
|
process.exit(1);
|
|
@@ -5554,7 +6013,7 @@ async function handleUnixTime(input3, options) {
|
|
|
5554
6013
|
const timestamp = service.dateToUnix(date, options.ms);
|
|
5555
6014
|
console.log("\u{1F552} Date to Unix Timestamp:");
|
|
5556
6015
|
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
5557
|
-
console.log(` Input: ${
|
|
6016
|
+
console.log(` Input: ${input4}`);
|
|
5558
6017
|
console.log(` Parsed: ${date.toISOString()}`);
|
|
5559
6018
|
console.log();
|
|
5560
6019
|
console.log(` ${timestamp}${options.ms ? " (ms)" : ""}`);
|
|
@@ -5566,7 +6025,7 @@ async function handleUnixTime(input3, options) {
|
|
|
5566
6025
|
}
|
|
5567
6026
|
}
|
|
5568
6027
|
function createUnixTimeCommand() {
|
|
5569
|
-
const cmd = new
|
|
6028
|
+
const cmd = new Command23("unix-time").description("Convert unix timestamps to/from dates").argument("[input]", "Unix timestamp or date string").option("-f, --format <format>", "Output format (iso, local, utc)", "iso").option("--ms", "Use milliseconds instead of seconds").addHelpText("after", `
|
|
5570
6029
|
Examples:
|
|
5571
6030
|
$ jai1 utils unix-time
|
|
5572
6031
|
$ jai1 utils unix-time 1702550400
|
|
@@ -5574,14 +6033,14 @@ Examples:
|
|
|
5574
6033
|
$ jai1 utils unix-time "2024-01-15 10:30:00"
|
|
5575
6034
|
$ jai1 utils unix-time "2024-01-15" --format local
|
|
5576
6035
|
$ jai1 utils unix-time --ms
|
|
5577
|
-
`).action(async (
|
|
5578
|
-
await handleUnixTime(
|
|
6036
|
+
`).action(async (input4, options) => {
|
|
6037
|
+
await handleUnixTime(input4, options);
|
|
5579
6038
|
});
|
|
5580
6039
|
return cmd;
|
|
5581
6040
|
}
|
|
5582
6041
|
|
|
5583
6042
|
// src/commands/utils/timezone.ts
|
|
5584
|
-
import { Command as
|
|
6043
|
+
import { Command as Command24 } from "commander";
|
|
5585
6044
|
async function handleTimezoneConversion(time, options) {
|
|
5586
6045
|
const service = new UtilsService();
|
|
5587
6046
|
try {
|
|
@@ -5618,7 +6077,7 @@ async function handleTimezoneConversion(time, options) {
|
|
|
5618
6077
|
}
|
|
5619
6078
|
}
|
|
5620
6079
|
function createTimezoneCommand() {
|
|
5621
|
-
const cmd = new
|
|
6080
|
+
const cmd = new Command24("timezone").description("Convert time between timezones").argument("[time]", 'Time to convert (e.g., "2024-01-15 10:00")').option("--from <timezone>", "Source timezone").option("--to <timezone>", "Target timezone").option("--list", "Show available timezones").addHelpText("after", `
|
|
5622
6081
|
Examples:
|
|
5623
6082
|
$ jai1 utils timezone --list
|
|
5624
6083
|
$ jai1 utils timezone "2024-01-15 10:00" --from "America/New_York" --to "Asia/Tokyo"
|
|
@@ -5631,11 +6090,11 @@ Examples:
|
|
|
5631
6090
|
}
|
|
5632
6091
|
|
|
5633
6092
|
// src/commands/utils/url-encode.ts
|
|
5634
|
-
import { Command as
|
|
5635
|
-
async function handleUrlEncode(
|
|
6093
|
+
import { Command as Command25 } from "commander";
|
|
6094
|
+
async function handleUrlEncode(input4, options) {
|
|
5636
6095
|
const service = new UtilsService();
|
|
5637
6096
|
try {
|
|
5638
|
-
const encoded = service.urlEncode(
|
|
6097
|
+
const encoded = service.urlEncode(input4, options.full);
|
|
5639
6098
|
console.log("\u{1F517} URL Encoded:");
|
|
5640
6099
|
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
5641
6100
|
if (options.full) {
|
|
@@ -5652,24 +6111,24 @@ async function handleUrlEncode(input3, options) {
|
|
|
5652
6111
|
}
|
|
5653
6112
|
}
|
|
5654
6113
|
function createUrlEncodeCommand() {
|
|
5655
|
-
const cmd = new
|
|
6114
|
+
const cmd = new Command25("url-encode").description("Encode URL component or full URL").argument("<input>", "Text to encode").option("--full", "Encode full URL (use encodeURI instead of encodeURIComponent)").addHelpText("after", `
|
|
5656
6115
|
Examples:
|
|
5657
6116
|
$ jai1 utils url-encode "hello world"
|
|
5658
6117
|
$ jai1 utils url-encode "hello world & test"
|
|
5659
6118
|
$ jai1 utils url-encode "name=John Doe&age=30"
|
|
5660
6119
|
$ jai1 utils url-encode "https://example.com/path with spaces" --full
|
|
5661
|
-
`).showHelpAfterError("(add --help for additional examples)").action(async (
|
|
5662
|
-
await handleUrlEncode(
|
|
6120
|
+
`).showHelpAfterError("(add --help for additional examples)").action(async (input4, options) => {
|
|
6121
|
+
await handleUrlEncode(input4, options);
|
|
5663
6122
|
});
|
|
5664
6123
|
return cmd;
|
|
5665
6124
|
}
|
|
5666
6125
|
|
|
5667
6126
|
// src/commands/utils/url-decode.ts
|
|
5668
|
-
import { Command as
|
|
5669
|
-
async function handleUrlDecode(
|
|
6127
|
+
import { Command as Command26 } from "commander";
|
|
6128
|
+
async function handleUrlDecode(input4, options) {
|
|
5670
6129
|
const service = new UtilsService();
|
|
5671
6130
|
try {
|
|
5672
|
-
const decoded = service.urlDecode(
|
|
6131
|
+
const decoded = service.urlDecode(input4, options.full);
|
|
5673
6132
|
console.log("\u{1F517} URL Decoded:");
|
|
5674
6133
|
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
5675
6134
|
if (options.full) {
|
|
@@ -5686,20 +6145,20 @@ async function handleUrlDecode(input3, options) {
|
|
|
5686
6145
|
}
|
|
5687
6146
|
}
|
|
5688
6147
|
function createUrlDecodeCommand() {
|
|
5689
|
-
const cmd = new
|
|
6148
|
+
const cmd = new Command26("url-decode").description("Decode URL-encoded string").argument("<input>", "Text to decode").option("--full", "Decode full URL (use decodeURI instead of decodeURIComponent)").addHelpText("after", `
|
|
5690
6149
|
Examples:
|
|
5691
6150
|
$ jai1 utils url-decode "hello%20world"
|
|
5692
6151
|
$ jai1 utils url-decode "hello%20world%20%26%20test"
|
|
5693
6152
|
$ jai1 utils url-decode "name%3DJohn%20Doe%26age%3D30"
|
|
5694
6153
|
$ jai1 utils url-decode "https://example.com/path%20with%20spaces" --full
|
|
5695
|
-
`).showHelpAfterError("(add --help for additional examples)").action(async (
|
|
5696
|
-
await handleUrlDecode(
|
|
6154
|
+
`).showHelpAfterError("(add --help for additional examples)").action(async (input4, options) => {
|
|
6155
|
+
await handleUrlDecode(input4, options);
|
|
5697
6156
|
});
|
|
5698
6157
|
return cmd;
|
|
5699
6158
|
}
|
|
5700
6159
|
|
|
5701
6160
|
// src/commands/utils/cron.ts
|
|
5702
|
-
import { Command as
|
|
6161
|
+
import { Command as Command27 } from "commander";
|
|
5703
6162
|
import cronstrue from "cronstrue";
|
|
5704
6163
|
async function handleCronParse(expression) {
|
|
5705
6164
|
try {
|
|
@@ -5742,7 +6201,7 @@ async function handleCronParse(expression) {
|
|
|
5742
6201
|
}
|
|
5743
6202
|
}
|
|
5744
6203
|
function createCronCommand() {
|
|
5745
|
-
const cmd = new
|
|
6204
|
+
const cmd = new Command27("cron").description("Parse and explain cron expression").argument("<expression>", 'Cron expression (e.g., "0 5 * * *")').addHelpText("after", `
|
|
5746
6205
|
Examples:
|
|
5747
6206
|
$ jai1 utils cron "0 5 * * *"
|
|
5748
6207
|
$ jai1 utils cron "*/15 * * * *"
|
|
@@ -5756,7 +6215,7 @@ Examples:
|
|
|
5756
6215
|
}
|
|
5757
6216
|
|
|
5758
6217
|
// src/commands/utils/markdown-preview.ts
|
|
5759
|
-
import { Command as
|
|
6218
|
+
import { Command as Command28 } from "commander";
|
|
5760
6219
|
import { readFile as readFile5 } from "fs/promises";
|
|
5761
6220
|
import { marked } from "marked";
|
|
5762
6221
|
import TerminalRenderer from "marked-terminal";
|
|
@@ -5795,7 +6254,7 @@ ${code.trim()}
|
|
|
5795
6254
|
}
|
|
5796
6255
|
}
|
|
5797
6256
|
function createMarkdownPreviewCommand() {
|
|
5798
|
-
const cmd = new
|
|
6257
|
+
const cmd = new Command28("markdown-preview").description("Preview markdown file in terminal").argument("<file>", "Markdown file path").option("--raw", "Show raw markdown instead of rendered").addHelpText("after", `
|
|
5799
6258
|
Examples:
|
|
5800
6259
|
$ jai1 utils markdown-preview README.md
|
|
5801
6260
|
$ jai1 utils markdown-preview ./docs/guide.md
|
|
@@ -5829,14 +6288,14 @@ var PasswordView = () => {
|
|
|
5829
6288
|
React27.useEffect(() => {
|
|
5830
6289
|
handleGenerate();
|
|
5831
6290
|
}, []);
|
|
5832
|
-
useInput12((
|
|
6291
|
+
useInput12((input4, key) => {
|
|
5833
6292
|
if (key.tab) {
|
|
5834
6293
|
if (focusedField === "length") setFocusedField("count");
|
|
5835
6294
|
else if (focusedField === "count") setFocusedField("generate");
|
|
5836
6295
|
else setFocusedField("length");
|
|
5837
6296
|
} else if (key.return) {
|
|
5838
6297
|
handleGenerate();
|
|
5839
|
-
} else if (
|
|
6298
|
+
} else if (input4 === "c" && passwords.length > 0) {
|
|
5840
6299
|
handleCopy(0);
|
|
5841
6300
|
}
|
|
5842
6301
|
});
|
|
@@ -5919,7 +6378,7 @@ var UuidView = () => {
|
|
|
5919
6378
|
React28.useEffect(() => {
|
|
5920
6379
|
handleGenerate();
|
|
5921
6380
|
}, []);
|
|
5922
|
-
useInput13((
|
|
6381
|
+
useInput13((input4, key) => {
|
|
5923
6382
|
if (key.tab) {
|
|
5924
6383
|
const fields = ["count", "uppercase", "hyphens", "generate"];
|
|
5925
6384
|
const currentIndex = fields.indexOf(focusedField);
|
|
@@ -5932,7 +6391,7 @@ var UuidView = () => {
|
|
|
5932
6391
|
} else {
|
|
5933
6392
|
handleGenerate();
|
|
5934
6393
|
}
|
|
5935
|
-
} else if (
|
|
6394
|
+
} else if (input4 === "c" && uuids.length > 0) {
|
|
5936
6395
|
handleCopy(0);
|
|
5937
6396
|
}
|
|
5938
6397
|
});
|
|
@@ -6007,7 +6466,7 @@ var HashView = () => {
|
|
|
6007
6466
|
React29.useEffect(() => {
|
|
6008
6467
|
handleGenerate();
|
|
6009
6468
|
}, []);
|
|
6010
|
-
useInput14((
|
|
6469
|
+
useInput14((input4, key) => {
|
|
6011
6470
|
if (key.tab) {
|
|
6012
6471
|
const fields = ["text", "algorithm", "generate"];
|
|
6013
6472
|
const currentIndex = fields.indexOf(focusedField);
|
|
@@ -6020,7 +6479,7 @@ var HashView = () => {
|
|
|
6020
6479
|
} else if (key.rightArrow && focusedField === "algorithm") {
|
|
6021
6480
|
const currentIndex = ALGORITHMS.indexOf(algorithm);
|
|
6022
6481
|
setAlgorithm(ALGORITHMS[(currentIndex + 1) % ALGORITHMS.length]);
|
|
6023
|
-
} else if (
|
|
6482
|
+
} else if (input4 === "c" && hash) {
|
|
6024
6483
|
handleCopy();
|
|
6025
6484
|
}
|
|
6026
6485
|
});
|
|
@@ -6086,7 +6545,7 @@ import React30, { useState as useState17 } from "react";
|
|
|
6086
6545
|
import { Box as Box20, Text as Text21, useInput as useInput15 } from "ink";
|
|
6087
6546
|
import TextInput7 from "ink-text-input";
|
|
6088
6547
|
var Base64View = () => {
|
|
6089
|
-
const [
|
|
6548
|
+
const [input4, setInput] = useState17("");
|
|
6090
6549
|
const [mode, setMode] = useState17("encode");
|
|
6091
6550
|
const [urlSafe, setUrlSafe] = useState17(false);
|
|
6092
6551
|
const [result, setResult] = useState17("");
|
|
@@ -6094,7 +6553,7 @@ var Base64View = () => {
|
|
|
6094
6553
|
const [focusedField, setFocusedField] = useState17("input");
|
|
6095
6554
|
const [copied, setCopied] = useState17(false);
|
|
6096
6555
|
const service = new UtilsService();
|
|
6097
|
-
useInput15((
|
|
6556
|
+
useInput15((input5, key) => {
|
|
6098
6557
|
if (key.tab) {
|
|
6099
6558
|
const fields = ["input", "mode", "urlsafe", "convert"];
|
|
6100
6559
|
const currentIndex = fields.indexOf(focusedField);
|
|
@@ -6109,22 +6568,22 @@ var Base64View = () => {
|
|
|
6109
6568
|
} else {
|
|
6110
6569
|
handleConvert();
|
|
6111
6570
|
}
|
|
6112
|
-
} else if (
|
|
6571
|
+
} else if (input5 === "c" && result) {
|
|
6113
6572
|
handleCopy();
|
|
6114
6573
|
}
|
|
6115
6574
|
});
|
|
6116
6575
|
const handleConvert = () => {
|
|
6117
|
-
if (!
|
|
6576
|
+
if (!input4) {
|
|
6118
6577
|
setError("Input cannot be empty");
|
|
6119
6578
|
return;
|
|
6120
6579
|
}
|
|
6121
6580
|
try {
|
|
6122
6581
|
setError("");
|
|
6123
6582
|
if (mode === "encode") {
|
|
6124
|
-
const encoded = service.base64Encode(
|
|
6583
|
+
const encoded = service.base64Encode(input4, urlSafe);
|
|
6125
6584
|
setResult(encoded);
|
|
6126
6585
|
} else {
|
|
6127
|
-
const decoded = service.base64Decode(
|
|
6586
|
+
const decoded = service.base64Decode(input4);
|
|
6128
6587
|
setResult(decoded.toString("utf-8"));
|
|
6129
6588
|
}
|
|
6130
6589
|
setCopied(false);
|
|
@@ -6153,7 +6612,7 @@ var Base64View = () => {
|
|
|
6153
6612
|
marginBottom: 1
|
|
6154
6613
|
},
|
|
6155
6614
|
/* @__PURE__ */ React30.createElement(Text21, { bold: true, color: "yellow", marginBottom: 1 }, "Options:"),
|
|
6156
|
-
/* @__PURE__ */ React30.createElement(Box20, { marginBottom: 1, flexDirection: "column" }, /* @__PURE__ */ React30.createElement(Box20, { marginBottom: 0 }, /* @__PURE__ */ React30.createElement(Text21, { color: focusedField === "input" ? "green" : void 0 }, focusedField === "input" ? "\u25B6 " : " ", "Input text:")), /* @__PURE__ */ React30.createElement(Box20, { marginLeft: 2, width: 60 }, focusedField === "input" ? /* @__PURE__ */ React30.createElement(TextInput7, { value:
|
|
6615
|
+
/* @__PURE__ */ React30.createElement(Box20, { marginBottom: 1, flexDirection: "column" }, /* @__PURE__ */ React30.createElement(Box20, { marginBottom: 0 }, /* @__PURE__ */ React30.createElement(Text21, { color: focusedField === "input" ? "green" : void 0 }, focusedField === "input" ? "\u25B6 " : " ", "Input text:")), /* @__PURE__ */ React30.createElement(Box20, { marginLeft: 2, width: 60 }, focusedField === "input" ? /* @__PURE__ */ React30.createElement(TextInput7, { value: input4, onChange: setInput, placeholder: "Enter text..." }) : /* @__PURE__ */ React30.createElement(Text21, { dimColor: !input4 }, input4 || "(empty)"))),
|
|
6157
6616
|
/* @__PURE__ */ React30.createElement(Box20, { marginBottom: 1 }, /* @__PURE__ */ React30.createElement(Box20, { width: 20 }, /* @__PURE__ */ React30.createElement(Text21, { color: focusedField === "mode" ? "green" : void 0 }, focusedField === "mode" ? "\u25B6 " : " ", "Mode:")), /* @__PURE__ */ React30.createElement(Text21, { bold: true, color: focusedField === "mode" ? "yellow" : void 0 }, mode === "encode" ? "ENCODE" : "DECODE"), focusedField === "mode" && /* @__PURE__ */ React30.createElement(Text21, { dimColor: true }, " (Enter to toggle)")),
|
|
6158
6617
|
mode === "encode" && /* @__PURE__ */ React30.createElement(Box20, { marginBottom: 1 }, /* @__PURE__ */ React30.createElement(Text21, { color: focusedField === "urlsafe" ? "green" : void 0 }, focusedField === "urlsafe" ? "\u25B6 " : " ", "[", urlSafe ? "\u2713" : " ", "] URL-Safe (+ \u2192 -, / \u2192 _)")),
|
|
6159
6618
|
/* @__PURE__ */ React30.createElement(Box20, { marginTop: 1 }, /* @__PURE__ */ React30.createElement(
|
|
@@ -6198,14 +6657,14 @@ import React31, { useState as useState18 } from "react";
|
|
|
6198
6657
|
import { Box as Box21, Text as Text22, useInput as useInput16 } from "ink";
|
|
6199
6658
|
import TextInput8 from "ink-text-input";
|
|
6200
6659
|
var UrlView = () => {
|
|
6201
|
-
const [
|
|
6660
|
+
const [input4, setInput] = useState18("");
|
|
6202
6661
|
const [mode, setMode] = useState18("encode");
|
|
6203
6662
|
const [fullUrl, setFullUrl] = useState18(false);
|
|
6204
6663
|
const [result, setResult] = useState18("");
|
|
6205
6664
|
const [focusedField, setFocusedField] = useState18("input");
|
|
6206
6665
|
const [copied, setCopied] = useState18(false);
|
|
6207
6666
|
const service = new UtilsService();
|
|
6208
|
-
useInput16((
|
|
6667
|
+
useInput16((input5, key) => {
|
|
6209
6668
|
if (key.tab) {
|
|
6210
6669
|
const fields = ["input", "mode", "full", "convert"];
|
|
6211
6670
|
const currentIndex = fields.indexOf(focusedField);
|
|
@@ -6219,17 +6678,17 @@ var UrlView = () => {
|
|
|
6219
6678
|
} else {
|
|
6220
6679
|
handleConvert();
|
|
6221
6680
|
}
|
|
6222
|
-
} else if (
|
|
6681
|
+
} else if (input5 === "c" && result) {
|
|
6223
6682
|
handleCopy();
|
|
6224
6683
|
}
|
|
6225
6684
|
});
|
|
6226
6685
|
const handleConvert = () => {
|
|
6227
|
-
if (!
|
|
6686
|
+
if (!input4) return;
|
|
6228
6687
|
if (mode === "encode") {
|
|
6229
|
-
const encoded = fullUrl ? service.urlEncode(
|
|
6688
|
+
const encoded = fullUrl ? service.urlEncode(input4, true) : service.urlEncode(input4, false);
|
|
6230
6689
|
setResult(encoded);
|
|
6231
6690
|
} else {
|
|
6232
|
-
const decoded = fullUrl ? service.urlDecode(
|
|
6691
|
+
const decoded = fullUrl ? service.urlDecode(input4, true) : service.urlDecode(input4, false);
|
|
6233
6692
|
setResult(decoded);
|
|
6234
6693
|
}
|
|
6235
6694
|
setCopied(false);
|
|
@@ -6254,7 +6713,7 @@ var UrlView = () => {
|
|
|
6254
6713
|
marginBottom: 1
|
|
6255
6714
|
},
|
|
6256
6715
|
/* @__PURE__ */ React31.createElement(Text22, { bold: true, color: "yellow", marginBottom: 1 }, "Options:"),
|
|
6257
|
-
/* @__PURE__ */ React31.createElement(Box21, { marginBottom: 1, flexDirection: "column" }, /* @__PURE__ */ React31.createElement(Box21, { marginBottom: 0 }, /* @__PURE__ */ React31.createElement(Text22, { color: focusedField === "input" ? "green" : void 0 }, focusedField === "input" ? "\u25B6 " : " ", "Input text:")), /* @__PURE__ */ React31.createElement(Box21, { marginLeft: 2, width: 60 }, focusedField === "input" ? /* @__PURE__ */ React31.createElement(TextInput8, { value:
|
|
6716
|
+
/* @__PURE__ */ React31.createElement(Box21, { marginBottom: 1, flexDirection: "column" }, /* @__PURE__ */ React31.createElement(Box21, { marginBottom: 0 }, /* @__PURE__ */ React31.createElement(Text22, { color: focusedField === "input" ? "green" : void 0 }, focusedField === "input" ? "\u25B6 " : " ", "Input text:")), /* @__PURE__ */ React31.createElement(Box21, { marginLeft: 2, width: 60 }, focusedField === "input" ? /* @__PURE__ */ React31.createElement(TextInput8, { value: input4, onChange: setInput, placeholder: "Enter text or URL..." }) : /* @__PURE__ */ React31.createElement(Text22, { dimColor: !input4 }, input4 || "(empty)"))),
|
|
6258
6717
|
/* @__PURE__ */ React31.createElement(Box21, { marginBottom: 1 }, /* @__PURE__ */ React31.createElement(Box21, { width: 20 }, /* @__PURE__ */ React31.createElement(Text22, { color: focusedField === "mode" ? "green" : void 0 }, focusedField === "mode" ? "\u25B6 " : " ", "Mode:")), /* @__PURE__ */ React31.createElement(Text22, { bold: true, color: focusedField === "mode" ? "yellow" : void 0 }, mode === "encode" ? "ENCODE" : "DECODE"), focusedField === "mode" && /* @__PURE__ */ React31.createElement(Text22, { dimColor: true }, " (Enter to toggle)")),
|
|
6259
6718
|
/* @__PURE__ */ React31.createElement(Box21, { marginBottom: 1 }, /* @__PURE__ */ React31.createElement(Text22, { color: focusedField === "full" ? "green" : void 0 }, focusedField === "full" ? "\u25B6 " : " ", "[", fullUrl ? "\u2713" : " ", "] Full URL (encode/decode entire URL)")),
|
|
6260
6719
|
/* @__PURE__ */ React31.createElement(Box21, { marginTop: 1 }, /* @__PURE__ */ React31.createElement(
|
|
@@ -6288,7 +6747,7 @@ import React32, { useState as useState19 } from "react";
|
|
|
6288
6747
|
import { Box as Box22, Text as Text23, useInput as useInput17 } from "ink";
|
|
6289
6748
|
import TextInput9 from "ink-text-input";
|
|
6290
6749
|
var UnixTimeView = () => {
|
|
6291
|
-
const [
|
|
6750
|
+
const [input4, setInput] = useState19("");
|
|
6292
6751
|
const [mode, setMode] = useState19("now");
|
|
6293
6752
|
const [format, setFormat] = useState19("iso");
|
|
6294
6753
|
const [useMs, setUseMs] = useState19(false);
|
|
@@ -6297,7 +6756,7 @@ var UnixTimeView = () => {
|
|
|
6297
6756
|
const [focusedField, setFocusedField] = useState19("mode");
|
|
6298
6757
|
const [copied, setCopied] = useState19(false);
|
|
6299
6758
|
const service = new UtilsService();
|
|
6300
|
-
useInput17((
|
|
6759
|
+
useInput17((input5, key) => {
|
|
6301
6760
|
if (key.tab) {
|
|
6302
6761
|
const fields = mode === "now" ? ["mode", "ms", "convert"] : mode === "to-human" ? ["mode", "input", "format", "convert"] : ["mode", "input", "convert"];
|
|
6303
6762
|
const currentIndex = fields.indexOf(focusedField);
|
|
@@ -6318,7 +6777,7 @@ var UnixTimeView = () => {
|
|
|
6318
6777
|
} else {
|
|
6319
6778
|
handleConvert();
|
|
6320
6779
|
}
|
|
6321
|
-
} else if (
|
|
6780
|
+
} else if (input5 === "c" && result) {
|
|
6322
6781
|
handleCopy();
|
|
6323
6782
|
}
|
|
6324
6783
|
});
|
|
@@ -6329,18 +6788,18 @@ var UnixTimeView = () => {
|
|
|
6329
6788
|
const timestamp = service.unixTimeCurrent(useMs);
|
|
6330
6789
|
setResult(timestamp.toString());
|
|
6331
6790
|
} else if (mode === "to-human") {
|
|
6332
|
-
if (!
|
|
6791
|
+
if (!input4) {
|
|
6333
6792
|
setError("Please enter a timestamp");
|
|
6334
6793
|
return;
|
|
6335
6794
|
}
|
|
6336
|
-
const human = service.unixTimeToHuman(parseInt(
|
|
6795
|
+
const human = service.unixTimeToHuman(parseInt(input4, 10), format);
|
|
6337
6796
|
setResult(human);
|
|
6338
6797
|
} else {
|
|
6339
|
-
if (!
|
|
6798
|
+
if (!input4) {
|
|
6340
6799
|
setError("Please enter a date string");
|
|
6341
6800
|
return;
|
|
6342
6801
|
}
|
|
6343
|
-
const timestamp = service.unixTimeFromString(
|
|
6802
|
+
const timestamp = service.unixTimeFromString(input4);
|
|
6344
6803
|
setResult(timestamp.toString());
|
|
6345
6804
|
}
|
|
6346
6805
|
setCopied(false);
|
|
@@ -6373,11 +6832,11 @@ var UnixTimeView = () => {
|
|
|
6373
6832
|
mode !== "now" && /* @__PURE__ */ React32.createElement(Box22, { marginBottom: 1, flexDirection: "column" }, /* @__PURE__ */ React32.createElement(Box22, { marginBottom: 0 }, /* @__PURE__ */ React32.createElement(Text23, { color: focusedField === "input" ? "green" : void 0 }, focusedField === "input" ? "\u25B6 " : " ", mode === "to-human" ? "Timestamp:" : "Date string:")), /* @__PURE__ */ React32.createElement(Box22, { marginLeft: 2, width: 50 }, focusedField === "input" ? /* @__PURE__ */ React32.createElement(
|
|
6374
6833
|
TextInput9,
|
|
6375
6834
|
{
|
|
6376
|
-
value:
|
|
6835
|
+
value: input4,
|
|
6377
6836
|
onChange: setInput,
|
|
6378
6837
|
placeholder: mode === "to-human" ? "1702550400" : "2024-01-15 10:30:00"
|
|
6379
6838
|
}
|
|
6380
|
-
) : /* @__PURE__ */ React32.createElement(Text23, { dimColor: !
|
|
6839
|
+
) : /* @__PURE__ */ React32.createElement(Text23, { dimColor: !input4 }, input4 || "(empty)"))),
|
|
6381
6840
|
mode === "to-human" && /* @__PURE__ */ React32.createElement(Box22, { marginBottom: 1 }, /* @__PURE__ */ React32.createElement(Box22, { width: 20 }, /* @__PURE__ */ React32.createElement(Text23, { color: focusedField === "format" ? "green" : void 0 }, focusedField === "format" ? "\u25B6 " : " ", "Format:")), /* @__PURE__ */ React32.createElement(Text23, { bold: true, color: focusedField === "format" ? "yellow" : void 0 }, format.toUpperCase()), focusedField === "format" && /* @__PURE__ */ React32.createElement(Text23, { dimColor: true }, " (Enter to cycle)")),
|
|
6382
6841
|
mode === "now" && /* @__PURE__ */ React32.createElement(Box22, { marginBottom: 1 }, /* @__PURE__ */ React32.createElement(Text23, { color: focusedField === "ms" ? "green" : void 0 }, focusedField === "ms" ? "\u25B6 " : " ", "[", useMs ? "\u2713" : " ", "] Milliseconds")),
|
|
6383
6842
|
/* @__PURE__ */ React32.createElement(Box22, { marginTop: 1 }, /* @__PURE__ */ React32.createElement(
|
|
@@ -6431,7 +6890,7 @@ var JwtView = () => {
|
|
|
6431
6890
|
const [focusedField, setFocusedField] = useState20("mode");
|
|
6432
6891
|
const [copied, setCopied] = useState20(false);
|
|
6433
6892
|
const service = new UtilsService();
|
|
6434
|
-
useInput18((
|
|
6893
|
+
useInput18((input4, key) => {
|
|
6435
6894
|
if (key.tab) {
|
|
6436
6895
|
const fields = mode === "decode" ? ["mode", "token", "process"] : ["mode", "payload", "secret", "process"];
|
|
6437
6896
|
const currentIndex = fields.indexOf(focusedField);
|
|
@@ -6444,7 +6903,7 @@ var JwtView = () => {
|
|
|
6444
6903
|
} else {
|
|
6445
6904
|
handleProcess();
|
|
6446
6905
|
}
|
|
6447
|
-
} else if (
|
|
6906
|
+
} else if (input4 === "c" && result.token) {
|
|
6448
6907
|
handleCopy();
|
|
6449
6908
|
}
|
|
6450
6909
|
});
|
|
@@ -6569,12 +7028,12 @@ var CronView = () => {
|
|
|
6569
7028
|
const [focusedField, setFocusedField] = useState21("expression");
|
|
6570
7029
|
const [copied, setCopied] = useState21(false);
|
|
6571
7030
|
const service = new UtilsService();
|
|
6572
|
-
useInput19((
|
|
7031
|
+
useInput19((input4, key) => {
|
|
6573
7032
|
if (key.tab) {
|
|
6574
7033
|
setFocusedField(focusedField === "expression" ? "parse" : "expression");
|
|
6575
7034
|
} else if (key.return) {
|
|
6576
7035
|
handleParse();
|
|
6577
|
-
} else if (
|
|
7036
|
+
} else if (input4 === "c" && result) {
|
|
6578
7037
|
handleCopy();
|
|
6579
7038
|
}
|
|
6580
7039
|
});
|
|
@@ -6675,14 +7134,14 @@ var TimezoneView = () => {
|
|
|
6675
7134
|
const [focusedField, setFocusedField] = useState22("time");
|
|
6676
7135
|
const [copied, setCopied] = useState22(false);
|
|
6677
7136
|
const service = new UtilsService();
|
|
6678
|
-
useInput20((
|
|
7137
|
+
useInput20((input4, key) => {
|
|
6679
7138
|
if (key.tab) {
|
|
6680
7139
|
const fields = ["time", "from", "to", "convert"];
|
|
6681
7140
|
const currentIndex = fields.indexOf(focusedField);
|
|
6682
7141
|
setFocusedField(fields[(currentIndex + 1) % fields.length]);
|
|
6683
7142
|
} else if (key.return) {
|
|
6684
7143
|
handleConvert();
|
|
6685
|
-
} else if (
|
|
7144
|
+
} else if (input4 === "c" && result) {
|
|
6686
7145
|
handleCopy();
|
|
6687
7146
|
}
|
|
6688
7147
|
});
|
|
@@ -6801,7 +7260,7 @@ var HttpView = () => {
|
|
|
6801
7260
|
const [focusedField, setFocusedField] = useState23("url");
|
|
6802
7261
|
const [copied, setCopied] = useState23(false);
|
|
6803
7262
|
const service = new UtilsService();
|
|
6804
|
-
useInput21((
|
|
7263
|
+
useInput21((input4, key) => {
|
|
6805
7264
|
if (loading) return;
|
|
6806
7265
|
if (key.tab) {
|
|
6807
7266
|
const fields = ["url", "method", "headers", "body", "send"];
|
|
@@ -6815,7 +7274,7 @@ var HttpView = () => {
|
|
|
6815
7274
|
setMethod(METHODS[(currentIndex + 1) % METHODS.length]);
|
|
6816
7275
|
} else if (key.return && focusedField === "send") {
|
|
6817
7276
|
handleSend();
|
|
6818
|
-
} else if (
|
|
7277
|
+
} else if (input4 === "c" && response) {
|
|
6819
7278
|
handleCopy();
|
|
6820
7279
|
}
|
|
6821
7280
|
});
|
|
@@ -6939,14 +7398,14 @@ var HttpView = () => {
|
|
|
6939
7398
|
import React37, { useState as useState24 } from "react";
|
|
6940
7399
|
import { Box as Box27, Text as Text28, useInput as useInput22 } from "ink";
|
|
6941
7400
|
import TextInput14 from "ink-text-input";
|
|
6942
|
-
import * as
|
|
7401
|
+
import * as fs9 from "fs";
|
|
6943
7402
|
import * as path5 from "path";
|
|
6944
7403
|
var MarkdownView = () => {
|
|
6945
7404
|
const [filePath, setFilePath] = useState24("");
|
|
6946
7405
|
const [content, setContent] = useState24("");
|
|
6947
7406
|
const [error, setError] = useState24("");
|
|
6948
7407
|
const [focusedField, setFocusedField] = useState24("file");
|
|
6949
|
-
useInput22((
|
|
7408
|
+
useInput22((input4, key) => {
|
|
6950
7409
|
if (key.tab) {
|
|
6951
7410
|
setFocusedField(focusedField === "file" ? "preview" : "file");
|
|
6952
7411
|
} else if (key.return) {
|
|
@@ -6961,11 +7420,11 @@ var MarkdownView = () => {
|
|
|
6961
7420
|
try {
|
|
6962
7421
|
setError("");
|
|
6963
7422
|
const resolvedPath = path5.resolve(filePath);
|
|
6964
|
-
if (!
|
|
7423
|
+
if (!fs9.existsSync(resolvedPath)) {
|
|
6965
7424
|
setError(`File not found: ${resolvedPath}`);
|
|
6966
7425
|
return;
|
|
6967
7426
|
}
|
|
6968
|
-
let fileContent =
|
|
7427
|
+
let fileContent = fs9.readFileSync(resolvedPath, "utf-8");
|
|
6969
7428
|
fileContent = fileContent.replace(/```mermaid\n([\s\S]*?)```/g, (match, code) => {
|
|
6970
7429
|
return `
|
|
6971
7430
|
\u{1F3A8} **Mermaid Diagram**
|
|
@@ -7061,20 +7520,20 @@ var UtilsApp = ({ onExit }) => {
|
|
|
7061
7520
|
const [selectedIndex, setSelectedIndex] = useState25(0);
|
|
7062
7521
|
const [activeView, setActiveView] = useState25(null);
|
|
7063
7522
|
const { exit } = useApp5();
|
|
7064
|
-
useInput23((
|
|
7523
|
+
useInput23((input4, key) => {
|
|
7065
7524
|
if (activeView) {
|
|
7066
7525
|
if (key.escape) {
|
|
7067
7526
|
setActiveView(null);
|
|
7068
7527
|
}
|
|
7069
7528
|
return;
|
|
7070
7529
|
}
|
|
7071
|
-
if (key.upArrow ||
|
|
7530
|
+
if (key.upArrow || input4 === "k") {
|
|
7072
7531
|
setSelectedIndex((prev) => prev > 0 ? prev - 1 : MENU_ITEMS2.length - 1);
|
|
7073
|
-
} else if (key.downArrow ||
|
|
7532
|
+
} else if (key.downArrow || input4 === "j") {
|
|
7074
7533
|
setSelectedIndex((prev) => prev < MENU_ITEMS2.length - 1 ? prev + 1 : 0);
|
|
7075
7534
|
} else if (key.return) {
|
|
7076
7535
|
setActiveView(MENU_ITEMS2[selectedIndex].id);
|
|
7077
|
-
} else if (
|
|
7536
|
+
} else if (input4 === "q" || key.escape) {
|
|
7078
7537
|
onExit();
|
|
7079
7538
|
exit();
|
|
7080
7539
|
}
|
|
@@ -7204,7 +7663,7 @@ async function runInteractiveMode() {
|
|
|
7204
7663
|
|
|
7205
7664
|
// src/commands/utils/index.ts
|
|
7206
7665
|
function createUtilsCommand() {
|
|
7207
|
-
const utilsCommand = new
|
|
7666
|
+
const utilsCommand = new Command29("utils").description("Developer utilities for common tasks").option("-i, --interactive", "Run in interactive mode").addHelpText("after", `
|
|
7208
7667
|
Interactive Mode:
|
|
7209
7668
|
$ jai1 utils --interactive
|
|
7210
7669
|
$ jai1 utils -i
|
|
@@ -7239,14 +7698,14 @@ Quick Usage:
|
|
|
7239
7698
|
}
|
|
7240
7699
|
|
|
7241
7700
|
// src/commands/deps/index.ts
|
|
7242
|
-
import { Command as
|
|
7701
|
+
import { Command as Command31 } from "commander";
|
|
7243
7702
|
|
|
7244
7703
|
// src/commands/deps/upgrade.ts
|
|
7245
|
-
import { Command as
|
|
7704
|
+
import { Command as Command30 } from "commander";
|
|
7246
7705
|
import { checkbox as checkbox3, confirm as confirm4 } from "@inquirer/prompts";
|
|
7247
7706
|
|
|
7248
7707
|
// src/services/deps.service.ts
|
|
7249
|
-
import { promises as
|
|
7708
|
+
import { promises as fs10 } from "fs";
|
|
7250
7709
|
import path6 from "path";
|
|
7251
7710
|
import { execSync } from "child_process";
|
|
7252
7711
|
import pLimit2 from "p-limit";
|
|
@@ -7258,7 +7717,7 @@ var DepsService = class {
|
|
|
7258
7717
|
async readPackageJson(cwd) {
|
|
7259
7718
|
const pkgPath = path6.join(cwd, "package.json");
|
|
7260
7719
|
try {
|
|
7261
|
-
const content = await
|
|
7720
|
+
const content = await fs10.readFile(pkgPath, "utf-8");
|
|
7262
7721
|
return JSON.parse(content);
|
|
7263
7722
|
} catch (error) {
|
|
7264
7723
|
if (error.code === "ENOENT") {
|
|
@@ -7364,7 +7823,7 @@ var DepsService = class {
|
|
|
7364
7823
|
];
|
|
7365
7824
|
for (const { file, pm } of lockFiles) {
|
|
7366
7825
|
try {
|
|
7367
|
-
await
|
|
7826
|
+
await fs10.access(path6.join(cwd, file));
|
|
7368
7827
|
return pm;
|
|
7369
7828
|
} catch {
|
|
7370
7829
|
}
|
|
@@ -7456,7 +7915,7 @@ var colors2 = {
|
|
|
7456
7915
|
dim: "\x1B[2m"
|
|
7457
7916
|
};
|
|
7458
7917
|
function createDepsUpgradeCommand() {
|
|
7459
|
-
return new
|
|
7918
|
+
return new Command30("upgrade").description("Upgrade npm packages l\xEAn version m\u1EDBi nh\u1EA5t").option("--check", "Ch\u1EC9 ki\u1EC3m tra, kh\xF4ng upgrade").option("--all", "Upgrade t\u1EA5t c\u1EA3 kh\xF4ng c\u1EA7n ch\u1ECDn").option("--deps-only", "Ch\u1EC9 upgrade dependencies").option("--dev-only", "Ch\u1EC9 upgrade devDependencies").action(async (options) => {
|
|
7460
7919
|
await handleDepsUpgrade(options);
|
|
7461
7920
|
});
|
|
7462
7921
|
}
|
|
@@ -7636,20 +8095,20 @@ function displayUpgradeTable(packages) {
|
|
|
7636
8095
|
|
|
7637
8096
|
// src/commands/deps/index.ts
|
|
7638
8097
|
function createDepsCommand() {
|
|
7639
|
-
const depsCommand = new
|
|
8098
|
+
const depsCommand = new Command31("deps").description("Qu\u1EA3n l\xFD dependencies trong project");
|
|
7640
8099
|
depsCommand.addCommand(createDepsUpgradeCommand());
|
|
7641
8100
|
return depsCommand;
|
|
7642
8101
|
}
|
|
7643
8102
|
|
|
7644
8103
|
// src/commands/kit/index.ts
|
|
7645
|
-
import { Command as
|
|
8104
|
+
import { Command as Command35 } from "commander";
|
|
7646
8105
|
|
|
7647
8106
|
// src/commands/kit/list.ts
|
|
7648
|
-
import { Command as
|
|
8107
|
+
import { Command as Command32 } from "commander";
|
|
7649
8108
|
|
|
7650
8109
|
// src/services/starter-kit.service.ts
|
|
7651
|
-
import { promises as
|
|
7652
|
-
import { join as
|
|
8110
|
+
import { promises as fs11 } from "fs";
|
|
8111
|
+
import { join as join5 } from "path";
|
|
7653
8112
|
import AdmZip from "adm-zip";
|
|
7654
8113
|
var StarterKitService = class {
|
|
7655
8114
|
/**
|
|
@@ -7696,23 +8155,23 @@ var StarterKitService = class {
|
|
|
7696
8155
|
throw new NetworkError(`Failed to download kit: HTTP ${response.status}`);
|
|
7697
8156
|
}
|
|
7698
8157
|
if (onProgress) onProgress(30);
|
|
7699
|
-
const tmpDir =
|
|
7700
|
-
await
|
|
7701
|
-
const tmpFile =
|
|
8158
|
+
const tmpDir = join5(process.env.TMPDIR || "/tmp", "jai1-kits");
|
|
8159
|
+
await fs11.mkdir(tmpDir, { recursive: true });
|
|
8160
|
+
const tmpFile = join5(tmpDir, `${slug}.zip`);
|
|
7702
8161
|
const buffer = await response.arrayBuffer();
|
|
7703
|
-
await
|
|
8162
|
+
await fs11.writeFile(tmpFile, Buffer.from(buffer));
|
|
7704
8163
|
if (onProgress) onProgress(60);
|
|
7705
8164
|
const zip = new AdmZip(tmpFile);
|
|
7706
|
-
await
|
|
8165
|
+
await fs11.mkdir(targetDir, { recursive: true });
|
|
7707
8166
|
zip.extractAllTo(targetDir, true);
|
|
7708
8167
|
if (onProgress) onProgress(100);
|
|
7709
|
-
await
|
|
8168
|
+
await fs11.unlink(tmpFile);
|
|
7710
8169
|
}
|
|
7711
8170
|
};
|
|
7712
8171
|
|
|
7713
8172
|
// src/commands/kit/list.ts
|
|
7714
8173
|
function createKitListCommand() {
|
|
7715
|
-
return new
|
|
8174
|
+
return new Command32("list").description("List available starter kits").option("-c, --category <category>", "Filter by category (backend, frontend, fullstack)").option("-s, --search <term>", "Search kits by name or description").action(async (options) => {
|
|
7716
8175
|
const configService = new ConfigService();
|
|
7717
8176
|
const config = await configService.load();
|
|
7718
8177
|
if (!config) {
|
|
@@ -7749,9 +8208,9 @@ function createKitListCommand() {
|
|
|
7749
8208
|
}
|
|
7750
8209
|
|
|
7751
8210
|
// src/commands/kit/info.ts
|
|
7752
|
-
import { Command as
|
|
8211
|
+
import { Command as Command33 } from "commander";
|
|
7753
8212
|
function createKitInfoCommand() {
|
|
7754
|
-
return new
|
|
8213
|
+
return new Command33("info").description("Show detailed information about a starter kit").argument("<slug>", "Starter kit slug").action(async (slug) => {
|
|
7755
8214
|
const configService = new ConfigService();
|
|
7756
8215
|
const config = await configService.load();
|
|
7757
8216
|
if (!config) {
|
|
@@ -7800,9 +8259,9 @@ Post-Init Commands:`);
|
|
|
7800
8259
|
}
|
|
7801
8260
|
|
|
7802
8261
|
// src/commands/kit/create.ts
|
|
7803
|
-
import { Command as
|
|
7804
|
-
import { promises as
|
|
7805
|
-
import { join as
|
|
8262
|
+
import { Command as Command34 } from "commander";
|
|
8263
|
+
import { promises as fs12 } from "fs";
|
|
8264
|
+
import { join as join6 } from "path";
|
|
7806
8265
|
import { select as select2, input, checkbox as checkbox4 } from "@inquirer/prompts";
|
|
7807
8266
|
import { execa as execa2 } from "execa";
|
|
7808
8267
|
|
|
@@ -7845,7 +8304,7 @@ var HookExecutor = class {
|
|
|
7845
8304
|
|
|
7846
8305
|
// src/commands/kit/create.ts
|
|
7847
8306
|
function createKitCreateCommand() {
|
|
7848
|
-
return new
|
|
8307
|
+
return new Command34("create").description("Create a new project from a starter kit").argument("<slug>", "Starter kit slug").argument("[directory]", "Project directory (default: ./<slug>)").option("-y, --yes", "Auto mode - use defaults, no prompts").option("--name <name>", "Project name").option("--skip-install", "Skip dependency installation").option("--skip-git", "Skip git initialization").option("--skip-framework", "Skip framework apply").option("--skip-ide", "Skip IDE sync").action(async (slug, directory, options) => {
|
|
7849
8308
|
const configService = new ConfigService();
|
|
7850
8309
|
const config = await configService.load();
|
|
7851
8310
|
if (!config) {
|
|
@@ -7865,9 +8324,9 @@ function createKitCreateCommand() {
|
|
|
7865
8324
|
}
|
|
7866
8325
|
}
|
|
7867
8326
|
}
|
|
7868
|
-
const targetDir = directory ||
|
|
8327
|
+
const targetDir = directory || join6(process.cwd(), options.name || slug);
|
|
7869
8328
|
try {
|
|
7870
|
-
await
|
|
8329
|
+
await fs12.access(targetDir);
|
|
7871
8330
|
throw new Error(`Directory already exists: ${targetDir}`);
|
|
7872
8331
|
} catch (error) {
|
|
7873
8332
|
if (error.code !== "ENOENT") {
|
|
@@ -7985,7 +8444,7 @@ function createKitCreateCommand() {
|
|
|
7985
8444
|
async function applyVariableSubstitution(dir, variables) {
|
|
7986
8445
|
const files = await getAllFiles(dir);
|
|
7987
8446
|
for (const file of files) {
|
|
7988
|
-
let content = await
|
|
8447
|
+
let content = await fs12.readFile(file, "utf-8");
|
|
7989
8448
|
let modified = false;
|
|
7990
8449
|
for (const [key, value] of Object.entries(variables)) {
|
|
7991
8450
|
const regex = new RegExp(`\\{\\{${key}\\}\\}`, "g");
|
|
@@ -7995,15 +8454,15 @@ async function applyVariableSubstitution(dir, variables) {
|
|
|
7995
8454
|
}
|
|
7996
8455
|
}
|
|
7997
8456
|
if (modified) {
|
|
7998
|
-
await
|
|
8457
|
+
await fs12.writeFile(file, content, "utf-8");
|
|
7999
8458
|
}
|
|
8000
8459
|
}
|
|
8001
8460
|
}
|
|
8002
8461
|
async function getAllFiles(dir) {
|
|
8003
8462
|
const files = [];
|
|
8004
|
-
const entries = await
|
|
8463
|
+
const entries = await fs12.readdir(dir, { withFileTypes: true });
|
|
8005
8464
|
for (const entry of entries) {
|
|
8006
|
-
const fullPath =
|
|
8465
|
+
const fullPath = join6(dir, entry.name);
|
|
8007
8466
|
if (entry.isDirectory()) {
|
|
8008
8467
|
if (!entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
8009
8468
|
files.push(...await getAllFiles(fullPath));
|
|
@@ -8017,7 +8476,7 @@ async function getAllFiles(dir) {
|
|
|
8017
8476
|
|
|
8018
8477
|
// src/commands/kit/index.ts
|
|
8019
8478
|
function createKitCommand() {
|
|
8020
|
-
const cmd = new
|
|
8479
|
+
const cmd = new Command35("kit").description("Manage starter kits for new projects").action(() => {
|
|
8021
8480
|
cmd.help();
|
|
8022
8481
|
});
|
|
8023
8482
|
cmd.addCommand(createKitListCommand());
|
|
@@ -8027,12 +8486,12 @@ function createKitCommand() {
|
|
|
8027
8486
|
}
|
|
8028
8487
|
|
|
8029
8488
|
// src/commands/rules/index.ts
|
|
8030
|
-
import { Command as
|
|
8489
|
+
import { Command as Command42 } from "commander";
|
|
8031
8490
|
|
|
8032
8491
|
// src/commands/rules/list.ts
|
|
8033
|
-
import { Command as
|
|
8492
|
+
import { Command as Command36 } from "commander";
|
|
8034
8493
|
function createRulesListCommand() {
|
|
8035
|
-
return new
|
|
8494
|
+
return new Command36("list").description("List available rule presets").option("--json", "Output as JSON").action(async (options) => {
|
|
8036
8495
|
const configService = new ConfigService();
|
|
8037
8496
|
const config = await configService.load();
|
|
8038
8497
|
if (!config) {
|
|
@@ -8087,12 +8546,12 @@ function createRulesListCommand() {
|
|
|
8087
8546
|
}
|
|
8088
8547
|
|
|
8089
8548
|
// src/commands/rules/init.ts
|
|
8090
|
-
import { Command as
|
|
8091
|
-
import { promises as
|
|
8092
|
-
import { join as
|
|
8549
|
+
import { Command as Command37 } from "commander";
|
|
8550
|
+
import { promises as fs13 } from "fs";
|
|
8551
|
+
import { join as join7 } from "path";
|
|
8093
8552
|
import { select as select3, confirm as confirm5 } from "@inquirer/prompts";
|
|
8094
8553
|
function createRulesInitCommand() {
|
|
8095
|
-
return new
|
|
8554
|
+
return new Command37("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) => {
|
|
8096
8555
|
const configService = new ConfigService();
|
|
8097
8556
|
const config = await configService.load();
|
|
8098
8557
|
if (!config) {
|
|
@@ -8174,7 +8633,7 @@ function createRulesInitCommand() {
|
|
|
8174
8633
|
appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8175
8634
|
customContext: "09-custom.mdc"
|
|
8176
8635
|
};
|
|
8177
|
-
await
|
|
8636
|
+
await fs13.writeFile("jai1-rules.json", JSON.stringify(projectConfig, null, 2));
|
|
8178
8637
|
console.log("\u2713 Created jai1-rules.json");
|
|
8179
8638
|
console.log("\n\u2705 Preset applied successfully!\n");
|
|
8180
8639
|
console.log("Next steps:");
|
|
@@ -8184,11 +8643,11 @@ function createRulesInitCommand() {
|
|
|
8184
8643
|
});
|
|
8185
8644
|
}
|
|
8186
8645
|
async function applyCursorFormat(bundle) {
|
|
8187
|
-
const rulesDir =
|
|
8188
|
-
await
|
|
8646
|
+
const rulesDir = join7(process.cwd(), ".cursor", "rules");
|
|
8647
|
+
await fs13.mkdir(rulesDir, { recursive: true });
|
|
8189
8648
|
for (const [filename, content] of Object.entries(bundle.files)) {
|
|
8190
|
-
const filePath =
|
|
8191
|
-
await
|
|
8649
|
+
const filePath = join7(rulesDir, filename);
|
|
8650
|
+
await fs13.writeFile(filePath, content, "utf-8");
|
|
8192
8651
|
console.log(`\u2713 Created .cursor/rules/${filename}`);
|
|
8193
8652
|
}
|
|
8194
8653
|
}
|
|
@@ -8215,112 +8674,1049 @@ async function applyAgentsMdFormat(bundle) {
|
|
|
8215
8674
|
}
|
|
8216
8675
|
}
|
|
8217
8676
|
const agentsMd = sections.join("\n");
|
|
8218
|
-
await
|
|
8677
|
+
await fs13.writeFile("AGENTS.md", agentsMd, "utf-8");
|
|
8219
8678
|
console.log("\u2713 Created AGENTS.md");
|
|
8220
8679
|
}
|
|
8221
8680
|
|
|
8222
|
-
// src/commands/rules/
|
|
8223
|
-
import { Command as
|
|
8224
|
-
import { promises as
|
|
8225
|
-
import { join as
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
|
|
8236
|
-
);
|
|
8237
|
-
}
|
|
8238
|
-
|
|
8239
|
-
|
|
8240
|
-
|
|
8241
|
-
|
|
8242
|
-
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
|
|
8681
|
+
// src/commands/rules/apply.ts
|
|
8682
|
+
import { Command as Command38 } from "commander";
|
|
8683
|
+
import { promises as fs15 } from "fs";
|
|
8684
|
+
import { join as join9 } from "path";
|
|
8685
|
+
import { select as select4, confirm as confirm6, checkbox as checkbox5 } from "@inquirer/prompts";
|
|
8686
|
+
|
|
8687
|
+
// src/services/rules-generator.service.ts
|
|
8688
|
+
var RulesGeneratorService = class {
|
|
8689
|
+
/**
|
|
8690
|
+
* Generate files for a specific IDE format
|
|
8691
|
+
*/
|
|
8692
|
+
generateForIde(bundle, ideId) {
|
|
8693
|
+
const format = IDE_FORMATS[ideId];
|
|
8694
|
+
if (!format) {
|
|
8695
|
+
throw new Error(`Unknown IDE format: ${ideId}`);
|
|
8696
|
+
}
|
|
8697
|
+
switch (ideId) {
|
|
8698
|
+
case "cursor":
|
|
8699
|
+
return this.generateCursorFiles(bundle, format);
|
|
8700
|
+
case "windsurf":
|
|
8701
|
+
return this.generateWindsurfFiles(bundle, format);
|
|
8702
|
+
case "antigravity":
|
|
8703
|
+
return this.generateAntigravityFiles(bundle, format);
|
|
8704
|
+
case "claude":
|
|
8705
|
+
return this.generateClaudeFiles(bundle, format);
|
|
8706
|
+
case "agentsmd":
|
|
8707
|
+
return this.generateAgentsMd(bundle, format);
|
|
8708
|
+
case "gemini":
|
|
8709
|
+
return this.generateGeminiMd(bundle, format);
|
|
8710
|
+
default:
|
|
8711
|
+
throw new Error(`Unsupported IDE format: ${ideId}`);
|
|
8246
8712
|
}
|
|
8247
|
-
|
|
8248
|
-
|
|
8713
|
+
}
|
|
8714
|
+
/**
|
|
8715
|
+
* Generate Cursor format (.mdc files)
|
|
8716
|
+
*/
|
|
8717
|
+
generateCursorFiles(bundle, format) {
|
|
8718
|
+
const files = [];
|
|
8719
|
+
files.push({
|
|
8720
|
+
path: `${format.rulesPath}/00-jai1${format.fileExtension}`,
|
|
8721
|
+
content: this.generateCursorJai1Rule(),
|
|
8722
|
+
description: "Jai1 Framework base rule"
|
|
8723
|
+
});
|
|
8724
|
+
for (const [filename, content] of Object.entries(bundle.files)) {
|
|
8725
|
+
files.push({
|
|
8726
|
+
path: `${format.rulesPath}/${filename}`,
|
|
8727
|
+
content,
|
|
8728
|
+
// Already in .mdc format
|
|
8729
|
+
description: `Preset rule: ${filename}`
|
|
8730
|
+
});
|
|
8249
8731
|
}
|
|
8250
|
-
|
|
8251
|
-
|
|
8732
|
+
return files;
|
|
8733
|
+
}
|
|
8734
|
+
/**
|
|
8735
|
+
* Generate Cursor jai1 base rule
|
|
8736
|
+
*/
|
|
8737
|
+
generateCursorJai1Rule() {
|
|
8738
|
+
return `---
|
|
8739
|
+
description: Jai1 Framework integration and skill loading
|
|
8740
|
+
globs: ["**/*"]
|
|
8741
|
+
alwaysApply: true
|
|
8742
|
+
---
|
|
8743
|
+
|
|
8744
|
+
${JAI1_BASE_RULE}`;
|
|
8745
|
+
}
|
|
8746
|
+
/**
|
|
8747
|
+
* Generate Windsurf format (.md files with trigger metadata)
|
|
8748
|
+
*/
|
|
8749
|
+
generateWindsurfFiles(bundle, format) {
|
|
8750
|
+
const files = [];
|
|
8751
|
+
files.push({
|
|
8752
|
+
path: `${format.rulesPath}/00-jai1${format.fileExtension}`,
|
|
8753
|
+
content: this.generateWindsurfJai1Rule(),
|
|
8754
|
+
description: "Jai1 Framework base rule"
|
|
8755
|
+
});
|
|
8756
|
+
for (const [filename, content] of Object.entries(bundle.files)) {
|
|
8757
|
+
const converted = this.convertToWindsurf(content, filename);
|
|
8758
|
+
const newFilename = filename.replace(/\.mdc$/, ".md");
|
|
8759
|
+
files.push({
|
|
8760
|
+
path: `${format.rulesPath}/${newFilename}`,
|
|
8761
|
+
content: converted,
|
|
8762
|
+
description: `Preset rule: ${newFilename}`
|
|
8763
|
+
});
|
|
8252
8764
|
}
|
|
8253
|
-
|
|
8765
|
+
return files;
|
|
8766
|
+
}
|
|
8767
|
+
/**
|
|
8768
|
+
* Convert Cursor format to Windsurf format
|
|
8769
|
+
*/
|
|
8770
|
+
convertToWindsurf(content, filename) {
|
|
8771
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
8772
|
+
if (!frontmatterMatch) {
|
|
8773
|
+
return content;
|
|
8774
|
+
}
|
|
8775
|
+
const [, frontmatter, body] = frontmatterMatch;
|
|
8776
|
+
const parsed = this.parseFrontmatter(frontmatter);
|
|
8777
|
+
let trigger = "always";
|
|
8778
|
+
if (parsed.alwaysApply === "true" || parsed.alwaysApply === true) {
|
|
8779
|
+
trigger = "always";
|
|
8780
|
+
} else if (parsed.globs && parsed.globs.length > 0) {
|
|
8781
|
+
trigger = parsed.globs.join(", ");
|
|
8782
|
+
}
|
|
8783
|
+
const windsurfFrontmatter = `---
|
|
8784
|
+
trigger: ${trigger}
|
|
8785
|
+
---`;
|
|
8786
|
+
return `${windsurfFrontmatter}
|
|
8787
|
+
|
|
8788
|
+
${body.trim()}
|
|
8789
|
+
`;
|
|
8790
|
+
}
|
|
8791
|
+
/**
|
|
8792
|
+
* Generate Windsurf jai1 base rule
|
|
8793
|
+
*/
|
|
8794
|
+
generateWindsurfJai1Rule() {
|
|
8795
|
+
return `---
|
|
8796
|
+
trigger: always
|
|
8797
|
+
---
|
|
8798
|
+
|
|
8799
|
+
${JAI1_BASE_RULE}`;
|
|
8800
|
+
}
|
|
8801
|
+
/**
|
|
8802
|
+
* Generate Antigravity format (.md files with trigger metadata + @AGENTS.md reference)
|
|
8803
|
+
*/
|
|
8804
|
+
generateAntigravityFiles(bundle, format) {
|
|
8805
|
+
const files = [];
|
|
8806
|
+
files.push({
|
|
8807
|
+
path: `${format.rulesPath}/00-jai1${format.fileExtension}`,
|
|
8808
|
+
content: this.generateAntigravityJai1Rule(),
|
|
8809
|
+
description: "Jai1 Framework base rule"
|
|
8810
|
+
});
|
|
8811
|
+
for (const [filename, content] of Object.entries(bundle.files)) {
|
|
8812
|
+
const converted = this.convertToAntigravity(content, filename);
|
|
8813
|
+
const newFilename = filename.replace(/\.mdc$/, ".md");
|
|
8814
|
+
files.push({
|
|
8815
|
+
path: `${format.rulesPath}/${newFilename}`,
|
|
8816
|
+
content: converted,
|
|
8817
|
+
description: `Preset rule: ${newFilename}`
|
|
8818
|
+
});
|
|
8819
|
+
}
|
|
8820
|
+
return files;
|
|
8821
|
+
}
|
|
8822
|
+
/**
|
|
8823
|
+
* Convert Cursor format to Antigravity format
|
|
8824
|
+
*/
|
|
8825
|
+
convertToAntigravity(content, filename) {
|
|
8826
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
8827
|
+
if (!frontmatterMatch) {
|
|
8828
|
+
return content;
|
|
8829
|
+
}
|
|
8830
|
+
const [, frontmatter, body] = frontmatterMatch;
|
|
8831
|
+
const parsed = this.parseFrontmatter(frontmatter);
|
|
8832
|
+
let trigger = "always_on";
|
|
8833
|
+
if (parsed.alwaysApply === "true" || parsed.alwaysApply === true) {
|
|
8834
|
+
trigger = "always_on";
|
|
8835
|
+
}
|
|
8836
|
+
const antigravityFrontmatter = `---
|
|
8837
|
+
trigger: ${trigger}
|
|
8838
|
+
---
|
|
8839
|
+
|
|
8840
|
+
@AGENTS.md`;
|
|
8841
|
+
return `${antigravityFrontmatter}
|
|
8842
|
+
|
|
8843
|
+
${body.trim()}
|
|
8844
|
+
`;
|
|
8845
|
+
}
|
|
8846
|
+
/**
|
|
8847
|
+
* Generate Antigravity jai1 base rule
|
|
8848
|
+
*/
|
|
8849
|
+
generateAntigravityJai1Rule() {
|
|
8850
|
+
return `---
|
|
8851
|
+
trigger: always_on
|
|
8852
|
+
---
|
|
8853
|
+
|
|
8854
|
+
@AGENTS.md
|
|
8855
|
+
|
|
8856
|
+
${JAI1_BASE_RULE}`;
|
|
8857
|
+
}
|
|
8858
|
+
/**
|
|
8859
|
+
* Generate Claude Code format (.md files with paths metadata)
|
|
8860
|
+
*/
|
|
8861
|
+
generateClaudeFiles(bundle, format) {
|
|
8862
|
+
const files = [];
|
|
8863
|
+
files.push({
|
|
8864
|
+
path: `${format.rulesPath}/00-jai1${format.fileExtension}`,
|
|
8865
|
+
content: this.generateClaudeJai1Rule(),
|
|
8866
|
+
description: "Jai1 Framework base rule"
|
|
8867
|
+
});
|
|
8868
|
+
for (const [filename, content] of Object.entries(bundle.files)) {
|
|
8869
|
+
const converted = this.convertToClaude(content, filename);
|
|
8870
|
+
const newFilename = filename.replace(/\.mdc$/, ".md");
|
|
8871
|
+
files.push({
|
|
8872
|
+
path: `${format.rulesPath}/${newFilename}`,
|
|
8873
|
+
content: converted,
|
|
8874
|
+
description: `Preset rule: ${newFilename}`
|
|
8875
|
+
});
|
|
8876
|
+
}
|
|
8877
|
+
return files;
|
|
8878
|
+
}
|
|
8879
|
+
/**
|
|
8880
|
+
* Convert Cursor format to Claude format
|
|
8881
|
+
*/
|
|
8882
|
+
convertToClaude(content, filename) {
|
|
8883
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
8884
|
+
if (!frontmatterMatch) {
|
|
8885
|
+
return content;
|
|
8886
|
+
}
|
|
8887
|
+
const [, frontmatter, body] = frontmatterMatch;
|
|
8888
|
+
const parsed = this.parseFrontmatter(frontmatter);
|
|
8889
|
+
let metadata = `---
|
|
8890
|
+
description: ${parsed.description || "Rule"}`;
|
|
8891
|
+
if (parsed.globs && parsed.globs.length > 0) {
|
|
8892
|
+
const paths = parsed.globs.map((g) => g.replace(/[\[\]"]/g, "")).join(", ");
|
|
8893
|
+
metadata += `
|
|
8894
|
+
paths: ${paths}`;
|
|
8895
|
+
}
|
|
8896
|
+
metadata += "\n---";
|
|
8897
|
+
return `${metadata}
|
|
8898
|
+
|
|
8899
|
+
${body.trim()}
|
|
8900
|
+
`;
|
|
8901
|
+
}
|
|
8902
|
+
/**
|
|
8903
|
+
* Generate Claude jai1 base rule
|
|
8904
|
+
*/
|
|
8905
|
+
generateClaudeJai1Rule() {
|
|
8906
|
+
return `---
|
|
8907
|
+
description: Jai1 Framework integration and skill loading
|
|
8908
|
+
paths: **/*
|
|
8909
|
+
---
|
|
8910
|
+
|
|
8911
|
+
${JAI1_BASE_RULE}`;
|
|
8912
|
+
}
|
|
8913
|
+
/**
|
|
8914
|
+
* Generate AGENTS.md file (single merged file)
|
|
8915
|
+
*/
|
|
8916
|
+
generateAgentsMd(bundle, format) {
|
|
8917
|
+
const sections = [];
|
|
8918
|
+
sections.push("# AGENTS.md\n");
|
|
8919
|
+
sections.push(`<!-- Generated by jai1 rules - Preset: ${bundle.preset.slug} v${bundle.preset.version} -->
|
|
8920
|
+
`);
|
|
8921
|
+
sections.push("## Jai1 Framework Integration\n");
|
|
8922
|
+
sections.push(JAI1_BASE_RULE.trim());
|
|
8923
|
+
sections.push("\n---\n");
|
|
8924
|
+
const fileOrder = [
|
|
8925
|
+
"01-project.mdc",
|
|
8926
|
+
"02-standards.mdc",
|
|
8927
|
+
"03-frontend.mdc",
|
|
8928
|
+
"04-backend.mdc",
|
|
8929
|
+
"05-testing.mdc",
|
|
8930
|
+
"06-workflow.mdc",
|
|
8931
|
+
"07-deployment.mdc",
|
|
8932
|
+
"08-database.mdc",
|
|
8933
|
+
"09-custom.mdc"
|
|
8934
|
+
];
|
|
8935
|
+
for (const filename of fileOrder) {
|
|
8936
|
+
if (bundle.files[filename]) {
|
|
8937
|
+
const content = bundle.files[filename];
|
|
8938
|
+
const withoutFrontmatter = this.removeFrontmatter(content);
|
|
8939
|
+
if (withoutFrontmatter.trim()) {
|
|
8940
|
+
sections.push(withoutFrontmatter.trim());
|
|
8941
|
+
sections.push("\n");
|
|
8942
|
+
}
|
|
8943
|
+
}
|
|
8944
|
+
}
|
|
8945
|
+
const agentsMd = sections.join("\n");
|
|
8946
|
+
return [
|
|
8947
|
+
{
|
|
8948
|
+
path: "AGENTS.md",
|
|
8949
|
+
content: agentsMd,
|
|
8950
|
+
description: "Single AGENTS.md file with all rules"
|
|
8951
|
+
}
|
|
8952
|
+
];
|
|
8953
|
+
}
|
|
8954
|
+
/**
|
|
8955
|
+
* Generate Gemini CLI format (GEMINI.md that references AGENTS.md)
|
|
8956
|
+
*/
|
|
8957
|
+
generateGeminiMd(bundle, format) {
|
|
8958
|
+
const content = `# Gemini Instructions
|
|
8959
|
+
|
|
8960
|
+
@AGENTS.md
|
|
8961
|
+
|
|
8962
|
+
You are an AI coding assistant integrated with the Jai1 Framework.
|
|
8963
|
+
Follow all instructions and patterns defined in AGENTS.md above.
|
|
8964
|
+
|
|
8965
|
+
## Additional Gemini-Specific Guidelines
|
|
8966
|
+
|
|
8967
|
+
- Use the context from AGENTS.md for all coding decisions
|
|
8968
|
+
- Apply the Jai1 Framework patterns consistently
|
|
8969
|
+
- Reference skills using the @jai1-[skill-name] syntax
|
|
8970
|
+
- Maintain consistency with project standards
|
|
8971
|
+
|
|
8972
|
+
<!-- Generated by jai1 rules - Preset: ${bundle.preset.slug} v${bundle.preset.version} -->
|
|
8973
|
+
`;
|
|
8974
|
+
return [
|
|
8975
|
+
{
|
|
8976
|
+
path: "GEMINI.md",
|
|
8977
|
+
content,
|
|
8978
|
+
description: "Gemini CLI instructions file"
|
|
8979
|
+
}
|
|
8980
|
+
];
|
|
8981
|
+
}
|
|
8982
|
+
/**
|
|
8983
|
+
* Remove frontmatter from content
|
|
8984
|
+
*/
|
|
8985
|
+
removeFrontmatter(content) {
|
|
8986
|
+
return content.replace(/^---\n[\s\S]*?\n---\n/, "");
|
|
8987
|
+
}
|
|
8988
|
+
/**
|
|
8989
|
+
* Parse YAML frontmatter into object
|
|
8990
|
+
*/
|
|
8991
|
+
parseFrontmatter(frontmatter) {
|
|
8992
|
+
const result = {};
|
|
8993
|
+
const lines = frontmatter.split("\n");
|
|
8994
|
+
for (const line of lines) {
|
|
8995
|
+
const match = line.match(/^(\w+):\s*(.+)$/);
|
|
8996
|
+
if (match) {
|
|
8997
|
+
const [, key, value] = match;
|
|
8998
|
+
if (value.startsWith("[") && value.endsWith("]")) {
|
|
8999
|
+
result[key] = value.slice(1, -1).split(",").map((v) => v.trim().replace(/^["']|["']$/g, ""));
|
|
9000
|
+
} else if (value === "true" || value === "false") {
|
|
9001
|
+
result[key] = value === "true";
|
|
9002
|
+
} else {
|
|
9003
|
+
result[key] = value.replace(/^["']|["']$/g, "");
|
|
9004
|
+
}
|
|
9005
|
+
}
|
|
9006
|
+
}
|
|
9007
|
+
return result;
|
|
9008
|
+
}
|
|
9009
|
+
/**
|
|
9010
|
+
* Get all IDE IDs that can be generated
|
|
9011
|
+
*/
|
|
9012
|
+
getAvailableIdes() {
|
|
9013
|
+
return Object.keys(IDE_FORMATS);
|
|
9014
|
+
}
|
|
9015
|
+
/**
|
|
9016
|
+
* Check if IDE format exists
|
|
9017
|
+
*/
|
|
9018
|
+
isValidIde(ideId) {
|
|
9019
|
+
return ideId in IDE_FORMATS;
|
|
9020
|
+
}
|
|
9021
|
+
/**
|
|
9022
|
+
* Get IDE format details
|
|
9023
|
+
*/
|
|
9024
|
+
getIdeFormat(ideId) {
|
|
9025
|
+
return IDE_FORMATS[ideId];
|
|
9026
|
+
}
|
|
9027
|
+
/**
|
|
9028
|
+
* Resolve IDE dependencies
|
|
9029
|
+
* For example, Gemini requires AGENTS.md
|
|
9030
|
+
*/
|
|
9031
|
+
resolveIdeDependencies(ides) {
|
|
9032
|
+
const resolved = new Set(ides);
|
|
9033
|
+
for (const ide of ides) {
|
|
9034
|
+
const format = IDE_FORMATS[ide];
|
|
9035
|
+
if (format?.dependencies) {
|
|
9036
|
+
format.dependencies.forEach((dep) => resolved.add(dep));
|
|
9037
|
+
}
|
|
9038
|
+
}
|
|
9039
|
+
return Array.from(resolved);
|
|
9040
|
+
}
|
|
9041
|
+
};
|
|
9042
|
+
|
|
9043
|
+
// src/services/backup.service.ts
|
|
9044
|
+
import { promises as fs14 } from "fs";
|
|
9045
|
+
import { join as join8, dirname } from "path";
|
|
9046
|
+
var BackupService = class {
|
|
9047
|
+
backupDir = ".jai1/backups";
|
|
9048
|
+
/**
|
|
9049
|
+
* Create backup of existing rules for specified IDEs
|
|
9050
|
+
*/
|
|
9051
|
+
async createBackup(ides, presetSlug) {
|
|
9052
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
9053
|
+
const backupPath = join8(this.backupDir, timestamp);
|
|
9054
|
+
const backedUpFiles = [];
|
|
9055
|
+
let hasContent = false;
|
|
9056
|
+
for (const ideId of ides) {
|
|
9057
|
+
const format = IDE_FORMATS[ideId];
|
|
9058
|
+
if (!format) {
|
|
9059
|
+
console.warn(`Unknown IDE format: ${ideId}, skipping backup`);
|
|
9060
|
+
continue;
|
|
9061
|
+
}
|
|
9062
|
+
const rulesPath = format.rulesPath === "." ? process.cwd() : join8(process.cwd(), format.rulesPath);
|
|
9063
|
+
try {
|
|
9064
|
+
const exists = await this.pathExists(rulesPath);
|
|
9065
|
+
if (!exists) {
|
|
9066
|
+
continue;
|
|
9067
|
+
}
|
|
9068
|
+
if (ideId === "agentsmd") {
|
|
9069
|
+
await this.backupSingleFile("AGENTS.md", backupPath, ideId, backedUpFiles);
|
|
9070
|
+
hasContent = true;
|
|
9071
|
+
} else if (ideId === "gemini") {
|
|
9072
|
+
await this.backupSingleFile("GEMINI.md", backupPath, ideId, backedUpFiles);
|
|
9073
|
+
hasContent = true;
|
|
9074
|
+
} else {
|
|
9075
|
+
const stats = await fs14.stat(rulesPath);
|
|
9076
|
+
if (stats.isDirectory()) {
|
|
9077
|
+
const files = await fs14.readdir(rulesPath);
|
|
9078
|
+
for (const file of files) {
|
|
9079
|
+
if (file.endsWith(format.fileExtension)) {
|
|
9080
|
+
const originalPath = join8(rulesPath, file);
|
|
9081
|
+
const relativePath = join8(format.rulesPath, file);
|
|
9082
|
+
const destPath = join8(backupPath, ideId, file);
|
|
9083
|
+
await fs14.mkdir(dirname(destPath), { recursive: true });
|
|
9084
|
+
await fs14.copyFile(originalPath, destPath);
|
|
9085
|
+
backedUpFiles.push({
|
|
9086
|
+
originalPath: relativePath,
|
|
9087
|
+
backupPath: join8(ideId, file),
|
|
9088
|
+
ide: ideId
|
|
9089
|
+
});
|
|
9090
|
+
hasContent = true;
|
|
9091
|
+
}
|
|
9092
|
+
}
|
|
9093
|
+
}
|
|
9094
|
+
}
|
|
9095
|
+
} catch (error) {
|
|
9096
|
+
continue;
|
|
9097
|
+
}
|
|
9098
|
+
}
|
|
9099
|
+
if (!hasContent) {
|
|
9100
|
+
return null;
|
|
9101
|
+
}
|
|
9102
|
+
const metadata = {
|
|
9103
|
+
timestamp,
|
|
9104
|
+
preset: presetSlug,
|
|
9105
|
+
ides,
|
|
9106
|
+
files: backedUpFiles
|
|
9107
|
+
};
|
|
9108
|
+
await fs14.mkdir(backupPath, { recursive: true });
|
|
9109
|
+
await fs14.writeFile(
|
|
9110
|
+
join8(backupPath, "metadata.json"),
|
|
9111
|
+
JSON.stringify(metadata, null, 2),
|
|
9112
|
+
"utf-8"
|
|
9113
|
+
);
|
|
9114
|
+
return backupPath;
|
|
9115
|
+
}
|
|
9116
|
+
/**
|
|
9117
|
+
* Backup a single file (for AGENTS.md, GEMINI.md)
|
|
9118
|
+
*/
|
|
9119
|
+
async backupSingleFile(filename, backupPath, ideId, backedUpFiles) {
|
|
9120
|
+
const originalPath = join8(process.cwd(), filename);
|
|
9121
|
+
try {
|
|
9122
|
+
const exists = await this.pathExists(originalPath);
|
|
9123
|
+
if (!exists) {
|
|
9124
|
+
return;
|
|
9125
|
+
}
|
|
9126
|
+
const destPath = join8(backupPath, ideId, filename);
|
|
9127
|
+
await fs14.mkdir(dirname(destPath), { recursive: true });
|
|
9128
|
+
await fs14.copyFile(originalPath, destPath);
|
|
9129
|
+
backedUpFiles.push({
|
|
9130
|
+
originalPath: filename,
|
|
9131
|
+
backupPath: join8(ideId, filename),
|
|
9132
|
+
ide: ideId
|
|
9133
|
+
});
|
|
9134
|
+
} catch (error) {
|
|
9135
|
+
}
|
|
9136
|
+
}
|
|
9137
|
+
/**
|
|
9138
|
+
* Restore files from a backup
|
|
9139
|
+
*/
|
|
9140
|
+
async restoreBackup(backupPath) {
|
|
9141
|
+
const metadataPath = join8(backupPath, "metadata.json");
|
|
9142
|
+
const metadataContent = await fs14.readFile(metadataPath, "utf-8");
|
|
9143
|
+
const metadata = JSON.parse(metadataContent);
|
|
9144
|
+
console.log(`
|
|
9145
|
+
Restoring backup from ${metadata.timestamp}...`);
|
|
9146
|
+
for (const file of metadata.files) {
|
|
9147
|
+
const sourcePath = join8(backupPath, file.backupPath);
|
|
9148
|
+
const destPath = join8(process.cwd(), file.originalPath);
|
|
9149
|
+
await fs14.mkdir(dirname(destPath), { recursive: true });
|
|
9150
|
+
await fs14.copyFile(sourcePath, destPath);
|
|
9151
|
+
console.log(`\u2713 Restored ${file.originalPath}`);
|
|
9152
|
+
}
|
|
9153
|
+
console.log("\n\u2705 Backup restored successfully!");
|
|
9154
|
+
}
|
|
9155
|
+
/**
|
|
9156
|
+
* List all available backups
|
|
9157
|
+
*/
|
|
9158
|
+
async listBackups() {
|
|
9159
|
+
try {
|
|
9160
|
+
const backupDirPath = join8(process.cwd(), this.backupDir);
|
|
9161
|
+
const exists = await this.pathExists(backupDirPath);
|
|
9162
|
+
if (!exists) {
|
|
9163
|
+
return [];
|
|
9164
|
+
}
|
|
9165
|
+
const entries = await fs14.readdir(backupDirPath, { withFileTypes: true });
|
|
9166
|
+
const backups = [];
|
|
9167
|
+
for (const entry of entries) {
|
|
9168
|
+
if (entry.isDirectory()) {
|
|
9169
|
+
const metadataPath = join8(backupDirPath, entry.name, "metadata.json");
|
|
9170
|
+
try {
|
|
9171
|
+
const metadataContent = await fs14.readFile(metadataPath, "utf-8");
|
|
9172
|
+
const metadata = JSON.parse(metadataContent);
|
|
9173
|
+
backups.push(metadata);
|
|
9174
|
+
} catch {
|
|
9175
|
+
continue;
|
|
9176
|
+
}
|
|
9177
|
+
}
|
|
9178
|
+
}
|
|
9179
|
+
return backups.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
9180
|
+
} catch {
|
|
9181
|
+
return [];
|
|
9182
|
+
}
|
|
9183
|
+
}
|
|
9184
|
+
/**
|
|
9185
|
+
* Delete a specific backup
|
|
9186
|
+
*/
|
|
9187
|
+
async deleteBackup(timestamp) {
|
|
9188
|
+
const backupPath = join8(process.cwd(), this.backupDir, timestamp);
|
|
9189
|
+
await this.deleteDirectory(backupPath);
|
|
9190
|
+
}
|
|
9191
|
+
/**
|
|
9192
|
+
* Delete all backups older than specified days
|
|
9193
|
+
*/
|
|
9194
|
+
async cleanupOldBackups(daysToKeep = 30) {
|
|
9195
|
+
const backups = await this.listBackups();
|
|
9196
|
+
const cutoffDate = /* @__PURE__ */ new Date();
|
|
9197
|
+
cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
|
|
9198
|
+
let deletedCount = 0;
|
|
9199
|
+
for (const backup of backups) {
|
|
9200
|
+
const backupDate = new Date(backup.timestamp);
|
|
9201
|
+
if (backupDate < cutoffDate) {
|
|
9202
|
+
await this.deleteBackup(backup.timestamp);
|
|
9203
|
+
deletedCount++;
|
|
9204
|
+
}
|
|
9205
|
+
}
|
|
9206
|
+
return deletedCount;
|
|
9207
|
+
}
|
|
9208
|
+
/**
|
|
9209
|
+
* Get the most recent backup
|
|
9210
|
+
*/
|
|
9211
|
+
async getLatestBackup() {
|
|
9212
|
+
const backups = await this.listBackups();
|
|
9213
|
+
return backups.length > 0 ? backups[0] : null;
|
|
9214
|
+
}
|
|
9215
|
+
/**
|
|
9216
|
+
* Check if a path exists
|
|
9217
|
+
*/
|
|
9218
|
+
async pathExists(path8) {
|
|
9219
|
+
try {
|
|
9220
|
+
await fs14.access(path8);
|
|
9221
|
+
return true;
|
|
9222
|
+
} catch {
|
|
9223
|
+
return false;
|
|
9224
|
+
}
|
|
9225
|
+
}
|
|
9226
|
+
/**
|
|
9227
|
+
* Recursively delete a directory
|
|
9228
|
+
*/
|
|
9229
|
+
async deleteDirectory(path8) {
|
|
9230
|
+
try {
|
|
9231
|
+
const exists = await this.pathExists(path8);
|
|
9232
|
+
if (!exists) {
|
|
9233
|
+
return;
|
|
9234
|
+
}
|
|
9235
|
+
const entries = await fs14.readdir(path8, { withFileTypes: true });
|
|
9236
|
+
for (const entry of entries) {
|
|
9237
|
+
const fullPath = join8(path8, entry.name);
|
|
9238
|
+
if (entry.isDirectory()) {
|
|
9239
|
+
await this.deleteDirectory(fullPath);
|
|
9240
|
+
} else {
|
|
9241
|
+
await fs14.unlink(fullPath);
|
|
9242
|
+
}
|
|
9243
|
+
}
|
|
9244
|
+
await fs14.rmdir(path8);
|
|
9245
|
+
} catch (error) {
|
|
9246
|
+
}
|
|
9247
|
+
}
|
|
9248
|
+
/**
|
|
9249
|
+
* Get backup directory path
|
|
9250
|
+
*/
|
|
9251
|
+
getBackupDir() {
|
|
9252
|
+
return join8(process.cwd(), this.backupDir);
|
|
9253
|
+
}
|
|
9254
|
+
/**
|
|
9255
|
+
* Ensure backup directory exists
|
|
9256
|
+
*/
|
|
9257
|
+
async ensureBackupDir() {
|
|
9258
|
+
const backupDirPath = join8(process.cwd(), this.backupDir);
|
|
9259
|
+
await fs14.mkdir(backupDirPath, { recursive: true });
|
|
9260
|
+
}
|
|
9261
|
+
};
|
|
9262
|
+
|
|
9263
|
+
// src/commands/rules/apply.ts
|
|
9264
|
+
function createRulesApplyCommand() {
|
|
9265
|
+
return new Command38("apply").description("Apply rule preset to project with multi-IDE support").argument("[preset]", "Preset slug to apply (optional)").option("--ides <ides>", "Comma-separated list of IDE formats (cursor,windsurf,antigravity,claude,agentsmd,gemini)").option("--skip-backup", "Skip backup creation").option("-y, --yes", "Skip all confirmations (auto mode)").action(async (presetSlug, options) => {
|
|
9266
|
+
const configService = new ConfigService();
|
|
9267
|
+
const config = await configService.load();
|
|
9268
|
+
if (!config) {
|
|
9269
|
+
throw new ValidationError('Not initialized. Run "jai1 auth" first.');
|
|
9270
|
+
}
|
|
9271
|
+
const generatorService = new RulesGeneratorService();
|
|
9272
|
+
const backupService = new BackupService();
|
|
9273
|
+
const autoMode = options.yes === true;
|
|
9274
|
+
const skipBackup = options.skipBackup === true;
|
|
9275
|
+
let selectedIdes = [];
|
|
9276
|
+
if (options.ides) {
|
|
9277
|
+
selectedIdes = options.ides.split(",").map((ide) => ide.trim()).filter((ide) => generatorService.isValidIde(ide));
|
|
9278
|
+
if (selectedIdes.length === 0) {
|
|
9279
|
+
throw new ValidationError("No valid IDE formats specified");
|
|
9280
|
+
}
|
|
9281
|
+
}
|
|
9282
|
+
if (!presetSlug) {
|
|
9283
|
+
console.log("\u{1F4CB} Fetching available presets...\n");
|
|
9284
|
+
const response = await fetch(`${config.apiUrl}/api/rules/presets`, {
|
|
9285
|
+
headers: {
|
|
9286
|
+
"JAI1-Access-Key": config.accessKey
|
|
9287
|
+
}
|
|
9288
|
+
});
|
|
9289
|
+
if (!response.ok) {
|
|
9290
|
+
throw new Error(`Failed to fetch presets: ${response.statusText}`);
|
|
9291
|
+
}
|
|
9292
|
+
const data = await response.json();
|
|
9293
|
+
if (data.total === 0) {
|
|
9294
|
+
console.log("No presets available.");
|
|
9295
|
+
return;
|
|
9296
|
+
}
|
|
9297
|
+
presetSlug = await select4({
|
|
9298
|
+
message: "Select a preset:",
|
|
9299
|
+
choices: data.presets.map((p) => ({
|
|
9300
|
+
name: `${p.name} - ${p.description}`,
|
|
9301
|
+
value: p.slug,
|
|
9302
|
+
description: `v${p.version} | ${p.tags.join(", ")}`
|
|
9303
|
+
}))
|
|
9304
|
+
});
|
|
9305
|
+
}
|
|
9306
|
+
console.log(`
|
|
9307
|
+
\u{1F4E6} Fetching preset: ${presetSlug}...
|
|
9308
|
+
`);
|
|
9309
|
+
const presetResponse = await fetch(`${config.apiUrl}/api/rules/presets/${presetSlug}`, {
|
|
9310
|
+
headers: {
|
|
9311
|
+
"JAI1-Access-Key": config.accessKey
|
|
9312
|
+
}
|
|
9313
|
+
});
|
|
9314
|
+
if (!presetResponse.ok) {
|
|
9315
|
+
if (presetResponse.status === 404) {
|
|
9316
|
+
throw new Error(`Preset '${presetSlug}' not found`);
|
|
9317
|
+
}
|
|
9318
|
+
throw new Error(`Failed to fetch preset: ${presetResponse.statusText}`);
|
|
9319
|
+
}
|
|
9320
|
+
const bundle = await presetResponse.json();
|
|
9321
|
+
console.log(`\u2713 Preset: ${bundle.preset.name} v${bundle.preset.version}`);
|
|
9322
|
+
console.log(` Files: ${Object.keys(bundle.files).length}`);
|
|
9323
|
+
if (selectedIdes.length === 0) {
|
|
9324
|
+
if (autoMode) {
|
|
9325
|
+
selectedIdes = ["cursor"];
|
|
9326
|
+
} else {
|
|
9327
|
+
const detectionService = new IdeDetectionService();
|
|
9328
|
+
const suggestions = await detectionService.suggestIdes();
|
|
9329
|
+
const detected = await detectionService.detectActiveIdes();
|
|
9330
|
+
if (suggestions.length > 0) {
|
|
9331
|
+
console.log("\n\u{1F4A1} Smart suggestions based on your project:\n");
|
|
9332
|
+
suggestions.slice(0, 3).forEach((s) => {
|
|
9333
|
+
const priority = s.priority === "high" ? "\u2B50" : s.priority === "medium" ? "\u{1F538}" : "\u25AB\uFE0F";
|
|
9334
|
+
console.log(`${priority} ${s.name} - ${s.reason}`);
|
|
9335
|
+
});
|
|
9336
|
+
console.log("");
|
|
9337
|
+
}
|
|
9338
|
+
const choices = [
|
|
9339
|
+
{
|
|
9340
|
+
name: "Cursor (.cursor/rules/)",
|
|
9341
|
+
value: "cursor",
|
|
9342
|
+
checked: suggestions.some((s) => s.ideId === "cursor") || detected.some((d) => d.id === "cursor")
|
|
9343
|
+
},
|
|
9344
|
+
{
|
|
9345
|
+
name: "Windsurf (.windsurf/rules/)",
|
|
9346
|
+
value: "windsurf",
|
|
9347
|
+
checked: detected.some((d) => d.id === "windsurf")
|
|
9348
|
+
},
|
|
9349
|
+
{
|
|
9350
|
+
name: "Antigravity (.agent/rules/)",
|
|
9351
|
+
value: "antigravity",
|
|
9352
|
+
checked: detected.some((d) => d.id === "antigravity")
|
|
9353
|
+
},
|
|
9354
|
+
{
|
|
9355
|
+
name: "Claude Code (.claude/rules/)",
|
|
9356
|
+
value: "claude",
|
|
9357
|
+
checked: suggestions.some((s) => s.ideId === "claude") || detected.some((d) => d.id === "claude")
|
|
9358
|
+
},
|
|
9359
|
+
{
|
|
9360
|
+
name: "AGENTS.md (single file)",
|
|
9361
|
+
value: "agentsmd",
|
|
9362
|
+
checked: suggestions.some((s) => s.ideId === "agentsmd")
|
|
9363
|
+
},
|
|
9364
|
+
{
|
|
9365
|
+
name: "Gemini CLI (GEMINI.md)",
|
|
9366
|
+
value: "gemini",
|
|
9367
|
+
checked: detected.some((d) => d.id === "gemini")
|
|
9368
|
+
}
|
|
9369
|
+
];
|
|
9370
|
+
selectedIdes = await checkbox5({
|
|
9371
|
+
message: "Select IDE formats to generate (pre-selected are recommended):",
|
|
9372
|
+
choices,
|
|
9373
|
+
required: true
|
|
9374
|
+
});
|
|
9375
|
+
}
|
|
9376
|
+
}
|
|
9377
|
+
const resolvedIdes = generatorService.resolveIdeDependencies(selectedIdes);
|
|
9378
|
+
if (resolvedIdes.length > selectedIdes.length) {
|
|
9379
|
+
const addedIdes = resolvedIdes.filter((ide) => !selectedIdes.includes(ide));
|
|
9380
|
+
console.log(`
|
|
9381
|
+
\u{1F4CC} Auto-added dependencies: ${addedIdes.join(", ")}`);
|
|
9382
|
+
}
|
|
9383
|
+
let backupPath = null;
|
|
9384
|
+
if (!skipBackup) {
|
|
9385
|
+
console.log("\n\u{1F4BE} Creating backup of existing rules...");
|
|
9386
|
+
try {
|
|
9387
|
+
backupPath = await backupService.createBackup(resolvedIdes, presetSlug);
|
|
9388
|
+
if (backupPath) {
|
|
9389
|
+
console.log(`\u2713 Backup created: ${backupPath}`);
|
|
9390
|
+
} else {
|
|
9391
|
+
console.log("\u2713 No existing files to backup");
|
|
9392
|
+
}
|
|
9393
|
+
} catch (error) {
|
|
9394
|
+
console.warn("\u26A0\uFE0F Backup failed, but continuing...");
|
|
9395
|
+
}
|
|
9396
|
+
}
|
|
9397
|
+
if (!autoMode) {
|
|
9398
|
+
console.log("\n\u{1F4CB} Summary:");
|
|
9399
|
+
console.log(` Preset: ${bundle.preset.name} v${bundle.preset.version}`);
|
|
9400
|
+
console.log(` IDEs: ${resolvedIdes.join(", ")}`);
|
|
9401
|
+
console.log(` Files: ${Object.keys(bundle.files).length} rule files`);
|
|
9402
|
+
if (backupPath) {
|
|
9403
|
+
console.log(` Backup: ${backupPath}`);
|
|
9404
|
+
}
|
|
9405
|
+
const proceed = await confirm6({
|
|
9406
|
+
message: "Apply these rules to the current directory?",
|
|
9407
|
+
default: true
|
|
9408
|
+
});
|
|
9409
|
+
if (!proceed) {
|
|
9410
|
+
console.log("Cancelled.");
|
|
9411
|
+
return;
|
|
9412
|
+
}
|
|
9413
|
+
}
|
|
9414
|
+
console.log("\n\u{1F4DD} Applying preset...\n");
|
|
9415
|
+
const allGeneratedFiles = [];
|
|
9416
|
+
for (const ideId of resolvedIdes) {
|
|
9417
|
+
try {
|
|
9418
|
+
const files = generatorService.generateForIde(bundle, ideId);
|
|
9419
|
+
for (const file of files) {
|
|
9420
|
+
const fullPath = join9(process.cwd(), file.path);
|
|
9421
|
+
await fs15.mkdir(join9(fullPath, ".."), { recursive: true });
|
|
9422
|
+
await fs15.writeFile(fullPath, file.content, "utf-8");
|
|
9423
|
+
console.log(`\u2713 [${ideId}] ${file.path}`);
|
|
9424
|
+
allGeneratedFiles.push({
|
|
9425
|
+
ide: ideId,
|
|
9426
|
+
path: file.path,
|
|
9427
|
+
description: file.description
|
|
9428
|
+
});
|
|
9429
|
+
}
|
|
9430
|
+
} catch (error) {
|
|
9431
|
+
console.error(`\u2717 Failed to generate ${ideId} files:`, error);
|
|
9432
|
+
}
|
|
9433
|
+
}
|
|
9434
|
+
const projectConfig = {
|
|
9435
|
+
preset: bundle.preset.slug,
|
|
9436
|
+
version: bundle.preset.version,
|
|
9437
|
+
appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9438
|
+
ides: resolvedIdes,
|
|
9439
|
+
customContext: "09-custom.mdc",
|
|
9440
|
+
backups: backupPath ? [
|
|
9441
|
+
{
|
|
9442
|
+
path: backupPath,
|
|
9443
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9444
|
+
ides: resolvedIdes
|
|
9445
|
+
}
|
|
9446
|
+
] : []
|
|
9447
|
+
};
|
|
9448
|
+
try {
|
|
9449
|
+
const existingConfigPath = join9(process.cwd(), "jai1-rules.json");
|
|
9450
|
+
const existingConfigContent = await fs15.readFile(existingConfigPath, "utf-8");
|
|
9451
|
+
const existingConfig = JSON.parse(existingConfigContent);
|
|
9452
|
+
if (existingConfig.backups && existingConfig.backups.length > 0) {
|
|
9453
|
+
projectConfig.backups = [
|
|
9454
|
+
...projectConfig.backups,
|
|
9455
|
+
...existingConfig.backups.slice(0, 5)
|
|
9456
|
+
// Keep last 5 backups
|
|
9457
|
+
];
|
|
9458
|
+
}
|
|
9459
|
+
} catch {
|
|
9460
|
+
}
|
|
9461
|
+
await fs15.writeFile(
|
|
9462
|
+
join9(process.cwd(), "jai1-rules.json"),
|
|
9463
|
+
JSON.stringify(projectConfig, null, 2),
|
|
9464
|
+
"utf-8"
|
|
9465
|
+
);
|
|
9466
|
+
console.log("\n\u2713 Updated jai1-rules.json");
|
|
9467
|
+
console.log("\n\u2705 Preset applied successfully!\n");
|
|
9468
|
+
console.log("\u{1F4CA} Summary:");
|
|
9469
|
+
console.log(` Generated ${allGeneratedFiles.length} files across ${resolvedIdes.length} IDE(s)`);
|
|
9470
|
+
const filesByIde = resolvedIdes.reduce((acc, ide) => {
|
|
9471
|
+
acc[ide] = allGeneratedFiles.filter((f) => f.ide === ide).length;
|
|
9472
|
+
return acc;
|
|
9473
|
+
}, {});
|
|
9474
|
+
for (const [ide, count] of Object.entries(filesByIde)) {
|
|
9475
|
+
const format = IDE_FORMATS[ide];
|
|
9476
|
+
console.log(` - ${format?.name}: ${count} files`);
|
|
9477
|
+
}
|
|
9478
|
+
if (backupPath) {
|
|
9479
|
+
console.log(`
|
|
9480
|
+
\u{1F4BE} Backup saved to: ${backupPath}`);
|
|
9481
|
+
console.log(' Use "jai1 rules restore" to restore if needed');
|
|
9482
|
+
}
|
|
9483
|
+
console.log("\n\u{1F4DD} Next steps:");
|
|
9484
|
+
console.log(" 1. Review generated files in your IDE");
|
|
9485
|
+
console.log(" 2. Edit custom rules if needed (look for 09-custom.*)");
|
|
9486
|
+
console.log(" 3. Commit the rules to git");
|
|
9487
|
+
console.log(' 4. Use "jai1 rules sync" to regenerate after editing');
|
|
9488
|
+
console.log(' 5. Use "jai1 ide status" to check IDE configuration\n');
|
|
8254
9489
|
});
|
|
8255
9490
|
}
|
|
8256
|
-
|
|
8257
|
-
|
|
8258
|
-
|
|
8259
|
-
|
|
8260
|
-
|
|
8261
|
-
|
|
8262
|
-
|
|
9491
|
+
|
|
9492
|
+
// src/commands/rules/restore.ts
|
|
9493
|
+
import { Command as Command39 } from "commander";
|
|
9494
|
+
import { join as join10 } from "path";
|
|
9495
|
+
import { select as select5, confirm as confirm7 } from "@inquirer/prompts";
|
|
9496
|
+
function createRulesRestoreCommand() {
|
|
9497
|
+
return new Command39("restore").description("Restore rules from a backup").option("--latest", "Restore the most recent backup").option("-y, --yes", "Skip confirmation").action(async (options) => {
|
|
9498
|
+
const backupService = new BackupService();
|
|
9499
|
+
const backups = await backupService.listBackups();
|
|
9500
|
+
if (backups.length === 0) {
|
|
9501
|
+
console.log("No backups available.");
|
|
9502
|
+
return;
|
|
9503
|
+
}
|
|
9504
|
+
let selectedBackup;
|
|
9505
|
+
if (options.latest) {
|
|
9506
|
+
selectedBackup = backups[0];
|
|
9507
|
+
console.log(`\u{1F4E6} Selected latest backup: ${selectedBackup.timestamp}`);
|
|
9508
|
+
} else {
|
|
9509
|
+
console.log("\u{1F4CB} Available backups:\n");
|
|
9510
|
+
const backupTimestamp = await select5({
|
|
9511
|
+
message: "Select a backup to restore:",
|
|
9512
|
+
choices: backups.map((backup) => ({
|
|
9513
|
+
name: formatBackupInfo(backup),
|
|
9514
|
+
value: backup.timestamp,
|
|
9515
|
+
description: `${backup.files.length} files | IDEs: ${backup.ides.join(", ")}`
|
|
9516
|
+
}))
|
|
9517
|
+
});
|
|
9518
|
+
selectedBackup = backups.find((b) => b.timestamp === backupTimestamp);
|
|
9519
|
+
if (!selectedBackup) {
|
|
9520
|
+
throw new ValidationError("Backup not found");
|
|
9521
|
+
}
|
|
9522
|
+
}
|
|
9523
|
+
console.log("\n\u{1F4CA} Backup Details:");
|
|
9524
|
+
console.log(` Timestamp: ${formatTimestamp(selectedBackup.timestamp)}`);
|
|
9525
|
+
console.log(` Preset: ${selectedBackup.preset || "N/A"}`);
|
|
9526
|
+
console.log(` IDEs: ${selectedBackup.ides.join(", ")}`);
|
|
9527
|
+
console.log(` Files: ${selectedBackup.files.length}`);
|
|
9528
|
+
if (!options.yes) {
|
|
9529
|
+
const proceed = await confirm7({
|
|
9530
|
+
message: "This will overwrite current rules. Continue?",
|
|
9531
|
+
default: false
|
|
9532
|
+
});
|
|
9533
|
+
if (!proceed) {
|
|
9534
|
+
console.log("Cancelled.");
|
|
9535
|
+
return;
|
|
9536
|
+
}
|
|
9537
|
+
}
|
|
9538
|
+
console.log("\n\u{1F504} Restoring backup...\n");
|
|
9539
|
+
try {
|
|
9540
|
+
const backupPath = join10(backupService.getBackupDir(), selectedBackup.timestamp);
|
|
9541
|
+
await backupService.restoreBackup(backupPath);
|
|
9542
|
+
console.log("\n\u2705 Backup restored successfully!\n");
|
|
9543
|
+
console.log("\u{1F4A1} Tip: Your IDE may need to be restarted to pick up the changes.");
|
|
9544
|
+
} catch (error) {
|
|
9545
|
+
console.error("\n\u274C Failed to restore backup:", error);
|
|
9546
|
+
throw error;
|
|
9547
|
+
}
|
|
9548
|
+
});
|
|
8263
9549
|
}
|
|
8264
|
-
|
|
8265
|
-
const
|
|
8266
|
-
|
|
8267
|
-
|
|
8268
|
-
console.log(`\u2713 Custom context found: ${config.customContext}`);
|
|
8269
|
-
} catch {
|
|
8270
|
-
console.log(`\u26A0\uFE0F Warning: ${config.customContext} not found in .cursor/rules/`);
|
|
8271
|
-
}
|
|
8272
|
-
console.log("\u2713 Cursor rules (.cursor/rules/) - no changes needed");
|
|
8273
|
-
console.log(" (Files are already in place, edit them directly)");
|
|
9550
|
+
function formatBackupInfo(backup) {
|
|
9551
|
+
const timestamp = formatTimestamp(backup.timestamp);
|
|
9552
|
+
const preset = backup.preset ? ` [${backup.preset}]` : "";
|
|
9553
|
+
return `${timestamp}${preset}`;
|
|
8274
9554
|
}
|
|
8275
|
-
|
|
8276
|
-
const rulesDir = join7(process.cwd(), ".cursor", "rules");
|
|
9555
|
+
function formatTimestamp(timestamp) {
|
|
8277
9556
|
try {
|
|
8278
|
-
|
|
9557
|
+
const date = new Date(timestamp);
|
|
9558
|
+
return date.toLocaleString();
|
|
8279
9559
|
} catch {
|
|
8280
|
-
|
|
8281
|
-
return;
|
|
9560
|
+
return timestamp;
|
|
8282
9561
|
}
|
|
8283
|
-
|
|
8284
|
-
|
|
8285
|
-
|
|
8286
|
-
|
|
8287
|
-
|
|
8288
|
-
|
|
8289
|
-
|
|
8290
|
-
|
|
8291
|
-
|
|
8292
|
-
"
|
|
8293
|
-
|
|
8294
|
-
"05-testing.mdc",
|
|
8295
|
-
"06-workflow.mdc",
|
|
8296
|
-
"09-custom.mdc"
|
|
8297
|
-
];
|
|
8298
|
-
for (const filename of fileOrder) {
|
|
8299
|
-
const filePath = join7(rulesDir, filename);
|
|
9562
|
+
}
|
|
9563
|
+
|
|
9564
|
+
// src/commands/rules/sync.ts
|
|
9565
|
+
import { Command as Command40 } from "commander";
|
|
9566
|
+
import { promises as fs16 } from "fs";
|
|
9567
|
+
import { join as join11 } from "path";
|
|
9568
|
+
import { confirm as confirm8 } from "@inquirer/prompts";
|
|
9569
|
+
function createRulesSyncCommand() {
|
|
9570
|
+
return new Command40("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) => {
|
|
9571
|
+
const configPath = join11(process.cwd(), "jai1-rules.json");
|
|
9572
|
+
let projectConfig;
|
|
8300
9573
|
try {
|
|
8301
|
-
const
|
|
8302
|
-
|
|
8303
|
-
sections.push(withoutFrontmatter.trim());
|
|
8304
|
-
sections.push("\n");
|
|
9574
|
+
const configContent = await fs16.readFile(configPath, "utf-8");
|
|
9575
|
+
projectConfig = JSON.parse(configContent);
|
|
8305
9576
|
} catch {
|
|
8306
|
-
|
|
9577
|
+
throw new ValidationError(
|
|
9578
|
+
'No jai1-rules.json found. Run "jai1 rules apply" first.'
|
|
9579
|
+
);
|
|
9580
|
+
}
|
|
9581
|
+
console.log("\u{1F504} Syncing rules...\n");
|
|
9582
|
+
console.log(`Preset: ${projectConfig.preset} v${projectConfig.version}`);
|
|
9583
|
+
let idesToSync = [];
|
|
9584
|
+
if (options.detect) {
|
|
9585
|
+
console.log("\n\u{1F50D} Auto-detecting active IDEs...");
|
|
9586
|
+
const detectionService = new IdeDetectionService();
|
|
9587
|
+
const detected = await detectionService.detectActiveIdes();
|
|
9588
|
+
if (detected.length === 0) {
|
|
9589
|
+
throw new ValidationError(
|
|
9590
|
+
'No active IDEs detected. Run "jai1 rules apply" first or use "jai1 ide status" to check.'
|
|
9591
|
+
);
|
|
9592
|
+
}
|
|
9593
|
+
console.log(`
|
|
9594
|
+
Detected ${detected.length} active IDE(s):
|
|
9595
|
+
`);
|
|
9596
|
+
detected.forEach((d) => {
|
|
9597
|
+
const confidence = d.confidence === "high" ? "\u{1F7E2}" : d.confidence === "medium" ? "\u{1F7E1}" : "\u{1F534}";
|
|
9598
|
+
console.log(` ${confidence} ${d.name} - ${d.ruleCount} rules`);
|
|
9599
|
+
});
|
|
9600
|
+
if (!options.yes) {
|
|
9601
|
+
const proceed = await confirm8({
|
|
9602
|
+
message: "\nSync these detected IDEs?",
|
|
9603
|
+
default: true
|
|
9604
|
+
});
|
|
9605
|
+
if (!proceed) {
|
|
9606
|
+
console.log("Cancelled.");
|
|
9607
|
+
return;
|
|
9608
|
+
}
|
|
9609
|
+
}
|
|
9610
|
+
idesToSync = detected.map((d) => d.id);
|
|
9611
|
+
} else if (options.ides) {
|
|
9612
|
+
const requested = options.ides.split(",").map((ide) => ide.trim());
|
|
9613
|
+
const configured = projectConfig.ides || [];
|
|
9614
|
+
idesToSync = requested;
|
|
9615
|
+
const notConfigured = requested.filter((ide) => !configured.includes(ide));
|
|
9616
|
+
if (notConfigured.length > 0) {
|
|
9617
|
+
console.log(`
|
|
9618
|
+
\u26A0\uFE0F IDEs not in config: ${notConfigured.join(", ")}`);
|
|
9619
|
+
console.log(" They will still be synced, but may not be in jai1-rules.json");
|
|
9620
|
+
}
|
|
9621
|
+
} else {
|
|
9622
|
+
idesToSync = projectConfig.ides || [];
|
|
9623
|
+
if (idesToSync.length === 0) {
|
|
9624
|
+
console.log("\n\u26A0\uFE0F No IDEs configured in jai1-rules.json");
|
|
9625
|
+
console.log(" Detecting from existing files...\n");
|
|
9626
|
+
const detectionService = new IdeDetectionService();
|
|
9627
|
+
idesToSync = await detectionService.detectExistingIdes();
|
|
9628
|
+
if (idesToSync.length === 0) {
|
|
9629
|
+
throw new ValidationError(
|
|
9630
|
+
'No rule outputs found. Run "jai1 rules apply" first.'
|
|
9631
|
+
);
|
|
9632
|
+
}
|
|
9633
|
+
console.log(` Found: ${idesToSync.join(", ")}
|
|
9634
|
+
`);
|
|
9635
|
+
}
|
|
9636
|
+
}
|
|
9637
|
+
console.log(`
|
|
9638
|
+
\u{1F4DD} Syncing ${idesToSync.length} IDE(s): ${idesToSync.join(", ")}
|
|
9639
|
+
`);
|
|
9640
|
+
const configService = new ConfigService();
|
|
9641
|
+
const config = await configService.load();
|
|
9642
|
+
if (!config) {
|
|
9643
|
+
throw new ValidationError('Not initialized. Run "jai1 auth" first.');
|
|
9644
|
+
}
|
|
9645
|
+
const presetResponse = await fetch(`${config.apiUrl}/api/rules/presets/${projectConfig.preset}`, {
|
|
9646
|
+
headers: {
|
|
9647
|
+
"JAI1-Access-Key": config.accessKey
|
|
9648
|
+
}
|
|
9649
|
+
});
|
|
9650
|
+
if (!presetResponse.ok) {
|
|
9651
|
+
throw new Error(`Failed to fetch preset: ${presetResponse.statusText}`);
|
|
9652
|
+
}
|
|
9653
|
+
const bundle = await presetResponse.json();
|
|
9654
|
+
const sourceDir = join11(process.cwd(), ".cursor", "rules");
|
|
9655
|
+
const sourceExists = await checkPathExists(sourceDir);
|
|
9656
|
+
if (sourceExists) {
|
|
9657
|
+
const files = await fs16.readdir(sourceDir);
|
|
9658
|
+
for (const file of files) {
|
|
9659
|
+
if (file.endsWith(".mdc")) {
|
|
9660
|
+
const filePath = join11(sourceDir, file);
|
|
9661
|
+
const content = await fs16.readFile(filePath, "utf-8");
|
|
9662
|
+
bundle.files[file] = content;
|
|
9663
|
+
}
|
|
9664
|
+
}
|
|
9665
|
+
}
|
|
9666
|
+
const generatorService = new RulesGeneratorService();
|
|
9667
|
+
for (const ideId of idesToSync) {
|
|
9668
|
+
try {
|
|
9669
|
+
const format = IDE_FORMATS[ideId];
|
|
9670
|
+
if (!format) {
|
|
9671
|
+
console.log(`\u26A0\uFE0F Unknown IDE format: ${ideId}, skipping`);
|
|
9672
|
+
continue;
|
|
9673
|
+
}
|
|
9674
|
+
const files = generatorService.generateForIde(bundle, ideId);
|
|
9675
|
+
for (const file of files) {
|
|
9676
|
+
const fullPath = join11(process.cwd(), file.path);
|
|
9677
|
+
await fs16.mkdir(join11(fullPath, ".."), { recursive: true });
|
|
9678
|
+
await fs16.writeFile(fullPath, file.content, "utf-8");
|
|
9679
|
+
}
|
|
9680
|
+
console.log(`\u2713 ${format.name} - ${files.length} files regenerated`);
|
|
9681
|
+
} catch (error) {
|
|
9682
|
+
console.error(`\u2717 Failed to sync ${ideId}:`, error);
|
|
9683
|
+
}
|
|
8307
9684
|
}
|
|
9685
|
+
if (JSON.stringify(projectConfig.ides) !== JSON.stringify(idesToSync)) {
|
|
9686
|
+
projectConfig.ides = idesToSync;
|
|
9687
|
+
await fs16.writeFile(
|
|
9688
|
+
join11(process.cwd(), "jai1-rules.json"),
|
|
9689
|
+
JSON.stringify(projectConfig, null, 2),
|
|
9690
|
+
"utf-8"
|
|
9691
|
+
);
|
|
9692
|
+
console.log("\n\u2713 Updated jai1-rules.json with synced IDEs");
|
|
9693
|
+
}
|
|
9694
|
+
console.log("\n\u2705 Rules synced successfully!\n");
|
|
9695
|
+
console.log("\u{1F4A1} Next steps:");
|
|
9696
|
+
console.log(" \u2022 Your IDE may need to be restarted to pick up changes");
|
|
9697
|
+
console.log(' \u2022 Use "jai1 ide status" to verify IDE configuration');
|
|
9698
|
+
console.log(" \u2022 Edit source files in .cursor/rules/ and sync again\n");
|
|
9699
|
+
});
|
|
9700
|
+
}
|
|
9701
|
+
async function checkPathExists(path8) {
|
|
9702
|
+
try {
|
|
9703
|
+
await fs16.access(join11(process.cwd(), path8));
|
|
9704
|
+
return true;
|
|
9705
|
+
} catch {
|
|
9706
|
+
return false;
|
|
8308
9707
|
}
|
|
8309
|
-
const agentsMd = sections.join("\n");
|
|
8310
|
-
await fs13.writeFile("AGENTS.md", agentsMd, "utf-8");
|
|
8311
|
-
console.log("\u2713 Regenerated AGENTS.md");
|
|
8312
9708
|
}
|
|
8313
9709
|
|
|
8314
9710
|
// src/commands/rules/info.ts
|
|
8315
|
-
import { Command as
|
|
8316
|
-
import { promises as
|
|
8317
|
-
import { join as
|
|
9711
|
+
import { Command as Command41 } from "commander";
|
|
9712
|
+
import { promises as fs17 } from "fs";
|
|
9713
|
+
import { join as join12 } from "path";
|
|
8318
9714
|
function createRulesInfoCommand() {
|
|
8319
|
-
return new
|
|
8320
|
-
const configPath =
|
|
9715
|
+
return new Command41("info").description("Show current preset information").option("--json", "Output as JSON").action(async (options) => {
|
|
9716
|
+
const configPath = join12(process.cwd(), "jai1-rules.json");
|
|
8321
9717
|
let projectConfig;
|
|
8322
9718
|
try {
|
|
8323
|
-
const configContent = await
|
|
9719
|
+
const configContent = await fs17.readFile(configPath, "utf-8");
|
|
8324
9720
|
projectConfig = JSON.parse(configContent);
|
|
8325
9721
|
} catch {
|
|
8326
9722
|
throw new ValidationError(
|
|
@@ -8336,43 +9732,72 @@ function createRulesInfoCommand() {
|
|
|
8336
9732
|
console.log(`Version: ${projectConfig.version}`);
|
|
8337
9733
|
console.log(`Applied at: ${new Date(projectConfig.appliedAt).toLocaleString()}`);
|
|
8338
9734
|
console.log(`Custom rules: ${projectConfig.customContext}`);
|
|
8339
|
-
|
|
8340
|
-
|
|
8341
|
-
|
|
8342
|
-
|
|
8343
|
-
|
|
8344
|
-
|
|
8345
|
-
|
|
8346
|
-
|
|
9735
|
+
if (projectConfig.ides && projectConfig.ides.length > 0) {
|
|
9736
|
+
console.log(`
|
|
9737
|
+
Configured IDEs (${projectConfig.ides.length}):`);
|
|
9738
|
+
for (const ideId of projectConfig.ides) {
|
|
9739
|
+
const format = IDE_FORMATS[ideId];
|
|
9740
|
+
if (format) {
|
|
9741
|
+
const exists = await checkIdeFilesExist(ideId, format);
|
|
9742
|
+
const status = exists ? "\u2713" : "\u2717";
|
|
9743
|
+
console.log(` ${status} ${format.name} (${format.description})`);
|
|
9744
|
+
}
|
|
9745
|
+
}
|
|
8347
9746
|
}
|
|
8348
|
-
if (
|
|
8349
|
-
console.log(
|
|
9747
|
+
if (projectConfig.backups && projectConfig.backups.length > 0) {
|
|
9748
|
+
console.log(`
|
|
9749
|
+
Available Backups (${projectConfig.backups.length}):`);
|
|
9750
|
+
for (const backup of projectConfig.backups.slice(0, 3)) {
|
|
9751
|
+
const timestamp = new Date(backup.timestamp).toLocaleString();
|
|
9752
|
+
console.log(` \u2022 ${timestamp} - IDEs: ${backup.ides.join(", ")}`);
|
|
9753
|
+
}
|
|
9754
|
+
if (projectConfig.backups.length > 3) {
|
|
9755
|
+
console.log(` ... and ${projectConfig.backups.length - 3} more`);
|
|
9756
|
+
}
|
|
8350
9757
|
}
|
|
8351
|
-
console.log(
|
|
9758
|
+
console.log("\n\u2139\uFE0F Commands:");
|
|
9759
|
+
console.log(' \u2022 "jai1 rules sync" - Regenerate outputs after editing');
|
|
9760
|
+
console.log(' \u2022 "jai1 rules restore" - Restore from backup');
|
|
9761
|
+
console.log(' \u2022 "jai1 rules apply" - Apply a different preset');
|
|
8352
9762
|
});
|
|
8353
9763
|
}
|
|
8354
9764
|
async function checkPathExists2(path8) {
|
|
8355
9765
|
try {
|
|
8356
|
-
await
|
|
9766
|
+
await fs17.access(join12(process.cwd(), path8));
|
|
8357
9767
|
return true;
|
|
8358
9768
|
} catch {
|
|
8359
9769
|
return false;
|
|
8360
9770
|
}
|
|
8361
9771
|
}
|
|
9772
|
+
async function checkIdeFilesExist(ideId, format) {
|
|
9773
|
+
try {
|
|
9774
|
+
if (ideId === "agentsmd") {
|
|
9775
|
+
return await checkPathExists2("AGENTS.md");
|
|
9776
|
+
} else if (ideId === "gemini") {
|
|
9777
|
+
return await checkPathExists2("GEMINI.md");
|
|
9778
|
+
} else {
|
|
9779
|
+
return await checkPathExists2(format.rulesPath);
|
|
9780
|
+
}
|
|
9781
|
+
} catch {
|
|
9782
|
+
return false;
|
|
9783
|
+
}
|
|
9784
|
+
}
|
|
8362
9785
|
|
|
8363
9786
|
// src/commands/rules/index.ts
|
|
8364
9787
|
function createRulesCommand() {
|
|
8365
|
-
const rulesCommand = new
|
|
9788
|
+
const rulesCommand = new Command42("rules").description("Manage rule presets for AI agents");
|
|
8366
9789
|
rulesCommand.addCommand(createRulesListCommand());
|
|
8367
9790
|
rulesCommand.addCommand(createRulesInitCommand());
|
|
9791
|
+
rulesCommand.addCommand(createRulesApplyCommand());
|
|
9792
|
+
rulesCommand.addCommand(createRulesRestoreCommand());
|
|
8368
9793
|
rulesCommand.addCommand(createRulesSyncCommand());
|
|
8369
9794
|
rulesCommand.addCommand(createRulesInfoCommand());
|
|
8370
9795
|
return rulesCommand;
|
|
8371
9796
|
}
|
|
8372
9797
|
|
|
8373
9798
|
// src/commands/upgrade.ts
|
|
8374
|
-
import { Command as
|
|
8375
|
-
import { confirm as
|
|
9799
|
+
import { Command as Command43 } from "commander";
|
|
9800
|
+
import { confirm as confirm9 } from "@inquirer/prompts";
|
|
8376
9801
|
import { execSync as execSync2 } from "child_process";
|
|
8377
9802
|
var colors3 = {
|
|
8378
9803
|
yellow: "\x1B[33m",
|
|
@@ -8383,7 +9808,7 @@ var colors3 = {
|
|
|
8383
9808
|
bold: "\x1B[1m"
|
|
8384
9809
|
};
|
|
8385
9810
|
function createUpgradeCommand() {
|
|
8386
|
-
return new
|
|
9811
|
+
return new Command43("upgrade").description("Upgrade jai1-client to the latest version").option("--check", "Only check for updates without installing").option("--force", "Force upgrade without confirmation").action(async (options) => {
|
|
8387
9812
|
await handleUpgrade(options);
|
|
8388
9813
|
});
|
|
8389
9814
|
}
|
|
@@ -8427,7 +9852,7 @@ ${colors3.bold}Current version:${colors3.reset} ${currentVersion}`);
|
|
|
8427
9852
|
return;
|
|
8428
9853
|
}
|
|
8429
9854
|
if (!options.force) {
|
|
8430
|
-
const shouldUpdate = await
|
|
9855
|
+
const shouldUpdate = await confirm9({
|
|
8431
9856
|
message: "Update to the latest version now?",
|
|
8432
9857
|
default: true
|
|
8433
9858
|
});
|
|
@@ -8531,11 +9956,11 @@ function getInstallCommand(packageManager2) {
|
|
|
8531
9956
|
}
|
|
8532
9957
|
|
|
8533
9958
|
// src/commands/clean.ts
|
|
8534
|
-
import { Command as
|
|
8535
|
-
import { confirm as
|
|
8536
|
-
import { join as
|
|
9959
|
+
import { Command as Command44 } from "commander";
|
|
9960
|
+
import { confirm as confirm10, select as select6 } from "@inquirer/prompts";
|
|
9961
|
+
import { join as join13 } from "path";
|
|
8537
9962
|
function createCleanCommand() {
|
|
8538
|
-
return new
|
|
9963
|
+
return new Command44("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) => {
|
|
8539
9964
|
await handleClean(options);
|
|
8540
9965
|
});
|
|
8541
9966
|
}
|
|
@@ -8546,7 +9971,7 @@ async function handleClean(options) {
|
|
|
8546
9971
|
{
|
|
8547
9972
|
name: "Backups",
|
|
8548
9973
|
description: "Component backup files (.jai1_backup/)",
|
|
8549
|
-
path:
|
|
9974
|
+
path: join13(cwd, ".jai1_backup"),
|
|
8550
9975
|
check: async () => {
|
|
8551
9976
|
const backups = await service.listBackups(cwd);
|
|
8552
9977
|
return { exists: backups.length > 0, count: backups.length };
|
|
@@ -8596,7 +10021,7 @@ async function handleClean(options) {
|
|
|
8596
10021
|
console.log(` Path: ${target.path}
|
|
8597
10022
|
`);
|
|
8598
10023
|
}
|
|
8599
|
-
const action = await
|
|
10024
|
+
const action = await select6({
|
|
8600
10025
|
message: "What do you want to clean?",
|
|
8601
10026
|
choices: [
|
|
8602
10027
|
...availableTargets.map(({ target, info }) => ({
|
|
@@ -8630,7 +10055,7 @@ async function cleanTarget(target, skipConfirm) {
|
|
|
8630
10055
|
}
|
|
8631
10056
|
const countStr = info.count ? ` (${info.count} items)` : "";
|
|
8632
10057
|
if (!skipConfirm) {
|
|
8633
|
-
const confirmed = await
|
|
10058
|
+
const confirmed = await confirm10({
|
|
8634
10059
|
message: `Delete ${target.name}${countStr}?`,
|
|
8635
10060
|
default: false
|
|
8636
10061
|
});
|
|
@@ -8648,7 +10073,7 @@ async function cleanTarget(target, skipConfirm) {
|
|
|
8648
10073
|
}
|
|
8649
10074
|
|
|
8650
10075
|
// src/commands/redmine/check.ts
|
|
8651
|
-
import { Command as
|
|
10076
|
+
import { Command as Command45 } from "commander";
|
|
8652
10077
|
|
|
8653
10078
|
// src/services/redmine-config.service.ts
|
|
8654
10079
|
import { readFile as readFile6 } from "fs/promises";
|
|
@@ -8955,7 +10380,7 @@ async function checkConnectivity(config) {
|
|
|
8955
10380
|
|
|
8956
10381
|
// src/commands/redmine/check.ts
|
|
8957
10382
|
function createRedmineCheckCommand() {
|
|
8958
|
-
const cmd = new
|
|
10383
|
+
const cmd = new Command45("check").description("Check Redmine connectivity").option("-c, --config <path>", "Config file path", "redmine.config.yaml").option("--json", "Output as JSON").action(async (options) => {
|
|
8959
10384
|
await handleRedmineCheck(options);
|
|
8960
10385
|
});
|
|
8961
10386
|
return cmd;
|
|
@@ -8983,7 +10408,7 @@ async function handleRedmineCheck(options) {
|
|
|
8983
10408
|
}
|
|
8984
10409
|
|
|
8985
10410
|
// src/commands/redmine/sync-issue.ts
|
|
8986
|
-
import { Command as
|
|
10411
|
+
import { Command as Command46 } from "commander";
|
|
8987
10412
|
|
|
8988
10413
|
// src/sync-issue.ts
|
|
8989
10414
|
import { resolve as resolve3, relative } from "path";
|
|
@@ -9126,10 +10551,10 @@ function generateFilename(issueId, title, config, existingSlugs = /* @__PURE__ *
|
|
|
9126
10551
|
|
|
9127
10552
|
// src/file.util.ts
|
|
9128
10553
|
import { readFile as readFile7, writeFile as writeFile2, mkdir } from "fs/promises";
|
|
9129
|
-
import { dirname } from "path";
|
|
10554
|
+
import { dirname as dirname2 } from "path";
|
|
9130
10555
|
import matter3 from "gray-matter";
|
|
9131
10556
|
async function ensureDir(filePath) {
|
|
9132
|
-
const dir =
|
|
10557
|
+
const dir = dirname2(filePath);
|
|
9133
10558
|
await mkdir(dir, { recursive: true });
|
|
9134
10559
|
}
|
|
9135
10560
|
async function readMarkdownFile(filePath) {
|
|
@@ -9359,7 +10784,7 @@ function extractIssueIdFromUrl(url) {
|
|
|
9359
10784
|
|
|
9360
10785
|
// src/commands/redmine/sync-issue.ts
|
|
9361
10786
|
function createSyncIssueCommand() {
|
|
9362
|
-
const cmd = new
|
|
10787
|
+
const cmd = new Command46("issue").description("Sync a single issue").option("-i, --id <number>", "Issue ID").option("-u, --url <url>", "Issue URL").option("--dry-run", "Preview without making changes").option("-c, --config <path>", "Config file path").option("-o, --output-dir <path>", "Output directory").option("--json", "Output as JSON").action(async (options) => {
|
|
9363
10788
|
await handleSyncIssue(options);
|
|
9364
10789
|
});
|
|
9365
10790
|
return cmd;
|
|
@@ -9403,7 +10828,7 @@ async function handleSyncIssue(options) {
|
|
|
9403
10828
|
}
|
|
9404
10829
|
|
|
9405
10830
|
// src/commands/redmine/sync-project.ts
|
|
9406
|
-
import { Command as
|
|
10831
|
+
import { Command as Command47 } from "commander";
|
|
9407
10832
|
|
|
9408
10833
|
// src/sync-project.ts
|
|
9409
10834
|
async function syncProject(config, options = {}) {
|
|
@@ -9473,7 +10898,7 @@ async function syncProject(config, options = {}) {
|
|
|
9473
10898
|
|
|
9474
10899
|
// src/commands/redmine/sync-project.ts
|
|
9475
10900
|
function createSyncProjectCommand() {
|
|
9476
|
-
const cmd = new
|
|
10901
|
+
const cmd = new Command47("project").description("Sync all issues in a project").option("-s, --status <status>", "Filter by status (default: *)", "*").option("--updated-since <date>", "Only sync issues updated since YYYY-MM-DD").option("--concurrency <number>", "Number of concurrent requests").option("--page-size <number>", "Page size for API requests").option("--dry-run", "Preview without making changes").option("-c, --config <path>", "Config file path").option("-o, --output-dir <path>", "Output directory").option("--json", "Output as JSON").action(async (options) => {
|
|
9477
10902
|
await handleSyncProject(options);
|
|
9478
10903
|
});
|
|
9479
10904
|
return cmd;
|
|
@@ -9528,12 +10953,12 @@ async function handleSyncProject(options) {
|
|
|
9528
10953
|
}
|
|
9529
10954
|
|
|
9530
10955
|
// src/commands/framework/info.ts
|
|
9531
|
-
import { Command as
|
|
9532
|
-
import { promises as
|
|
9533
|
-
import { join as
|
|
10956
|
+
import { Command as Command48 } from "commander";
|
|
10957
|
+
import { promises as fs18 } from "fs";
|
|
10958
|
+
import { join as join14 } from "path";
|
|
9534
10959
|
import { homedir as homedir5 } from "os";
|
|
9535
10960
|
function createInfoCommand() {
|
|
9536
|
-
const cmd = new
|
|
10961
|
+
const cmd = new Command48("info").description("Show jai1-client configuration and status").option("--json", "Output as JSON").option("--verbose", "Show detailed information").action(async (options) => {
|
|
9537
10962
|
await handleInfo(options);
|
|
9538
10963
|
});
|
|
9539
10964
|
return cmd;
|
|
@@ -9544,7 +10969,7 @@ async function handleInfo(options) {
|
|
|
9544
10969
|
if (!config) {
|
|
9545
10970
|
throw new ValidationError('Not initialized. Run "jai1 auth" first.');
|
|
9546
10971
|
}
|
|
9547
|
-
const frameworkPath =
|
|
10972
|
+
const frameworkPath = join14(homedir5(), ".jai1", "framework");
|
|
9548
10973
|
const projectStatus = await getProjectStatus2();
|
|
9549
10974
|
const info = {
|
|
9550
10975
|
configPath: configService.getConfigPath(),
|
|
@@ -9579,9 +11004,9 @@ function maskKey3(key) {
|
|
|
9579
11004
|
return "****" + key.slice(-4);
|
|
9580
11005
|
}
|
|
9581
11006
|
async function getProjectStatus2() {
|
|
9582
|
-
const projectJai1 =
|
|
11007
|
+
const projectJai1 = join14(process.cwd(), ".jai1");
|
|
9583
11008
|
try {
|
|
9584
|
-
await
|
|
11009
|
+
await fs18.access(projectJai1);
|
|
9585
11010
|
return { exists: true, version: "Synced" };
|
|
9586
11011
|
} catch {
|
|
9587
11012
|
return { exists: false };
|
|
@@ -9589,8 +11014,8 @@ async function getProjectStatus2() {
|
|
|
9589
11014
|
}
|
|
9590
11015
|
|
|
9591
11016
|
// src/commands/self-update.ts
|
|
9592
|
-
import { Command as
|
|
9593
|
-
import { confirm as
|
|
11017
|
+
import { Command as Command49 } from "commander";
|
|
11018
|
+
import { confirm as confirm11 } from "@inquirer/prompts";
|
|
9594
11019
|
import { execSync as execSync3 } from "child_process";
|
|
9595
11020
|
var colors4 = {
|
|
9596
11021
|
yellow: "\x1B[33m",
|
|
@@ -9601,7 +11026,7 @@ var colors4 = {
|
|
|
9601
11026
|
bold: "\x1B[1m"
|
|
9602
11027
|
};
|
|
9603
11028
|
function createSelfUpdateCommand() {
|
|
9604
|
-
return new
|
|
11029
|
+
return new Command49("self-update").description("Update jai1-client to the latest version").option("--check", "Only check for updates without installing").option("--force", "Force update without confirmation").action(async (options) => {
|
|
9605
11030
|
await handleSelfUpdate(options);
|
|
9606
11031
|
});
|
|
9607
11032
|
}
|
|
@@ -9645,7 +11070,7 @@ ${colors4.bold}Current version:${colors4.reset} ${currentVersion}`);
|
|
|
9645
11070
|
return;
|
|
9646
11071
|
}
|
|
9647
11072
|
if (!options.force) {
|
|
9648
|
-
const shouldUpdate = await
|
|
11073
|
+
const shouldUpdate = await confirm11({
|
|
9649
11074
|
message: "Update to the latest version now?",
|
|
9650
11075
|
default: true
|
|
9651
11076
|
});
|
|
@@ -9741,10 +11166,10 @@ function getInstallCommand2(packageManager2) {
|
|
|
9741
11166
|
}
|
|
9742
11167
|
|
|
9743
11168
|
// src/commands/clear-backups.ts
|
|
9744
|
-
import { Command as
|
|
9745
|
-
import { confirm as
|
|
11169
|
+
import { Command as Command50 } from "commander";
|
|
11170
|
+
import { confirm as confirm12 } from "@inquirer/prompts";
|
|
9746
11171
|
function createClearBackupsCommand() {
|
|
9747
|
-
return new
|
|
11172
|
+
return new Command50("clear-backups").description("Clear backup files").option("-y, --yes", "Skip confirmation").action(async (options) => {
|
|
9748
11173
|
const service = new ComponentsService();
|
|
9749
11174
|
const backups = await service.listBackups(process.cwd());
|
|
9750
11175
|
if (backups.length === 0) {
|
|
@@ -9760,7 +11185,7 @@ function createClearBackupsCommand() {
|
|
|
9760
11185
|
}
|
|
9761
11186
|
console.log();
|
|
9762
11187
|
if (!options.yes) {
|
|
9763
|
-
const ok = await
|
|
11188
|
+
const ok = await confirm12({ message: "Delete all backups?", default: false });
|
|
9764
11189
|
if (!ok) return;
|
|
9765
11190
|
}
|
|
9766
11191
|
await service.clearBackups(process.cwd());
|
|
@@ -9769,9 +11194,9 @@ function createClearBackupsCommand() {
|
|
|
9769
11194
|
}
|
|
9770
11195
|
|
|
9771
11196
|
// src/commands/vscode/index.ts
|
|
9772
|
-
import { Command as
|
|
9773
|
-
import { checkbox as
|
|
9774
|
-
import
|
|
11197
|
+
import { Command as Command51 } from "commander";
|
|
11198
|
+
import { checkbox as checkbox7, confirm as confirm13, select as select7 } from "@inquirer/prompts";
|
|
11199
|
+
import fs19 from "fs/promises";
|
|
9775
11200
|
import path7 from "path";
|
|
9776
11201
|
import { existsSync as existsSync3 } from "fs";
|
|
9777
11202
|
var PERFORMANCE_GROUPS2 = {
|
|
@@ -9909,7 +11334,7 @@ var PERFORMANCE_GROUPS2 = {
|
|
|
9909
11334
|
}
|
|
9910
11335
|
};
|
|
9911
11336
|
function createVSCodeCommand() {
|
|
9912
|
-
const vscodeCommand = new
|
|
11337
|
+
const vscodeCommand = new Command51("vscode").description("Qu\u1EA3n l\xFD c\xE0i \u0111\u1EB7t VSCode cho d\u1EF1 \xE1n hi\u1EC7n t\u1EA1i");
|
|
9913
11338
|
vscodeCommand.action(async () => {
|
|
9914
11339
|
await interactiveMode2();
|
|
9915
11340
|
});
|
|
@@ -9953,7 +11378,7 @@ async function interactiveMode2() {
|
|
|
9953
11378
|
console.log("\u2502 \u2022 Nh\u1EA5n ENTER \u0111\u1EC3 x\xE1c nh\u1EADn v\xE0 \xE1p d\u1EE5ng \u2502");
|
|
9954
11379
|
console.log("\u2502 \u2022 Nh\u1EA5n Ctrl+C \u0111\u1EC3 h\u1EE7y \u2502");
|
|
9955
11380
|
console.log("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F\n");
|
|
9956
|
-
const action = await
|
|
11381
|
+
const action = await select7({
|
|
9957
11382
|
message: "B\u1EA1n mu\u1ED1n l\xE0m g\xEC?",
|
|
9958
11383
|
choices: [
|
|
9959
11384
|
{ name: "\u2705 Enable c\xE1c nh\xF3m t\u1ED1i \u01B0u", value: "enable" },
|
|
@@ -9977,7 +11402,7 @@ async function selectGroupsToApply2(action) {
|
|
|
9977
11402
|
value: key
|
|
9978
11403
|
}));
|
|
9979
11404
|
try {
|
|
9980
|
-
const selectedGroups = await
|
|
11405
|
+
const selectedGroups = await checkbox7({
|
|
9981
11406
|
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):`,
|
|
9982
11407
|
choices
|
|
9983
11408
|
});
|
|
@@ -10002,18 +11427,18 @@ async function applyGroups2(groupKeys, action) {
|
|
|
10002
11427
|
return;
|
|
10003
11428
|
}
|
|
10004
11429
|
if (!existsSync3(vscodeDir)) {
|
|
10005
|
-
await
|
|
11430
|
+
await fs19.mkdir(vscodeDir, { recursive: true });
|
|
10006
11431
|
console.log("\u{1F4C1} \u0110\xE3 t\u1EA1o th\u01B0 m\u1EE5c .vscode/");
|
|
10007
11432
|
}
|
|
10008
11433
|
let currentSettings = {};
|
|
10009
11434
|
if (existsSync3(settingsPath)) {
|
|
10010
11435
|
try {
|
|
10011
|
-
const content = await
|
|
11436
|
+
const content = await fs19.readFile(settingsPath, "utf-8");
|
|
10012
11437
|
currentSettings = JSON.parse(content);
|
|
10013
11438
|
console.log("\u{1F4C4} \u0110\xE3 \u0111\u1ECDc c\xE0i \u0111\u1EB7t hi\u1EC7n t\u1EA1i t\u1EEB settings.json");
|
|
10014
11439
|
} catch {
|
|
10015
11440
|
console.warn("\u26A0\uFE0F Kh\xF4ng th\u1EC3 \u0111\u1ECDc settings.json (c\xF3 th\u1EC3 ch\u1EE9a comments).");
|
|
10016
|
-
const confirmOverwrite = await
|
|
11441
|
+
const confirmOverwrite = await confirm13({
|
|
10017
11442
|
message: "Ghi \u0111\xE8 file settings.json hi\u1EC7n t\u1EA1i?",
|
|
10018
11443
|
default: false
|
|
10019
11444
|
});
|
|
@@ -10048,7 +11473,7 @@ async function applyGroups2(groupKeys, action) {
|
|
|
10048
11473
|
}
|
|
10049
11474
|
}
|
|
10050
11475
|
}
|
|
10051
|
-
await
|
|
11476
|
+
await fs19.writeFile(settingsPath, JSON.stringify(newSettings, null, 2));
|
|
10052
11477
|
console.log(`
|
|
10053
11478
|
\u2705 \u0110\xE3 c\u1EADp nh\u1EADt c\xE0i \u0111\u1EB7t VSCode t\u1EA1i: ${settingsPath}`);
|
|
10054
11479
|
console.log("\u{1F4A1} M\u1EB9o: Kh\u1EDFi \u0111\u1ED9ng l\u1EA1i VSCode \u0111\u1EC3 \xE1p d\u1EE5ng c\xE1c thay \u0111\u1ED5i.");
|
|
@@ -10060,7 +11485,7 @@ async function resetSettings2(groupKeys) {
|
|
|
10060
11485
|
console.log("\n\u26A0\uFE0F Kh\xF4ng t\xECm th\u1EA5y file settings.json");
|
|
10061
11486
|
return;
|
|
10062
11487
|
}
|
|
10063
|
-
const confirmReset = await
|
|
11488
|
+
const confirmReset = await confirm13({
|
|
10064
11489
|
message: groupKeys.length === 0 ? "Reset T\u1EA4T C\u1EA2 settings v\u1EC1 m\u1EB7c \u0111\u1ECBnh (x\xF3a to\xE0n b\u1ED9 file)?" : `Reset c\xE1c nh\xF3m: ${groupKeys.join(", ")}?`,
|
|
10065
11490
|
default: false
|
|
10066
11491
|
});
|
|
@@ -10069,7 +11494,7 @@ async function resetSettings2(groupKeys) {
|
|
|
10069
11494
|
return;
|
|
10070
11495
|
}
|
|
10071
11496
|
if (groupKeys.length === 0) {
|
|
10072
|
-
await
|
|
11497
|
+
await fs19.unlink(settingsPath);
|
|
10073
11498
|
console.log("\n\u2705 \u0110\xE3 x\xF3a file settings.json");
|
|
10074
11499
|
} else {
|
|
10075
11500
|
await applyGroups2(groupKeys, "disable");
|
|
@@ -10080,9 +11505,9 @@ async function resetSettings2(groupKeys) {
|
|
|
10080
11505
|
// src/commands/guide.ts
|
|
10081
11506
|
import React40 from "react";
|
|
10082
11507
|
import { render as render6 } from "ink";
|
|
10083
|
-
import { Command as
|
|
11508
|
+
import { Command as Command52 } from "commander";
|
|
10084
11509
|
function createGuideCommand() {
|
|
10085
|
-
const cmd = new
|
|
11510
|
+
const cmd = new Command52("guide").description("Interactive guide center for Agentic Coding").option("--topic <topic>", "Open specific topic (intro, rules, workflows, prompts, skills)").action(async (options) => {
|
|
10086
11511
|
const { waitUntilExit } = render6(
|
|
10087
11512
|
React40.createElement(GuideApp, {
|
|
10088
11513
|
initialTopic: options.topic,
|
|
@@ -10099,9 +11524,9 @@ function createGuideCommand() {
|
|
|
10099
11524
|
// src/commands/context.ts
|
|
10100
11525
|
import React41 from "react";
|
|
10101
11526
|
import { render as render7 } from "ink";
|
|
10102
|
-
import { Command as
|
|
11527
|
+
import { Command as Command53 } from "commander";
|
|
10103
11528
|
function createContextCommand() {
|
|
10104
|
-
const cmd = new
|
|
11529
|
+
const cmd = new Command53("context").description("Kh\xE1m ph\xE1 v\xE0 qu\u1EA3n l\xFD context d\u1EF1 \xE1n cho c\xE1c IDE").option("--ide <ide>", "M\u1EDF tr\u1EF1c ti\u1EBFp IDE c\u1EE5 th\u1EC3 (cursor, windsurf, antigravity, jai1)").option("--type <type>", "Hi\u1EC3n th\u1ECB lo\u1EA1i context c\u1EE5 th\u1EC3 (rules, workflows, skills, agents, prompts)").option("--stats", "Hi\u1EC3n th\u1ECB th\u1ED1ng k\xEA context (non-interactive)").action(async (options) => {
|
|
10105
11530
|
let initialIDE;
|
|
10106
11531
|
if (options.ide) {
|
|
10107
11532
|
const validIDEs = ["cursor", "windsurf", "antigravity", "jai1"];
|
|
@@ -10178,10 +11603,10 @@ async function printStats2() {
|
|
|
10178
11603
|
}
|
|
10179
11604
|
|
|
10180
11605
|
// src/commands/migrate-ide.ts
|
|
10181
|
-
import { Command as
|
|
10182
|
-
import { checkbox as
|
|
11606
|
+
import { Command as Command54 } from "commander";
|
|
11607
|
+
import { checkbox as checkbox8, confirm as confirm14 } from "@inquirer/prompts";
|
|
10183
11608
|
function createMigrateIdeCommand() {
|
|
10184
|
-
const cmd = new
|
|
11609
|
+
const cmd = new Command54("migrate-ide").description("Migrate .jai1 rules v\xE0 workflows sang IDEs (Cursor, Windsurf, Claude Code, etc.)").option("--ide <ides...>", "Target IDEs (cursor, windsurf, antigravity, claudecode, opencode)").option("--type <types...>", "Content types (rules, workflows, commands)").option("--dry-run", "Preview changes without writing files").action(async (options) => {
|
|
10185
11610
|
await runMigrateIde(options);
|
|
10186
11611
|
});
|
|
10187
11612
|
return cmd;
|
|
@@ -10209,7 +11634,7 @@ async function runMigrateIde(options) {
|
|
|
10209
11634
|
value: ide
|
|
10210
11635
|
};
|
|
10211
11636
|
});
|
|
10212
|
-
selectedIdes = await
|
|
11637
|
+
selectedIdes = await checkbox8({
|
|
10213
11638
|
message: "Ch\u1ECDn IDE(s) \u0111\u1EC3 migrate (SPACE \u0111\u1EC3 ch\u1ECDn, ENTER \u0111\u1EC3 x\xE1c nh\u1EADn):",
|
|
10214
11639
|
choices: ideChoices
|
|
10215
11640
|
});
|
|
@@ -10228,7 +11653,7 @@ async function runMigrateIde(options) {
|
|
|
10228
11653
|
{ name: `Workflows (${content.workflows.length} files)`, value: "workflows" },
|
|
10229
11654
|
{ name: `Commands (${content.commands.length} files)`, value: "commands" }
|
|
10230
11655
|
];
|
|
10231
|
-
selectedTypes = await
|
|
11656
|
+
selectedTypes = await checkbox8({
|
|
10232
11657
|
message: "Ch\u1ECDn content types \u0111\u1EC3 migrate:",
|
|
10233
11658
|
choices: typeChoices
|
|
10234
11659
|
});
|
|
@@ -10250,7 +11675,7 @@ async function runMigrateIde(options) {
|
|
|
10250
11675
|
if (options.dryRun) {
|
|
10251
11676
|
console.log("\u{1F50D} DRY RUN - No files will be written\n");
|
|
10252
11677
|
}
|
|
10253
|
-
const confirmed = await
|
|
11678
|
+
const confirmed = await confirm14({
|
|
10254
11679
|
message: "Proceed with migration?",
|
|
10255
11680
|
default: true
|
|
10256
11681
|
});
|
|
@@ -10287,7 +11712,7 @@ async function runMigrateIde(options) {
|
|
|
10287
11712
|
}
|
|
10288
11713
|
|
|
10289
11714
|
// src/cli.ts
|
|
10290
|
-
var program = new
|
|
11715
|
+
var program = new Command55();
|
|
10291
11716
|
if (process.argv.includes("-v") || process.argv.includes("--version")) {
|
|
10292
11717
|
console.log(package_default.version);
|
|
10293
11718
|
if (!process.argv.includes("--skip-update-check")) {
|
|
@@ -10314,9 +11739,9 @@ program.addCommand(createKitCommand());
|
|
|
10314
11739
|
program.addCommand(createRulesCommand());
|
|
10315
11740
|
program.addCommand(createUpgradeCommand());
|
|
10316
11741
|
program.addCommand(createCleanCommand());
|
|
10317
|
-
var redmineCommand = new
|
|
11742
|
+
var redmineCommand = new Command55("redmine").description("Redmine context sync commands");
|
|
10318
11743
|
redmineCommand.addCommand(createRedmineCheckCommand());
|
|
10319
|
-
var syncCommand = new
|
|
11744
|
+
var syncCommand = new Command55("sync").description("Sync Redmine issues to markdown files");
|
|
10320
11745
|
syncCommand.addCommand(createSyncIssueCommand());
|
|
10321
11746
|
syncCommand.addCommand(createSyncProjectCommand());
|
|
10322
11747
|
redmineCommand.addCommand(syncCommand);
|