@jvittechs/jai1-cli 0.1.91 → 0.1.93

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.
Files changed (3) hide show
  1. package/dist/cli.js +1821 -1465
  2. package/dist/cli.js.map +1 -1
  3. package/package.json +2 -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 Command55 } from "commander";
4
+ import { Command as Command60 } 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.91",
36
+ version: "0.1.93",
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: {
@@ -97,6 +97,7 @@ var package_default = {
97
97
  "p-retry": "^6.2.0",
98
98
  react: "^18.3.1",
99
99
  slugify: "^1.6.6",
100
+ "terminal-image": "^4.1.0",
100
101
  undici: "^6.19.5",
101
102
  yaml: "^2.5.0",
102
103
  zod: "^3.23.8"
@@ -771,554 +772,965 @@ import { Box as Box2, Text as Text3, useInput, useApp } from "ink";
771
772
  import Spinner from "ink-spinner";
772
773
  import TextInput from "ink-text-input";
773
774
 
774
- // src/ui/shared/ProgressBar.tsx
775
- import React from "react";
776
- import { Box, Text } from "ink";
777
- var ProgressBar = ({
778
- current,
779
- total,
780
- width = 40,
781
- showPercentage = true,
782
- color = "cyan"
783
- }) => {
784
- const percentage = total > 0 ? Math.round(current / total * 100) : 0;
785
- const filled = Math.round(percentage / 100 * width);
786
- const empty = width - filled;
787
- const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
788
- return /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color }, bar), showPercentage && /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " ", current, "/", total, " (", percentage, "%)"));
789
- };
790
-
791
- // src/ui/shared/StatusIcon.tsx
792
- import React2 from "react";
793
- import { Text as Text2 } from "ink";
794
-
795
- // src/ui/shared/theme.ts
796
- var theme = {
797
- colors: {
798
- primary: "#6366f1",
799
- // Indigo
800
- success: "#22c55e",
801
- // Green
802
- warning: "#eab308",
803
- // Yellow
804
- error: "#ef4444",
805
- // Red
806
- muted: "#6b7280",
807
- // Gray
808
- border: "#374151",
809
- // Dark gray
810
- text: "#f3f4f6",
811
- // Light gray
812
- background: "#1a1a2e"
813
- // Dark background
814
- },
815
- icons: {
816
- success: "\u2705",
817
- error: "\u274C",
818
- pending: "\u25CB",
819
- loading: "\u23F3",
820
- package: "\u{1F4E6}",
821
- folder: "\u{1F4C1}",
822
- file: "\u{1F4C4}",
823
- tip: "\u{1F4A1}",
824
- warning: "\u26A0\uFE0F",
825
- checkmark: "\u2713",
826
- arrow: "\u2192",
827
- bullet: "\u2022"
828
- },
829
- borders: {
830
- single: {
831
- topLeft: "\u250C",
832
- topRight: "\u2510",
833
- bottomLeft: "\u2514",
834
- bottomRight: "\u2518",
835
- horizontal: "\u2500",
836
- vertical: "\u2502"
837
- },
838
- double: {
839
- topLeft: "\u2554",
840
- topRight: "\u2557",
841
- bottomLeft: "\u255A",
842
- bottomRight: "\u255D",
843
- horizontal: "\u2550",
844
- vertical: "\u2551"
845
- }
846
- }
847
- };
848
-
849
- // src/ui/shared/StatusIcon.tsx
850
- var StatusIcon = ({ status, text }) => {
851
- const iconMap = {
852
- success: theme.icons.success,
853
- error: theme.icons.error,
854
- pending: theme.icons.pending,
855
- loading: theme.icons.loading,
856
- warning: theme.icons.warning
857
- };
858
- const colorMap = {
859
- success: "green",
860
- error: "red",
861
- pending: "gray",
862
- loading: "yellow",
863
- warning: "yellow"
864
- };
865
- const icon = iconMap[status];
866
- const color = colorMap[status];
867
- return /* @__PURE__ */ React2.createElement(Text2, { color }, icon, " ", text);
868
- };
775
+ // src/services/migrate-ide.service.ts
776
+ import { promises as fs4 } from "fs";
777
+ import path from "path";
778
+ import matter from "gray-matter";
869
779
 
870
- // src/ui/apply/UnifiedApplyApp.tsx
871
- var UnifiedApplyApp = ({
872
- config,
873
- force = false,
874
- onExit
875
- }) => {
876
- const { exit } = useApp();
877
- const [viewState, setViewState] = useState("browse");
878
- const [components, setComponents] = useState([]);
879
- const [tags, setTags] = useState([]);
880
- const [installedPaths, setInstalledPaths] = useState(/* @__PURE__ */ new Set());
881
- const [loading, setLoading] = useState(true);
882
- const [error, setError] = useState(null);
883
- const [searchQuery, setSearchQuery] = useState("");
884
- const [selectedPaths, setSelectedPaths] = useState(/* @__PURE__ */ new Set());
885
- const [cursorIndex, setCursorIndex] = useState(0);
886
- const [focusArea, setFocusArea] = useState("packages");
887
- const [selectedPackageIndex, setSelectedPackageIndex] = useState(0);
888
- const [installProgress, setInstallProgress] = useState([]);
889
- const [installStats, setInstallStats] = useState({ total: 0, completed: 0, added: 0, updated: 0, failed: 0 });
890
- const service = new ComponentsService();
891
- useEffect(() => {
892
- const loadData = async () => {
893
- try {
894
- setLoading(true);
895
- const [comps, tagList, installed] = await Promise.all([
896
- service.list(config),
897
- service.listTags(config),
898
- service.getInstalled()
899
- ]);
900
- setComponents(comps);
901
- setTags(tagList);
902
- setInstalledPaths(new Set(Object.keys(installed)));
903
- } catch (err) {
904
- setError(err instanceof Error ? err.message : "Failed to load data");
905
- } finally {
906
- setLoading(false);
780
+ // src/constants/ide-migration-configs.ts
781
+ var IDE_MIGRATION_CONFIGS = {
782
+ cursor: {
783
+ id: "cursor",
784
+ name: "Cursor",
785
+ icon: "\u{1F52E}",
786
+ basePath: ".cursor",
787
+ rulesPath: "rules",
788
+ workflowsPath: null,
789
+ commandsPath: "commands",
790
+ fileExtension: ".mdc",
791
+ generateFrontmatter: (opts) => {
792
+ const lines = ["---"];
793
+ if (opts.description) {
794
+ lines.push(`description: ${opts.description}`);
907
795
  }
908
- };
909
- loadData();
910
- }, []);
911
- const filteredComponents = useMemo(() => {
912
- if (!searchQuery.trim()) return components;
913
- const query = searchQuery.toLowerCase();
914
- return components.filter(
915
- (c) => c.filepath.toLowerCase().includes(query) || c.name.toLowerCase().includes(query) || c.tags && c.tags.some((t) => t.toLowerCase().includes(query))
916
- );
917
- }, [components, searchQuery]);
918
- useInput((input4, key) => {
919
- if (viewState === "summary") {
920
- if (key.return || input4 === "q" || key.escape) {
921
- onExit();
796
+ if (opts.globs && opts.globs.length > 0) {
797
+ lines.push(`globs: ${opts.globs.join(", ")}`);
922
798
  }
923
- return;
924
- }
925
- if (viewState === "installing") {
926
- return;
799
+ lines.push(`alwaysApply: ${opts.alwaysApply}`);
800
+ lines.push("---");
801
+ return lines.join("\n");
927
802
  }
928
- if (key.tab) {
929
- if (focusArea === "packages") setFocusArea("components");
930
- else if (focusArea === "components") setFocusArea("search");
931
- else setFocusArea("packages");
932
- return;
803
+ },
804
+ windsurf: {
805
+ id: "windsurf",
806
+ name: "Windsurf",
807
+ icon: "\u{1F3C4}",
808
+ basePath: ".windsurf",
809
+ rulesPath: "rules",
810
+ workflowsPath: "workflows",
811
+ commandsPath: null,
812
+ fileExtension: ".md",
813
+ generateFrontmatter: (opts) => {
814
+ const trigger = opts.alwaysApply ? "always" : "glob";
815
+ return `---
816
+ trigger: ${trigger}
817
+ ---`;
933
818
  }
934
- if (key.escape || input4 === "q") {
935
- onExit();
936
- return;
819
+ },
820
+ antigravity: {
821
+ id: "antigravity",
822
+ name: "Antigravity",
823
+ icon: "\u{1F680}",
824
+ basePath: ".agent",
825
+ rulesPath: "rules",
826
+ workflowsPath: "workflows",
827
+ commandsPath: null,
828
+ fileExtension: ".md",
829
+ generateFrontmatter: (opts) => {
830
+ const trigger = opts.alwaysApply ? "always" : "glob";
831
+ return `---
832
+ trigger: ${trigger}
833
+ ---`;
937
834
  }
938
- if (key.return && focusArea !== "search") {
939
- if (selectedPaths.size > 0) {
940
- handleApply();
835
+ },
836
+ claudecode: {
837
+ id: "claudecode",
838
+ name: "Claude Code",
839
+ icon: "\u{1F916}",
840
+ basePath: ".claude",
841
+ rulesPath: "rules",
842
+ workflowsPath: null,
843
+ commandsPath: "commands",
844
+ fileExtension: ".md",
845
+ generateFrontmatter: (opts) => {
846
+ const lines = ["---"];
847
+ if (opts.description) {
848
+ lines.push(`description: ${opts.description}`);
941
849
  }
942
- return;
943
- }
944
- if (focusArea !== "search") {
945
- if (input4 === "a") {
946
- setSelectedPaths((prev) => {
947
- const next = new Set(prev);
948
- filteredComponents.forEach((c) => next.add(c.filepath));
949
- return next;
950
- });
951
- return;
952
- } else if (input4 === "c") {
953
- setSelectedPaths(/* @__PURE__ */ new Set());
954
- return;
850
+ if (opts.globs && opts.globs.length > 0) {
851
+ lines.push(`paths: ${opts.globs.join(", ")}`);
955
852
  }
853
+ lines.push("---");
854
+ return lines.join("\n");
956
855
  }
957
- if (focusArea === "components") {
958
- if (key.upArrow) {
959
- setCursorIndex((prev) => Math.max(0, prev - 1));
960
- } else if (key.downArrow) {
961
- setCursorIndex((prev) => Math.min(filteredComponents.length - 1, prev + 1));
962
- } else if (input4 === " ") {
963
- const current = filteredComponents[cursorIndex];
964
- if (current) {
965
- setSelectedPaths((prev) => {
966
- const next = new Set(prev);
967
- if (next.has(current.filepath)) {
968
- next.delete(current.filepath);
969
- } else {
970
- next.add(current.filepath);
971
- }
972
- return next;
973
- });
974
- }
975
- }
856
+ },
857
+ opencode: {
858
+ id: "opencode",
859
+ name: "OpenCode",
860
+ icon: "\u{1F4BB}",
861
+ basePath: ".opencode",
862
+ rulesPath: "rules",
863
+ workflowsPath: "workflows",
864
+ commandsPath: null,
865
+ fileExtension: ".md",
866
+ generateFrontmatter: (opts) => {
867
+ const trigger = opts.alwaysApply ? "always" : "glob";
868
+ return `---
869
+ trigger: ${trigger}
870
+ ---`;
976
871
  }
977
- if (focusArea === "packages") {
978
- if (key.leftArrow) {
979
- setSelectedPackageIndex((prev) => Math.max(0, prev - 1));
980
- } else if (key.rightArrow) {
981
- setSelectedPackageIndex((prev) => Math.min(tags.length - 1, prev + 1));
982
- } else if (input4 === " " || key.return) {
983
- const tag = tags[selectedPackageIndex];
984
- if (tag) {
985
- const packageComponents = components.filter((c) => c.tags?.includes(tag.tag));
986
- setSelectedPaths((prev) => {
987
- const next = new Set(prev);
988
- packageComponents.forEach((c) => next.add(c.filepath));
989
- return next;
990
- });
991
- setSearchQuery(tag.tag);
992
- }
993
- }
872
+ }
873
+ };
874
+ function getMigrationIDEs() {
875
+ return Object.keys(IDE_MIGRATION_CONFIGS);
876
+ }
877
+
878
+ // src/services/migrate-ide.service.ts
879
+ var MigrateIdeService = class {
880
+ projectPath;
881
+ jai1Path;
882
+ constructor(projectPath = process.cwd()) {
883
+ this.projectPath = projectPath;
884
+ this.jai1Path = path.join(projectPath, ".jai1");
885
+ }
886
+ /**
887
+ * Scan .jai1/ directory for content
888
+ */
889
+ async scanJai1Content() {
890
+ const manualRules = await this.scanContentType("rules");
891
+ const presetRules = await this.scanRulePreset();
892
+ const rules = [...presetRules, ...manualRules];
893
+ const workflows = await this.scanContentType("workflows");
894
+ const commands = [];
895
+ return {
896
+ rules,
897
+ workflows,
898
+ commands,
899
+ totalCount: rules.length + workflows.length + commands.length
900
+ };
901
+ }
902
+ /**
903
+ * Scan .jai1/rule-preset/ directory for rule preset files
904
+ */
905
+ async scanRulePreset() {
906
+ const items = [];
907
+ const presetDir = path.join(this.jai1Path, "rule-preset");
908
+ try {
909
+ await fs4.access(presetDir);
910
+ } catch {
911
+ return items;
994
912
  }
995
- });
996
- const handleApply = async () => {
997
- setViewState("installing");
998
- const targetDir = process.cwd() + "/.jai1";
999
- const pathsToInstall = Array.from(selectedPaths);
1000
- const resolvedPaths = await service.resolveWithDependencies(config, pathsToInstall);
1001
- const installed = await service.getInstalled();
1002
- const initialProgress = resolvedPaths.map((fp) => ({
1003
- filepath: fp,
1004
- status: "pending"
1005
- }));
1006
- setInstallProgress(initialProgress);
1007
- setInstallStats({ total: resolvedPaths.length, completed: 0, added: 0, updated: 0, failed: 0 });
1008
- let added = 0, updated = 0, failed = 0;
1009
- for (let i = 0; i < resolvedPaths.length; i++) {
1010
- const fp = resolvedPaths[i];
1011
- setInstallProgress((prev) => prev.map(
1012
- (p) => p.filepath === fp ? { ...p, status: "downloading" } : p
1013
- ));
913
+ const files = await fs4.readdir(presetDir);
914
+ for (const file of files) {
915
+ if (!file.endsWith(".mdc")) continue;
916
+ const filepath = path.join(presetDir, file);
917
+ const stat = await fs4.stat(filepath);
918
+ if (!stat.isFile()) continue;
919
+ const content = await fs4.readFile(filepath, "utf-8");
920
+ let frontmatter = {};
1014
921
  try {
1015
- const info = installed[fp];
1016
- if (info && !force && info.modified) {
1017
- setInstallProgress((prev) => prev.map(
1018
- (p) => p.filepath === fp ? { ...p, status: "error", error: "Modified locally" } : p
1019
- ));
1020
- failed++;
1021
- } else {
1022
- await service.install(config, fp, targetDir);
1023
- setInstallProgress((prev) => prev.map(
1024
- (p) => p.filepath === fp ? { ...p, status: "success" } : p
1025
- ));
1026
- if (info) updated++;
1027
- else added++;
1028
- }
1029
- } catch (err) {
1030
- setInstallProgress((prev) => prev.map(
1031
- (p) => p.filepath === fp ? { ...p, status: "error", error: err instanceof Error ? err.message : "Error" } : p
1032
- ));
1033
- failed++;
922
+ const { data } = matter(content);
923
+ frontmatter = data;
924
+ } catch {
1034
925
  }
1035
- setInstallStats({ total: resolvedPaths.length, completed: i + 1, added, updated, failed });
926
+ items.push({
927
+ type: "rules",
928
+ name: path.basename(file, ".mdc"),
929
+ filepath,
930
+ relativePath: path.relative(this.projectPath, filepath),
931
+ description: frontmatter.description,
932
+ globs: this.extractGlobs(frontmatter),
933
+ alwaysApply: this.extractAlwaysApply(frontmatter)
934
+ });
1036
935
  }
1037
- setViewState("summary");
1038
- };
1039
- if (loading) {
1040
- return /* @__PURE__ */ React3.createElement(Box2, { padding: 1 }, /* @__PURE__ */ React3.createElement(Text3, { color: "cyan" }, /* @__PURE__ */ React3.createElement(Spinner, { type: "dots" }), " \u0110ang t\u1EA3i components..."));
1041
- }
1042
- if (error) {
1043
- return /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React3.createElement(Text3, { color: "red" }, "\u274C Error: ", error), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "Press q to exit"));
1044
- }
1045
- if (viewState === "installing") {
1046
- return /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React3.createElement(Text3, { bold: true, color: "cyan" }, "\u{1F4E6} Installing Components")), /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React3.createElement(ProgressBar, { current: installStats.completed, total: installStats.total, width: 50 })), /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", borderStyle: "round", borderColor: "gray", padding: 1, height: 10 }, installProgress.slice(-8).map((item) => /* @__PURE__ */ React3.createElement(Box2, { key: item.filepath }, /* @__PURE__ */ React3.createElement(StatusIcon, { status: item.status === "success" ? "success" : item.status === "error" ? "error" : item.status === "downloading" ? "loading" : "pending" }), /* @__PURE__ */ React3.createElement(Text3, null, " ", item.filepath), item.error && /* @__PURE__ */ React3.createElement(Text3, { color: "red", dimColor: true }, " (", item.error, ")")))), /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1 }, /* @__PURE__ */ React3.createElement(Text3, null, "\u{1F4CA} ", /* @__PURE__ */ React3.createElement(Text3, { color: "green" }, installStats.added, " added"), " \xB7 ", /* @__PURE__ */ React3.createElement(Text3, { color: "cyan" }, installStats.updated, " updated"), " \xB7 ", /* @__PURE__ */ React3.createElement(Text3, { color: "red" }, installStats.failed, " failed"))));
1047
- }
1048
- if (viewState === "summary") {
1049
- return /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React3.createElement(Text3, { bold: true, color: "green" }, "\u2705 Installation Complete!")), /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", borderStyle: "round", borderColor: "gray", padding: 1 }, /* @__PURE__ */ React3.createElement(Text3, null, installStats.total, " components processed:"), /* @__PURE__ */ React3.createElement(Text3, null, " \u2514\u2500 ", /* @__PURE__ */ React3.createElement(Text3, { color: "green" }, installStats.added), " newly added"), /* @__PURE__ */ React3.createElement(Text3, null, " \u2514\u2500 ", /* @__PURE__ */ React3.createElement(Text3, { color: "cyan" }, installStats.updated), " updated"), installStats.failed > 0 && /* @__PURE__ */ React3.createElement(Text3, null, " \u2514\u2500 ", /* @__PURE__ */ React3.createElement(Text3, { color: "red" }, installStats.failed), " failed")), /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1 }, /* @__PURE__ */ React3.createElement(Text3, null, "\u{1F4C1} Location: ", /* @__PURE__ */ React3.createElement(Text3, { color: "cyan" }, process.cwd(), "/.jai1"))), /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "Press Enter or q to exit")));
936
+ return items;
1050
937
  }
1051
- const visibleComponents = filteredComponents.slice(0, 12);
1052
- const hasMore = filteredComponents.length > 12;
1053
- return /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React3.createElement(Text3, { bold: true, color: "cyan" }, "\u{1F4E6} Jai1 Apply"), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, " - Select components to install")), /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React3.createElement(Text3, { color: focusArea === "search" ? "cyan" : "gray" }, "\u{1F50D} Search: "), focusArea === "search" ? /* @__PURE__ */ React3.createElement(
1054
- TextInput,
1055
- {
1056
- value: searchQuery,
1057
- onChange: setSearchQuery,
1058
- placeholder: "Type to filter..."
938
+ /**
939
+ * Scan a specific content type
940
+ */
941
+ async scanContentType(type) {
942
+ const items = [];
943
+ const dirPath = path.join(this.jai1Path, type);
944
+ try {
945
+ await fs4.access(dirPath);
946
+ } catch {
947
+ return items;
1059
948
  }
1060
- ) : /* @__PURE__ */ React3.createElement(Text3, null, searchQuery || /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "Press Tab to search"))), /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1, flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true, dimColor: true }, "Quick Apply (Packages):"), /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1, flexWrap: "wrap" }, tags.slice(0, 6).map((tag, i) => {
1061
- const isSelected = focusArea === "packages" && i === selectedPackageIndex;
1062
- return /* @__PURE__ */ React3.createElement(Box2, { key: tag.tag, marginRight: 1 }, /* @__PURE__ */ React3.createElement(
1063
- Text3,
1064
- {
1065
- backgroundColor: isSelected ? "cyan" : void 0,
1066
- color: isSelected ? "black" : "gray"
1067
- },
1068
- "[",
1069
- tag.tag,
1070
- ":",
1071
- tag.count,
1072
- "]"
1073
- ));
1074
- }))), /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", borderStyle: "round", borderColor: focusArea === "components" ? "cyan" : "gray", padding: 1 }, /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Components "), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "(", filteredComponents.length, " shown", hasMore ? `, scroll for more` : "", ")")), visibleComponents.map((comp, i) => {
1075
- const isCursor = i === cursorIndex && focusArea === "components";
1076
- const isChecked = selectedPaths.has(comp.filepath);
1077
- const isInstalled = installedPaths.has(comp.filepath);
1078
- return /* @__PURE__ */ React3.createElement(Box2, { key: comp.filepath }, /* @__PURE__ */ React3.createElement(Text3, { color: isCursor ? "cyan" : "white" }, isCursor ? "\u276F " : " ", isChecked ? "[\u2713]" : "[ ]", " ", comp.filepath), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, " ", isInstalled ? "\u2713 installed" : "\u25CB new"));
1079
- }), filteredComponents.length === 0 && /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "No components match your search")), selectedPaths.size > 0 && /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Selected: ", selectedPaths.size, " components"), /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", marginLeft: 2 }, Array.from(selectedPaths).slice(0, 4).map((fp) => /* @__PURE__ */ React3.createElement(Text3, { key: fp, dimColor: true }, "\u{1F4CC} ", fp)), selectedPaths.size > 4 && /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, " ... and ", selectedPaths.size - 4, " more"))), /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "[Tab] Switch area \xB7 [\u2191\u2193/\u2190\u2192] Navigate \xB7 [\u2423] Toggle \xB7 [A] Select all \xB7 [C] Clear \xB7 [Enter] Apply \xB7 [Q] Quit")));
1080
- };
1081
-
1082
- // src/commands/apply.ts
1083
- function createApplyCommand() {
1084
- const cmd = new Command3("apply").description("Apply components to your project (interactive UI)").argument("[items...]", "Package name or component filepaths (non-interactive mode)").option("--force", "Force overwrite even if modified locally").option("--no-interactive", "Disable interactive UI (for CI/CD)").action(async (items, options) => {
1085
- const configService = new ConfigService();
1086
- const config = await configService.load();
1087
- if (!config) {
1088
- throw new ValidationError('Not initialized. Run "jai1 auth" first.');
949
+ const files = await fs4.readdir(dirPath);
950
+ for (const file of files) {
951
+ if (!file.endsWith(".md")) continue;
952
+ const filepath = path.join(dirPath, file);
953
+ const stat = await fs4.stat(filepath);
954
+ if (!stat.isFile()) continue;
955
+ const content = await fs4.readFile(filepath, "utf-8");
956
+ const { data: frontmatter } = matter(content);
957
+ items.push({
958
+ type,
959
+ name: path.basename(file, ".md"),
960
+ filepath,
961
+ relativePath: path.relative(this.projectPath, filepath),
962
+ description: frontmatter.description,
963
+ globs: this.extractGlobs(frontmatter),
964
+ alwaysApply: this.extractAlwaysApply(frontmatter)
965
+ });
1089
966
  }
1090
- if (options.interactive === false || items.length > 0) {
1091
- await nonInteractiveApply(config, items, options);
1092
- return;
967
+ return items;
968
+ }
969
+ /**
970
+ * Generate stub file content with @ reference
971
+ */
972
+ generateStubContent(ide, sourceItem) {
973
+ const config = IDE_MIGRATION_CONFIGS[ide];
974
+ if (!config) throw new Error(`Unknown IDE: ${ide}`);
975
+ const frontmatter = config.generateFrontmatter({
976
+ description: sourceItem.description,
977
+ globs: sourceItem.globs,
978
+ alwaysApply: sourceItem.alwaysApply,
979
+ sourceFile: sourceItem.relativePath
980
+ });
981
+ const reference = `@${sourceItem.relativePath}`;
982
+ if (frontmatter) {
983
+ return `${frontmatter}
984
+
985
+ ${reference}
986
+ `;
1093
987
  }
1094
- const { waitUntilExit } = render(
1095
- React4.createElement(UnifiedApplyApp, {
1096
- config,
1097
- force: options.force,
1098
- onExit: () => {
1099
- process.exit(0);
988
+ return `${reference}
989
+ `;
990
+ }
991
+ /**
992
+ * Migrate content to specific IDEs
993
+ */
994
+ async migrate(ides, contentTypes, content, onProgress) {
995
+ const results = [];
996
+ for (const ide of ides) {
997
+ const config = IDE_MIGRATION_CONFIGS[ide];
998
+ if (!config) continue;
999
+ for (const type of contentTypes) {
1000
+ const items = type === "rules" ? content.rules : type === "workflows" ? content.workflows : content.commands;
1001
+ for (const item of items) {
1002
+ const result = await this.migrateItem(ide, config, item);
1003
+ results.push(result);
1004
+ onProgress?.(result);
1100
1005
  }
1101
- })
1102
- );
1103
- await waitUntilExit();
1104
- });
1105
- return cmd;
1106
- }
1107
- async function nonInteractiveApply(config, items, options) {
1108
- const componentsService = new ComponentsService();
1109
- if (items.length === 0) {
1110
- console.log("\u274C No items specified. Use interactive mode or provide package/component names.");
1111
- console.log("\u{1F4A1} Usage: jai1 apply <package> or jai1 apply <filepath...>");
1112
- return;
1006
+ }
1007
+ }
1008
+ return results;
1113
1009
  }
1114
- const tags = await componentsService.listTags(config);
1115
- const packageNames = tags.map((t) => t.tag);
1116
- let filepaths;
1117
- if (items.length === 1 && packageNames.includes(items[0])) {
1118
- console.log(`\u{1F4E6} Applying package '${items[0]}'...`);
1119
- const components = await componentsService.list(config, { tag: items[0] });
1120
- filepaths = components.map((c) => c.filepath);
1121
- } else {
1122
- filepaths = items;
1123
- }
1124
- console.log("\u{1F50D} Resolving dependencies...");
1125
- const resolvedPaths = await componentsService.resolveWithDependencies(config, filepaths);
1126
- console.log(`\u{1F4E6} Will apply ${resolvedPaths.length} component(s):`);
1127
- for (const fp of resolvedPaths) {
1128
- const isDep = !filepaths.includes(fp);
1129
- const suffix = isDep ? " (dependency)" : "";
1130
- console.log(` - ${fp}${suffix}`);
1131
- }
1132
- const targetDir = process.cwd() + "/.jai1";
1133
- const installed = await componentsService.getInstalled();
1134
- let added = 0;
1135
- let updated = 0;
1136
- let skipped = 0;
1137
- for (const fp of resolvedPaths) {
1010
+ /**
1011
+ * Migrate a single item
1012
+ */
1013
+ async migrateItem(ide, config, item) {
1138
1014
  try {
1139
- const info = installed[fp];
1140
- if (info && !options.force && info.modified) {
1141
- console.log(`\u26A0\uFE0F ${fp}: modified locally. Use --force to overwrite.`);
1142
- skipped++;
1143
- continue;
1015
+ const targetPath = this.getTargetPath(config, item);
1016
+ if (!targetPath) {
1017
+ return {
1018
+ source: item,
1019
+ targetIDE: ide,
1020
+ targetPath: "",
1021
+ status: "skipped",
1022
+ error: `IDE ${config.id} does not support ${item.type}`
1023
+ };
1144
1024
  }
1145
- console.log(`\u{1F4E5} Downloading ${fp}...`);
1146
- await componentsService.install(config, fp, targetDir);
1147
- if (info) updated++;
1148
- else added++;
1149
- console.log(`\u2705 ${fp}`);
1025
+ const targetDir = path.dirname(targetPath);
1026
+ await fs4.mkdir(targetDir, { recursive: true });
1027
+ let status = "created";
1028
+ try {
1029
+ await fs4.access(targetPath);
1030
+ status = "updated";
1031
+ } catch {
1032
+ }
1033
+ const stubContent = this.generateStubContent(ide, item);
1034
+ await fs4.writeFile(targetPath, stubContent, "utf-8");
1035
+ return {
1036
+ source: item,
1037
+ targetIDE: ide,
1038
+ targetPath,
1039
+ status
1040
+ };
1150
1041
  } catch (error) {
1151
- console.log(`\u274C ${fp}: ${error instanceof Error ? error.message : "Error"}`);
1042
+ return {
1043
+ source: item,
1044
+ targetIDE: ide,
1045
+ targetPath: "",
1046
+ status: "error",
1047
+ error: error instanceof Error ? error.message : "Unknown error"
1048
+ };
1152
1049
  }
1153
1050
  }
1154
- console.log(`
1155
- \u2705 Complete: ${added} added, ${updated} updated, ${skipped} skipped`);
1156
- console.log(`\u{1F4C1} Location: ${targetDir}`);
1157
- }
1158
-
1159
- // src/commands/update.ts
1160
- import { Command as Command4 } from "commander";
1161
- import { confirm } from "@inquirer/prompts";
1162
- function createUpdateCommand() {
1163
- return new Command4("update").description("Update installed components to latest versions").option("--force", "Force update even if files are modified locally").action(async (options) => {
1164
- await handleUpdate(options);
1165
- });
1166
- }
1167
- async function handleUpdate(options) {
1168
- const configService = new ConfigService();
1169
- const config = await configService.load();
1170
- if (!config) {
1171
- throw new ValidationError('Not initialized. Run "jai1 auth" first.');
1172
- }
1173
- const componentsService = new ComponentsService();
1174
- const installed = await componentsService.getInstalled();
1175
- const filepaths = Object.keys(installed);
1176
- if (filepaths.length === 0) {
1177
- console.log('No components installed. Run "jai1 apply" to add components.');
1178
- return;
1179
- }
1180
- console.log("\u{1F50D} Checking for updates...");
1181
- try {
1182
- const checksums = await componentsService.getChecksums(config, filepaths);
1183
- const updates = [];
1184
- const upToDate = [];
1185
- for (const fp of filepaths) {
1186
- const current = installed[fp];
1187
- const latest = checksums[fp];
1188
- if (!latest) {
1189
- console.log(`\u26A0\uFE0F ${fp}: Component not found on server (deprecated?)`);
1190
- continue;
1191
- }
1192
- if (current.checksum !== latest.checksum) {
1193
- updates.push(fp);
1194
- } else {
1195
- upToDate.push(fp);
1196
- }
1197
- }
1198
- if (updates.length === 0) {
1199
- console.log("\u2705 All components are up to date.");
1200
- return;
1201
- }
1202
- console.log(`
1203
- \u{1F4E6} Found ${updates.length} updates:`);
1204
- for (const fp of updates) {
1205
- const current = installed[fp];
1206
- const latest = checksums[fp];
1207
- console.log(` - ${fp} (v${current.version} \u2192 v${latest.version})`);
1208
- }
1209
- console.log();
1210
- if (!options.force) {
1211
- const shouldUpdate = await confirm({ message: "Update now?", default: true });
1212
- if (!shouldUpdate) return;
1213
- }
1214
- const targetDir = process.cwd() + "/.jai1";
1215
- let updatedCount = 0;
1216
- const backupPaths = [];
1217
- for (const fp of updates) {
1218
- try {
1219
- console.log(`\u{1F4E5} Updating ${fp}...`);
1220
- const backupPath = await componentsService.backupFile(fp, targetDir);
1221
- if (backupPath) {
1222
- backupPaths.push(backupPath);
1223
- const displayPath = backupPath.split(".jai1_backup/")[1];
1224
- console.log(` Backed up: .../${displayPath}`);
1225
- }
1226
- await componentsService.install(config, fp, targetDir);
1227
- console.log(`\u2705 Updated`);
1228
- updatedCount++;
1229
- } catch (error) {
1230
- console.log(`\u274C Failed to update ${fp}: ${error instanceof Error ? error.message : "Unknown error"}`);
1231
- }
1051
+ /**
1052
+ * Get target file path for an item
1053
+ */
1054
+ getTargetPath(config, item) {
1055
+ let contentPath = null;
1056
+ switch (item.type) {
1057
+ case "rules":
1058
+ contentPath = config.rulesPath;
1059
+ break;
1060
+ case "workflows":
1061
+ contentPath = config.workflowsPath ?? config.commandsPath;
1062
+ break;
1063
+ case "commands":
1064
+ contentPath = config.commandsPath;
1065
+ break;
1232
1066
  }
1233
- console.log(`
1234
- \u2705 Update complete: ${updatedCount}/${updates.length} updated.`);
1235
- if (backupPaths.length > 0) {
1236
- console.log(`
1237
- \u{1F4C1} Backups created in .jai1_backup/`);
1238
- const cleanBackups = await confirm({ message: "Clear these backups?", default: false });
1239
- if (cleanBackups) {
1240
- await componentsService.clearBackups(process.cwd());
1241
- console.log("\u{1F5D1}\uFE0F Backups cleared.");
1242
- } else {
1243
- console.log('\u{1F4A1} Run "jai1 clean" to remove them later.');
1244
- }
1067
+ if (!contentPath) {
1068
+ return null;
1245
1069
  }
1246
- trackAction("components_update", { count: updatedCount });
1247
- } catch (error) {
1248
- throw new Error(`Update check failed: ${error instanceof Error ? error.message : "Unknown error"}`);
1249
- }
1250
- }
1251
-
1252
- // src/commands/framework/check.ts
1253
- import { Command as Command5 } from "commander";
1254
- function createCheckCommand() {
1255
- const cmd = new Command5("check").description("Check for framework updates").option("--json", "Output as JSON").action(async (options) => {
1256
- await handleCheck(options);
1257
- });
1258
- return cmd;
1259
- }
1260
- async function handleCheck(options) {
1261
- const configService = new ConfigService();
1262
- const config = await configService.load();
1263
- if (!config) {
1264
- throw new ValidationError('Not initialized. Run "jai1 auth" first.');
1070
+ const filename = `${item.name}${config.fileExtension}`;
1071
+ return path.join(this.projectPath, config.basePath, contentPath, filename);
1265
1072
  }
1266
- const componentsService = new ComponentsService();
1267
- const installed = await componentsService.getInstalled();
1268
- const filepaths = Object.keys(installed);
1269
- if (filepaths.length === 0) {
1270
- console.log('No components installed. Run "jai1 apply" to get started.');
1271
- return;
1073
+ // Helper methods
1074
+ extractGlobs(frontmatter) {
1075
+ const globs = frontmatter.globs;
1076
+ if (typeof globs === "string") return [globs];
1077
+ if (Array.isArray(globs)) return globs;
1078
+ return void 0;
1272
1079
  }
1273
- console.log("Checking for updates...\n");
1274
- try {
1275
- const checksums = await componentsService.getChecksums(config, filepaths);
1276
- const updates = [];
1277
- const upToDate = [];
1278
- for (const fp of filepaths) {
1279
- const current = installed[fp];
1280
- const latest = checksums[fp];
1281
- if (!latest) {
1282
- continue;
1283
- }
1284
- if (current.checksum !== latest.checksum) {
1285
- updates.push(fp);
1286
- } else {
1287
- upToDate.push(fp);
1288
- }
1289
- }
1290
- console.log(`\u{1F4CC} Components status:`);
1291
- for (const fp of updates) {
1292
- const current = installed[fp];
1293
- const latest = checksums[fp];
1294
- console.log(` ${fp} v${current.version} \u2192 v${latest.version} \u26A0\uFE0F UPDATE`);
1295
- }
1296
- if (updates.length === 0 && upToDate.length > 0) {
1297
- console.log(` \u2713 All ${upToDate.length} components up to date.`);
1298
- } else if (upToDate.length > 0) {
1299
- console.log(` \u2713 ${upToDate.length} components up to date.`);
1300
- }
1301
- console.log();
1302
- if (updates.length > 0) {
1303
- console.log(`Status: ${updates.length} component(s) update available!`);
1304
- console.log(`Run 'jai1 update' to update.`);
1305
- } else {
1306
- console.log(`Status: \u2705 All good!`);
1307
- }
1308
- } catch (error) {
1309
- console.error("Error checking updates:", error instanceof Error ? error.message : error);
1080
+ extractAlwaysApply(frontmatter) {
1081
+ return frontmatter.alwaysApply === true || frontmatter.trigger === "always" || frontmatter.trigger === "always_on";
1310
1082
  }
1311
- }
1312
-
1313
- // src/commands/ide/index.ts
1314
- import { Command as Command10 } from "commander";
1315
-
1316
- // src/commands/ide/context.ts
1317
- import React10 from "react";
1318
- import { render as render2 } from "ink";
1319
- import { Command as Command6 } from "commander";
1083
+ };
1320
1084
 
1321
- // src/ui/context/ContextApp.tsx
1085
+ // src/ui/shared/ProgressBar.tsx
1086
+ import React from "react";
1087
+ import { Box, Text } from "ink";
1088
+ var ProgressBar = ({
1089
+ current,
1090
+ total,
1091
+ width = 40,
1092
+ showPercentage = true,
1093
+ color = "cyan"
1094
+ }) => {
1095
+ const percentage = total > 0 ? Math.round(current / total * 100) : 0;
1096
+ const filled = Math.round(percentage / 100 * width);
1097
+ const empty = width - filled;
1098
+ const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
1099
+ return /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color }, bar), showPercentage && /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " ", current, "/", total, " (", percentage, "%)"));
1100
+ };
1101
+
1102
+ // src/ui/shared/StatusIcon.tsx
1103
+ import React2 from "react";
1104
+ import { Text as Text2 } from "ink";
1105
+
1106
+ // src/ui/shared/theme.ts
1107
+ var theme = {
1108
+ colors: {
1109
+ primary: "#6366f1",
1110
+ // Indigo
1111
+ success: "#22c55e",
1112
+ // Green
1113
+ warning: "#eab308",
1114
+ // Yellow
1115
+ error: "#ef4444",
1116
+ // Red
1117
+ muted: "#6b7280",
1118
+ // Gray
1119
+ border: "#374151",
1120
+ // Dark gray
1121
+ text: "#f3f4f6",
1122
+ // Light gray
1123
+ background: "#1a1a2e"
1124
+ // Dark background
1125
+ },
1126
+ icons: {
1127
+ success: "\u2705",
1128
+ error: "\u274C",
1129
+ pending: "\u25CB",
1130
+ loading: "\u23F3",
1131
+ package: "\u{1F4E6}",
1132
+ folder: "\u{1F4C1}",
1133
+ file: "\u{1F4C4}",
1134
+ tip: "\u{1F4A1}",
1135
+ warning: "\u26A0\uFE0F",
1136
+ checkmark: "\u2713",
1137
+ arrow: "\u2192",
1138
+ bullet: "\u2022"
1139
+ },
1140
+ borders: {
1141
+ single: {
1142
+ topLeft: "\u250C",
1143
+ topRight: "\u2510",
1144
+ bottomLeft: "\u2514",
1145
+ bottomRight: "\u2518",
1146
+ horizontal: "\u2500",
1147
+ vertical: "\u2502"
1148
+ },
1149
+ double: {
1150
+ topLeft: "\u2554",
1151
+ topRight: "\u2557",
1152
+ bottomLeft: "\u255A",
1153
+ bottomRight: "\u255D",
1154
+ horizontal: "\u2550",
1155
+ vertical: "\u2551"
1156
+ }
1157
+ }
1158
+ };
1159
+
1160
+ // src/ui/shared/StatusIcon.tsx
1161
+ var StatusIcon = ({ status, text }) => {
1162
+ const iconMap = {
1163
+ success: theme.icons.success,
1164
+ error: theme.icons.error,
1165
+ pending: theme.icons.pending,
1166
+ loading: theme.icons.loading,
1167
+ warning: theme.icons.warning
1168
+ };
1169
+ const colorMap = {
1170
+ success: "green",
1171
+ error: "red",
1172
+ pending: "gray",
1173
+ loading: "yellow",
1174
+ warning: "yellow"
1175
+ };
1176
+ const icon = iconMap[status];
1177
+ const color = colorMap[status];
1178
+ return /* @__PURE__ */ React2.createElement(Text2, { color }, icon, " ", text);
1179
+ };
1180
+
1181
+ // src/ui/apply/UnifiedApplyApp.tsx
1182
+ var UnifiedApplyApp = ({
1183
+ config,
1184
+ force = false,
1185
+ onExit
1186
+ }) => {
1187
+ const { exit } = useApp();
1188
+ const [viewState, setViewState] = useState("browse");
1189
+ const [components, setComponents] = useState([]);
1190
+ const [tags, setTags] = useState([]);
1191
+ const [installedPaths, setInstalledPaths] = useState(/* @__PURE__ */ new Set());
1192
+ const [loading, setLoading] = useState(true);
1193
+ const [error, setError] = useState(null);
1194
+ const [searchQuery, setSearchQuery] = useState("");
1195
+ const [selectedPaths, setSelectedPaths] = useState(/* @__PURE__ */ new Set());
1196
+ const [cursorIndex, setCursorIndex] = useState(0);
1197
+ const [focusArea, setFocusArea] = useState("packages");
1198
+ const [selectedPackageIndex, setSelectedPackageIndex] = useState(0);
1199
+ const [installProgress, setInstallProgress] = useState([]);
1200
+ const [installStats, setInstallStats] = useState({ total: 0, completed: 0, added: 0, updated: 0, failed: 0 });
1201
+ const [availableIdes, setAvailableIdes] = useState([]);
1202
+ const [selectedIdes, setSelectedIdes] = useState(/* @__PURE__ */ new Set());
1203
+ const [ideCursorIndex, setIdeCursorIndex] = useState(0);
1204
+ const [syncProgress, setSyncProgress] = useState([]);
1205
+ const [syncStats, setSyncStats] = useState({ total: 0, completed: 0, created: 0, updated: 0, skipped: 0, errors: 0 });
1206
+ const service = new ComponentsService();
1207
+ useEffect(() => {
1208
+ const loadData = async () => {
1209
+ try {
1210
+ const [comps, tagList, installed] = await Promise.all([
1211
+ service.list(config),
1212
+ service.listTags(config),
1213
+ service.getInstalled()
1214
+ ]);
1215
+ setComponents(comps);
1216
+ setTags(tagList);
1217
+ setInstalledPaths(new Set(Object.keys(installed)));
1218
+ setLoading(false);
1219
+ } catch (err) {
1220
+ setError(err instanceof Error ? err.message : "Failed to load");
1221
+ setLoading(false);
1222
+ }
1223
+ };
1224
+ loadData();
1225
+ setAvailableIdes(getMigrationIDEs());
1226
+ }, []);
1227
+ const filteredComponents = useMemo(() => {
1228
+ if (!searchQuery.trim()) return components;
1229
+ const query = searchQuery.toLowerCase();
1230
+ return components.filter(
1231
+ (c) => c.filepath.toLowerCase().includes(query) || c.tags?.some((t) => t.toLowerCase().includes(query))
1232
+ );
1233
+ }, [components, searchQuery]);
1234
+ useInput((input4, key) => {
1235
+ if (viewState === "done") {
1236
+ if (key.return || input4 === "q" || key.escape) {
1237
+ onExit();
1238
+ }
1239
+ return;
1240
+ }
1241
+ if (viewState === "summary") {
1242
+ if (key.return) {
1243
+ setViewState("ide-sync");
1244
+ } else if (input4 === "s" || input4 === "q" || key.escape) {
1245
+ onExit();
1246
+ }
1247
+ return;
1248
+ }
1249
+ if (viewState === "ide-sync") {
1250
+ if (key.upArrow) {
1251
+ setIdeCursorIndex((prev) => Math.max(0, prev - 1));
1252
+ } else if (key.downArrow) {
1253
+ setIdeCursorIndex((prev) => Math.min(availableIdes.length - 1, prev + 1));
1254
+ } else if (input4 === " ") {
1255
+ const ide = availableIdes[ideCursorIndex];
1256
+ if (ide) {
1257
+ setSelectedIdes((prev) => {
1258
+ const next = new Set(prev);
1259
+ if (next.has(ide)) {
1260
+ next.delete(ide);
1261
+ } else {
1262
+ next.add(ide);
1263
+ }
1264
+ return next;
1265
+ });
1266
+ }
1267
+ } else if (input4 === "a") {
1268
+ setSelectedIdes(new Set(availableIdes));
1269
+ } else if (input4 === "c") {
1270
+ setSelectedIdes(/* @__PURE__ */ new Set());
1271
+ } else if (key.return) {
1272
+ if (selectedIdes.size > 0) {
1273
+ handleIdeSync();
1274
+ }
1275
+ } else if (input4 === "s" || input4 === "q" || key.escape) {
1276
+ onExit();
1277
+ }
1278
+ return;
1279
+ }
1280
+ if (viewState === "syncing") {
1281
+ return;
1282
+ }
1283
+ if (viewState === "installing") {
1284
+ return;
1285
+ }
1286
+ if (key.tab) {
1287
+ if (focusArea === "packages") setFocusArea("components");
1288
+ else if (focusArea === "components") setFocusArea("search");
1289
+ else setFocusArea("packages");
1290
+ return;
1291
+ }
1292
+ if (key.escape || input4 === "q") {
1293
+ onExit();
1294
+ return;
1295
+ }
1296
+ if (key.return && focusArea !== "search") {
1297
+ if (selectedPaths.size > 0) {
1298
+ handleApply();
1299
+ }
1300
+ return;
1301
+ }
1302
+ if (focusArea !== "search") {
1303
+ if (input4 === "a") {
1304
+ setSelectedPaths((prev) => {
1305
+ const next = new Set(prev);
1306
+ filteredComponents.forEach((c) => next.add(c.filepath));
1307
+ return next;
1308
+ });
1309
+ return;
1310
+ } else if (input4 === "c") {
1311
+ setSelectedPaths(/* @__PURE__ */ new Set());
1312
+ return;
1313
+ }
1314
+ }
1315
+ if (focusArea === "components") {
1316
+ if (key.upArrow) {
1317
+ setCursorIndex((prev) => Math.max(0, prev - 1));
1318
+ } else if (key.downArrow) {
1319
+ setCursorIndex((prev) => Math.min(filteredComponents.length - 1, prev + 1));
1320
+ } else if (input4 === " ") {
1321
+ const current = filteredComponents[cursorIndex];
1322
+ if (current) {
1323
+ setSelectedPaths((prev) => {
1324
+ const next = new Set(prev);
1325
+ if (next.has(current.filepath)) {
1326
+ next.delete(current.filepath);
1327
+ } else {
1328
+ next.add(current.filepath);
1329
+ }
1330
+ return next;
1331
+ });
1332
+ }
1333
+ }
1334
+ }
1335
+ if (focusArea === "packages") {
1336
+ if (key.leftArrow) {
1337
+ setSelectedPackageIndex((prev) => Math.max(0, prev - 1));
1338
+ } else if (key.rightArrow) {
1339
+ setSelectedPackageIndex((prev) => Math.min(tags.length - 1, prev + 1));
1340
+ } else if (input4 === " " || key.return) {
1341
+ const tag = tags[selectedPackageIndex];
1342
+ if (tag) {
1343
+ const packageComponents = components.filter((c) => c.tags?.includes(tag.tag));
1344
+ setSelectedPaths((prev) => {
1345
+ const next = new Set(prev);
1346
+ packageComponents.forEach((c) => next.add(c.filepath));
1347
+ return next;
1348
+ });
1349
+ setSearchQuery(tag.tag);
1350
+ }
1351
+ }
1352
+ }
1353
+ });
1354
+ const handleApply = async () => {
1355
+ setViewState("installing");
1356
+ const targetDir = process.cwd() + "/.jai1";
1357
+ const pathsToInstall = Array.from(selectedPaths);
1358
+ const resolvedPaths = await service.resolveWithDependencies(config, pathsToInstall);
1359
+ const installed = await service.getInstalled();
1360
+ const initialProgress = resolvedPaths.map((fp) => ({
1361
+ filepath: fp,
1362
+ status: "pending"
1363
+ }));
1364
+ setInstallProgress(initialProgress);
1365
+ setInstallStats({ total: resolvedPaths.length, completed: 0, added: 0, updated: 0, failed: 0 });
1366
+ let added = 0, updated = 0, failed = 0;
1367
+ for (let i = 0; i < resolvedPaths.length; i++) {
1368
+ const fp = resolvedPaths[i];
1369
+ setInstallProgress((prev) => prev.map(
1370
+ (p) => p.filepath === fp ? { ...p, status: "downloading" } : p
1371
+ ));
1372
+ try {
1373
+ const info = installed[fp];
1374
+ if (info && !force && info.modified) {
1375
+ setInstallProgress((prev) => prev.map(
1376
+ (p) => p.filepath === fp ? { ...p, status: "error", error: "Modified locally" } : p
1377
+ ));
1378
+ failed++;
1379
+ } else {
1380
+ await service.install(config, fp, targetDir);
1381
+ setInstallProgress((prev) => prev.map(
1382
+ (p) => p.filepath === fp ? { ...p, status: "success" } : p
1383
+ ));
1384
+ if (info) updated++;
1385
+ else added++;
1386
+ }
1387
+ } catch (err) {
1388
+ setInstallProgress((prev) => prev.map(
1389
+ (p) => p.filepath === fp ? { ...p, status: "error", error: err instanceof Error ? err.message : "Error" } : p
1390
+ ));
1391
+ failed++;
1392
+ }
1393
+ setInstallStats({ total: resolvedPaths.length, completed: i + 1, added, updated, failed });
1394
+ }
1395
+ setViewState("summary");
1396
+ };
1397
+ const handleIdeSync = async () => {
1398
+ setViewState("syncing");
1399
+ const migrateService = new MigrateIdeService();
1400
+ const content = await migrateService.scanJai1Content();
1401
+ if (content.totalCount === 0) {
1402
+ setSyncStats({ total: 0, completed: 0, created: 0, updated: 0, skipped: 0, errors: 0 });
1403
+ setViewState("done");
1404
+ return;
1405
+ }
1406
+ const selectedIdeList = Array.from(selectedIdes);
1407
+ const contentTypes = ["rules", "workflows"];
1408
+ const totalItems = contentTypes.reduce((sum, type) => {
1409
+ return sum + (type === "rules" ? content.rules.length : type === "workflows" ? content.workflows.length : content.commands.length);
1410
+ }, 0);
1411
+ const totalFiles = totalItems * selectedIdeList.length;
1412
+ setSyncStats({ total: totalFiles, completed: 0, created: 0, updated: 0, skipped: 0, errors: 0 });
1413
+ let created = 0, updated = 0, skipped = 0, errors = 0;
1414
+ let completed = 0;
1415
+ const results = await migrateService.migrate(
1416
+ selectedIdeList,
1417
+ contentTypes,
1418
+ content,
1419
+ (result) => {
1420
+ completed++;
1421
+ if (result.status === "created") created++;
1422
+ else if (result.status === "updated") updated++;
1423
+ else if (result.status === "skipped") skipped++;
1424
+ else if (result.status === "error") errors++;
1425
+ setSyncProgress((prev) => [...prev.slice(-7), {
1426
+ targetPath: result.targetPath || result.source.relativePath,
1427
+ status: result.status === "error" ? "error" : "success",
1428
+ error: result.error
1429
+ }]);
1430
+ setSyncStats({ total: totalFiles, completed, created, updated, skipped, errors });
1431
+ }
1432
+ );
1433
+ setViewState("done");
1434
+ };
1435
+ if (loading) {
1436
+ return /* @__PURE__ */ React3.createElement(Box2, { padding: 1 }, /* @__PURE__ */ React3.createElement(Text3, { color: "cyan" }, /* @__PURE__ */ React3.createElement(Spinner, { type: "dots" }), " \u0110ang t\u1EA3i components..."));
1437
+ }
1438
+ if (error) {
1439
+ return /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React3.createElement(Text3, { color: "red" }, "\u274C Error: ", error), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "Press q to exit"));
1440
+ }
1441
+ if (viewState === "installing") {
1442
+ return /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React3.createElement(Text3, { bold: true, color: "cyan" }, "\u{1F4E6} Installing Components")), /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React3.createElement(ProgressBar, { current: installStats.completed, total: installStats.total, width: 50 })), /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", borderStyle: "round", borderColor: "gray", padding: 1, height: 10 }, installProgress.slice(-8).map((item) => /* @__PURE__ */ React3.createElement(Box2, { key: item.filepath }, /* @__PURE__ */ React3.createElement(StatusIcon, { status: item.status === "success" ? "success" : item.status === "error" ? "error" : item.status === "downloading" ? "loading" : "pending" }), /* @__PURE__ */ React3.createElement(Text3, null, " ", item.filepath), item.error && /* @__PURE__ */ React3.createElement(Text3, { color: "red", dimColor: true }, " (", item.error, ")")))), /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1 }, /* @__PURE__ */ React3.createElement(Text3, null, "\u{1F4CA} ", /* @__PURE__ */ React3.createElement(Text3, { color: "green" }, installStats.added, " added"), " \xB7 ", /* @__PURE__ */ React3.createElement(Text3, { color: "cyan" }, installStats.updated, " updated"), " \xB7 ", /* @__PURE__ */ React3.createElement(Text3, { color: "red" }, installStats.failed, " failed"))));
1443
+ }
1444
+ if (viewState === "summary") {
1445
+ return /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React3.createElement(Text3, { bold: true, color: "green" }, "\u2705 Installation Complete!")), /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", borderStyle: "round", borderColor: "gray", padding: 1 }, /* @__PURE__ */ React3.createElement(Text3, null, installStats.total, " components processed:"), /* @__PURE__ */ React3.createElement(Text3, null, " \u2514\u2500 ", /* @__PURE__ */ React3.createElement(Text3, { color: "green" }, installStats.added), " newly added"), /* @__PURE__ */ React3.createElement(Text3, null, " \u2514\u2500 ", /* @__PURE__ */ React3.createElement(Text3, { color: "cyan" }, installStats.updated), " updated"), installStats.failed > 0 && /* @__PURE__ */ React3.createElement(Text3, null, " \u2514\u2500 ", /* @__PURE__ */ React3.createElement(Text3, { color: "red" }, installStats.failed), " failed")), /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1 }, /* @__PURE__ */ React3.createElement(Text3, null, "\u{1F4C1} Location: ", /* @__PURE__ */ React3.createElement(Text3, { color: "cyan" }, process.cwd(), "/.jai1"))), /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true, color: "yellow" }, "\u{1F4E6} Next Step: Sync to IDE(s)?"), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "Sync .jai1/ content to your IDE directories (rules, workflows)")), /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "[Enter] Select IDE(s) to sync \xB7 [S/Q] Skip and exit")));
1446
+ }
1447
+ if (viewState === "ide-sync") {
1448
+ return /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React3.createElement(Text3, { bold: true, color: "cyan" }, "\u{1F504} Select IDE(s) to Sync")), /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "Sync .jai1/ content (rules, workflows) to IDE-specific directories")), /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", padding: 1 }, availableIdes.map((ide, i) => {
1449
+ const ideConfig = IDE_MIGRATION_CONFIGS[ide];
1450
+ const isCursor = i === ideCursorIndex;
1451
+ const isChecked = selectedIdes.has(ide);
1452
+ return /* @__PURE__ */ React3.createElement(Box2, { key: ide }, /* @__PURE__ */ React3.createElement(Text3, { color: isCursor ? "cyan" : "white" }, isCursor ? "\u276F " : " ", isChecked ? "[\u2713]" : "[ ]", " ", ideConfig.icon, " ", ideConfig.name));
1453
+ })), selectedIdes.size > 0 && /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1 }, /* @__PURE__ */ React3.createElement(Text3, null, "Selected: ", /* @__PURE__ */ React3.createElement(Text3, { color: "green" }, selectedIdes.size), " IDE(s)")), /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "[\u2191\u2193] Navigate \xB7 [\u2423] Toggle \xB7 [A] Select all \xB7 [C] Clear \xB7 [Enter] Sync \xB7 [S/Q] Skip")));
1454
+ }
1455
+ if (viewState === "syncing") {
1456
+ return /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React3.createElement(Text3, { bold: true, color: "cyan" }, "\u{1F504} Syncing to IDE(s)")), /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React3.createElement(ProgressBar, { current: syncStats.completed, total: syncStats.total, width: 50 })), /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", borderStyle: "round", borderColor: "gray", padding: 1, height: 10 }, syncProgress.slice(-8).map((item, idx) => /* @__PURE__ */ React3.createElement(Box2, { key: `${item.targetPath}-${idx}` }, /* @__PURE__ */ React3.createElement(StatusIcon, { status: item.status === "success" ? "success" : item.status === "error" ? "error" : item.status === "syncing" ? "loading" : "pending" }), /* @__PURE__ */ React3.createElement(Text3, null, " ", item.targetPath), item.error && /* @__PURE__ */ React3.createElement(Text3, { color: "red", dimColor: true }, " (", item.error, ")")))), /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1 }, /* @__PURE__ */ React3.createElement(Text3, null, "\u{1F4CA} ", /* @__PURE__ */ React3.createElement(Text3, { color: "green" }, syncStats.created, " created"), " \xB7 ", /* @__PURE__ */ React3.createElement(Text3, { color: "cyan" }, syncStats.updated, " updated"), syncStats.skipped > 0 && /* @__PURE__ */ React3.createElement(React3.Fragment, null, " \xB7 ", /* @__PURE__ */ React3.createElement(Text3, { color: "yellow" }, syncStats.skipped, " skipped")), syncStats.errors > 0 && /* @__PURE__ */ React3.createElement(React3.Fragment, null, " \xB7 ", /* @__PURE__ */ React3.createElement(Text3, { color: "red" }, syncStats.errors, " errors")))));
1457
+ }
1458
+ if (viewState === "done") {
1459
+ return /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React3.createElement(Text3, { bold: true, color: "green" }, "\u{1F389} All Done!")), /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", borderStyle: "round", borderColor: "gray", padding: 1 }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "\u{1F4E6} Components Applied:"), /* @__PURE__ */ React3.createElement(Text3, null, " \u2514\u2500 ", /* @__PURE__ */ React3.createElement(Text3, { color: "green" }, installStats.added), " added, ", /* @__PURE__ */ React3.createElement(Text3, { color: "cyan" }, installStats.updated), " updated"), syncStats.total > 0 && /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement(Text3, null, " "), /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "\u{1F504} IDE Sync:"), /* @__PURE__ */ React3.createElement(Text3, null, " \u2514\u2500 ", /* @__PURE__ */ React3.createElement(Text3, { color: "green" }, syncStats.created), " created, ", /* @__PURE__ */ React3.createElement(Text3, { color: "cyan" }, syncStats.updated), " updated"), /* @__PURE__ */ React3.createElement(Text3, null, " \u2514\u2500 Synced to: ", Array.from(selectedIdes).map((ide) => IDE_MIGRATION_CONFIGS[ide].name).join(", ")))), /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1 }, /* @__PURE__ */ React3.createElement(Text3, null, "\u{1F4C1} Framework: ", /* @__PURE__ */ React3.createElement(Text3, { color: "cyan" }, process.cwd(), "/.jai1"))), /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "Press Enter or Q to exit")));
1460
+ }
1461
+ const visibleComponents = filteredComponents.slice(0, 12);
1462
+ const hasMore = filteredComponents.length > 12;
1463
+ return /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React3.createElement(Text3, { bold: true, color: "cyan" }, "\u{1F4E6} Jai1 Apply"), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, " - Select components to install")), /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React3.createElement(Text3, { color: focusArea === "search" ? "cyan" : "gray" }, "\u{1F50D} Search: "), focusArea === "search" ? /* @__PURE__ */ React3.createElement(
1464
+ TextInput,
1465
+ {
1466
+ value: searchQuery,
1467
+ onChange: setSearchQuery,
1468
+ placeholder: "Type to filter..."
1469
+ }
1470
+ ) : /* @__PURE__ */ React3.createElement(Text3, null, searchQuery || /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "Press Tab to search"))), /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1, flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true, dimColor: true }, "Quick Apply (Packages):"), /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1, flexWrap: "wrap" }, tags.slice(0, 6).map((tag, i) => {
1471
+ const isSelected = focusArea === "packages" && i === selectedPackageIndex;
1472
+ return /* @__PURE__ */ React3.createElement(Box2, { key: tag.tag, marginRight: 1 }, /* @__PURE__ */ React3.createElement(
1473
+ Text3,
1474
+ {
1475
+ backgroundColor: isSelected ? "cyan" : void 0,
1476
+ color: isSelected ? "black" : "gray"
1477
+ },
1478
+ "[",
1479
+ tag.tag,
1480
+ ":",
1481
+ tag.count,
1482
+ "]"
1483
+ ));
1484
+ }))), /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", borderStyle: "round", borderColor: focusArea === "components" ? "cyan" : "gray", padding: 1 }, /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Components "), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "(", filteredComponents.length, " shown", hasMore ? `, scroll for more` : "", ")")), visibleComponents.map((comp, i) => {
1485
+ const isCursor = i === cursorIndex && focusArea === "components";
1486
+ const isChecked = selectedPaths.has(comp.filepath);
1487
+ const isInstalled = installedPaths.has(comp.filepath);
1488
+ return /* @__PURE__ */ React3.createElement(Box2, { key: comp.filepath }, /* @__PURE__ */ React3.createElement(Text3, { color: isCursor ? "cyan" : "white" }, isCursor ? "\u276F " : " ", isChecked ? "[\u2713]" : "[ ]", " ", comp.filepath), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, " ", isInstalled ? "\u2713 installed" : "\u25CB new"));
1489
+ }), filteredComponents.length === 0 && /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "No components match your search")), selectedPaths.size > 0 && /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Selected: ", selectedPaths.size, " components"), /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", marginLeft: 2 }, Array.from(selectedPaths).slice(0, 4).map((fp) => /* @__PURE__ */ React3.createElement(Text3, { key: fp, dimColor: true }, "\u{1F4CC} ", fp)), selectedPaths.size > 4 && /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, " ... and ", selectedPaths.size - 4, " more"))), /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "[Tab] Switch area \xB7 [\u2191\u2193/\u2190\u2192] Navigate \xB7 [\u2423] Toggle \xB7 [A] Select all \xB7 [C] Clear \xB7 [Enter] Apply \xB7 [Q] Quit")));
1490
+ };
1491
+
1492
+ // src/commands/apply.ts
1493
+ function createApplyCommand() {
1494
+ const cmd = new Command3("apply").description("Apply components to your project (interactive UI)").argument("[items...]", "Package name or component filepaths (non-interactive mode)").option("--force", "Force overwrite even if modified locally").option("--no-interactive", "Disable interactive UI (for CI/CD)").action(async (items, options) => {
1495
+ const configService = new ConfigService();
1496
+ const config = await configService.load();
1497
+ if (!config) {
1498
+ throw new ValidationError('Not initialized. Run "jai1 auth" first.');
1499
+ }
1500
+ if (options.interactive === false || items.length > 0) {
1501
+ await nonInteractiveApply(config, items, options);
1502
+ return;
1503
+ }
1504
+ const { waitUntilExit } = render(
1505
+ React4.createElement(UnifiedApplyApp, {
1506
+ config,
1507
+ force: options.force,
1508
+ onExit: () => {
1509
+ process.exit(0);
1510
+ }
1511
+ })
1512
+ );
1513
+ await waitUntilExit();
1514
+ });
1515
+ return cmd;
1516
+ }
1517
+ async function nonInteractiveApply(config, items, options) {
1518
+ const componentsService = new ComponentsService();
1519
+ if (items.length === 0) {
1520
+ console.log("\u274C No items specified. Use interactive mode or provide package/component names.");
1521
+ console.log("\u{1F4A1} Usage: jai1 apply <package> or jai1 apply <filepath...>");
1522
+ return;
1523
+ }
1524
+ const tags = await componentsService.listTags(config);
1525
+ const packageNames = tags.map((t) => t.tag);
1526
+ let filepaths;
1527
+ if (items.length === 1 && packageNames.includes(items[0])) {
1528
+ console.log(`\u{1F4E6} Applying package '${items[0]}'...`);
1529
+ const components = await componentsService.list(config, { tag: items[0] });
1530
+ filepaths = components.map((c) => c.filepath);
1531
+ } else {
1532
+ filepaths = items;
1533
+ }
1534
+ console.log("\u{1F50D} Resolving dependencies...");
1535
+ const resolvedPaths = await componentsService.resolveWithDependencies(config, filepaths);
1536
+ console.log(`\u{1F4E6} Will apply ${resolvedPaths.length} component(s):`);
1537
+ for (const fp of resolvedPaths) {
1538
+ const isDep = !filepaths.includes(fp);
1539
+ const suffix = isDep ? " (dependency)" : "";
1540
+ console.log(` - ${fp}${suffix}`);
1541
+ }
1542
+ const targetDir = process.cwd() + "/.jai1";
1543
+ const installed = await componentsService.getInstalled();
1544
+ let added = 0;
1545
+ let updated = 0;
1546
+ let skipped = 0;
1547
+ for (const fp of resolvedPaths) {
1548
+ try {
1549
+ const info = installed[fp];
1550
+ if (info && !options.force && info.modified) {
1551
+ console.log(`\u26A0\uFE0F ${fp}: modified locally. Use --force to overwrite.`);
1552
+ skipped++;
1553
+ continue;
1554
+ }
1555
+ console.log(`\u{1F4E5} Downloading ${fp}...`);
1556
+ await componentsService.install(config, fp, targetDir);
1557
+ if (info) updated++;
1558
+ else added++;
1559
+ console.log(`\u2705 ${fp}`);
1560
+ } catch (error) {
1561
+ console.log(`\u274C ${fp}: ${error instanceof Error ? error.message : "Error"}`);
1562
+ }
1563
+ }
1564
+ console.log(`
1565
+ \u2705 Complete: ${added} added, ${updated} updated, ${skipped} skipped`);
1566
+ console.log(`\u{1F4C1} Location: ${targetDir}`);
1567
+ console.log(`
1568
+ \u{1F4A1} Next step: Run "jai1 ide sync" to sync content to your IDE(s)`);
1569
+ }
1570
+
1571
+ // src/commands/update.ts
1572
+ import { Command as Command4 } from "commander";
1573
+ import { confirm } from "@inquirer/prompts";
1574
+ function createUpdateCommand() {
1575
+ return new Command4("update").description("Update installed components to latest versions").option("--force", "Force update even if files are modified locally").action(async (options) => {
1576
+ await handleUpdate(options);
1577
+ });
1578
+ }
1579
+ async function handleUpdate(options) {
1580
+ const configService = new ConfigService();
1581
+ const config = await configService.load();
1582
+ if (!config) {
1583
+ throw new ValidationError('Not initialized. Run "jai1 auth" first.');
1584
+ }
1585
+ const componentsService = new ComponentsService();
1586
+ const installed = await componentsService.getInstalled();
1587
+ const filepaths = Object.keys(installed);
1588
+ if (filepaths.length === 0) {
1589
+ console.log('No components installed. Run "jai1 apply" to add components.');
1590
+ return;
1591
+ }
1592
+ console.log("\u{1F50D} Checking for updates...");
1593
+ try {
1594
+ const checksums = await componentsService.getChecksums(config, filepaths);
1595
+ const updates = [];
1596
+ const upToDate = [];
1597
+ for (const fp of filepaths) {
1598
+ const current = installed[fp];
1599
+ const latest = checksums[fp];
1600
+ if (!latest) {
1601
+ console.log(`\u26A0\uFE0F ${fp}: Component not found on server (deprecated?)`);
1602
+ continue;
1603
+ }
1604
+ if (current.checksum !== latest.checksum) {
1605
+ updates.push(fp);
1606
+ } else {
1607
+ upToDate.push(fp);
1608
+ }
1609
+ }
1610
+ if (updates.length === 0) {
1611
+ console.log("\u2705 All components are up to date.");
1612
+ return;
1613
+ }
1614
+ console.log(`
1615
+ \u{1F4E6} Found ${updates.length} updates:`);
1616
+ for (const fp of updates) {
1617
+ const current = installed[fp];
1618
+ const latest = checksums[fp];
1619
+ console.log(` - ${fp} (v${current.version} \u2192 v${latest.version})`);
1620
+ }
1621
+ console.log();
1622
+ if (!options.force) {
1623
+ const shouldUpdate = await confirm({ message: "Update now?", default: true });
1624
+ if (!shouldUpdate) return;
1625
+ }
1626
+ const targetDir = process.cwd() + "/.jai1";
1627
+ let updatedCount = 0;
1628
+ const backupPaths = [];
1629
+ for (const fp of updates) {
1630
+ try {
1631
+ console.log(`\u{1F4E5} Updating ${fp}...`);
1632
+ const backupPath = await componentsService.backupFile(fp, targetDir);
1633
+ if (backupPath) {
1634
+ backupPaths.push(backupPath);
1635
+ const displayPath = backupPath.split(".jai1_backup/")[1];
1636
+ console.log(` Backed up: .../${displayPath}`);
1637
+ }
1638
+ await componentsService.install(config, fp, targetDir);
1639
+ console.log(`\u2705 Updated`);
1640
+ updatedCount++;
1641
+ } catch (error) {
1642
+ console.log(`\u274C Failed to update ${fp}: ${error instanceof Error ? error.message : "Unknown error"}`);
1643
+ }
1644
+ }
1645
+ console.log(`
1646
+ \u2705 Update complete: ${updatedCount}/${updates.length} updated.`);
1647
+ if (backupPaths.length > 0) {
1648
+ console.log(`
1649
+ \u{1F4C1} Backups created in .jai1_backup/`);
1650
+ const cleanBackups = await confirm({ message: "Clear these backups?", default: false });
1651
+ if (cleanBackups) {
1652
+ await componentsService.clearBackups(process.cwd());
1653
+ console.log("\u{1F5D1}\uFE0F Backups cleared.");
1654
+ } else {
1655
+ console.log('\u{1F4A1} Run "jai1 clean" to remove them later.');
1656
+ }
1657
+ }
1658
+ trackAction("components_update", { count: updatedCount });
1659
+ } catch (error) {
1660
+ throw new Error(`Update check failed: ${error instanceof Error ? error.message : "Unknown error"}`);
1661
+ }
1662
+ }
1663
+
1664
+ // src/commands/framework/check.ts
1665
+ import { Command as Command5 } from "commander";
1666
+ function createCheckCommand() {
1667
+ const cmd = new Command5("check").description("Check for framework updates").option("--json", "Output as JSON").action(async (options) => {
1668
+ await handleCheck(options);
1669
+ });
1670
+ return cmd;
1671
+ }
1672
+ async function handleCheck(options) {
1673
+ const configService = new ConfigService();
1674
+ const config = await configService.load();
1675
+ if (!config) {
1676
+ throw new ValidationError('Not initialized. Run "jai1 auth" first.');
1677
+ }
1678
+ const componentsService = new ComponentsService();
1679
+ const installed = await componentsService.getInstalled();
1680
+ const filepaths = Object.keys(installed);
1681
+ if (filepaths.length === 0) {
1682
+ console.log('No components installed. Run "jai1 apply" to get started.');
1683
+ return;
1684
+ }
1685
+ console.log("Checking for updates...\n");
1686
+ try {
1687
+ const checksums = await componentsService.getChecksums(config, filepaths);
1688
+ const updates = [];
1689
+ const upToDate = [];
1690
+ for (const fp of filepaths) {
1691
+ const current = installed[fp];
1692
+ const latest = checksums[fp];
1693
+ if (!latest) {
1694
+ continue;
1695
+ }
1696
+ if (current.checksum !== latest.checksum) {
1697
+ updates.push(fp);
1698
+ } else {
1699
+ upToDate.push(fp);
1700
+ }
1701
+ }
1702
+ console.log(`\u{1F4CC} Components status:`);
1703
+ for (const fp of updates) {
1704
+ const current = installed[fp];
1705
+ const latest = checksums[fp];
1706
+ console.log(` ${fp} v${current.version} \u2192 v${latest.version} \u26A0\uFE0F UPDATE`);
1707
+ }
1708
+ if (updates.length === 0 && upToDate.length > 0) {
1709
+ console.log(` \u2713 All ${upToDate.length} components up to date.`);
1710
+ } else if (upToDate.length > 0) {
1711
+ console.log(` \u2713 ${upToDate.length} components up to date.`);
1712
+ }
1713
+ console.log();
1714
+ if (updates.length > 0) {
1715
+ console.log(`Status: ${updates.length} component(s) update available!`);
1716
+ console.log(`Run 'jai1 update' to update.`);
1717
+ } else {
1718
+ console.log(`Status: \u2705 All good!`);
1719
+ }
1720
+ } catch (error) {
1721
+ console.error("Error checking updates:", error instanceof Error ? error.message : error);
1722
+ }
1723
+ }
1724
+
1725
+ // src/commands/ide/index.ts
1726
+ import { Command as Command10 } from "commander";
1727
+
1728
+ // src/commands/ide/context.ts
1729
+ import React10 from "react";
1730
+ import { render as render2 } from "ink";
1731
+ import { Command as Command6 } from "commander";
1732
+
1733
+ // src/ui/context/ContextApp.tsx
1322
1734
  import React9, { useState as useState6, useEffect as useEffect2 } from "react";
1323
1735
  import { Box as Box7, Text as Text8, useInput as useInput6, useApp as useApp2 } from "ink";
1324
1736
 
@@ -1623,9 +2035,9 @@ var DetailView = ({ item, scrollPosition: initialScroll, onBack }) => {
1623
2035
  };
1624
2036
 
1625
2037
  // src/services/context-scanner.service.ts
1626
- import { promises as fs4 } from "fs";
1627
- import path from "path";
1628
- import matter from "gray-matter";
2038
+ import { promises as fs5 } from "fs";
2039
+ import path2 from "path";
2040
+ import matter2 from "gray-matter";
1629
2041
 
1630
2042
  // src/constants/ide-configs.ts
1631
2043
  var IDE_CONFIGS = {
@@ -1788,8 +2200,8 @@ var ContextScannerService = class {
1788
2200
  if (!relativePath) return [];
1789
2201
  let dirPath;
1790
2202
  if (ide === "jai1") {
1791
- const jai1Path = path.join(this.projectPath, ".jai1", relativePath);
1792
- const frameworkPath = path.join(this.projectPath, "packages/jai1-framework", relativePath);
2203
+ const jai1Path = path2.join(this.projectPath, ".jai1", relativePath);
2204
+ const frameworkPath = path2.join(this.projectPath, "packages/jai1-framework", relativePath);
1793
2205
  if (await this.pathExists(jai1Path)) {
1794
2206
  dirPath = jai1Path;
1795
2207
  } else if (await this.pathExists(frameworkPath)) {
@@ -1798,9 +2210,9 @@ var ContextScannerService = class {
1798
2210
  return [];
1799
2211
  }
1800
2212
  } else {
1801
- dirPath = path.join(this.projectPath, config.basePath, relativePath);
2213
+ dirPath = path2.join(this.projectPath, config.basePath, relativePath);
1802
2214
  try {
1803
- await fs4.access(dirPath);
2215
+ await fs5.access(dirPath);
1804
2216
  } catch {
1805
2217
  return [];
1806
2218
  }
@@ -1811,10 +2223,10 @@ var ContextScannerService = class {
1811
2223
  const skillItems = await this.scanSkills(dirPath, ide);
1812
2224
  items.push(...skillItems);
1813
2225
  } else {
1814
- const files = await fs4.readdir(dirPath);
2226
+ const files = await fs5.readdir(dirPath);
1815
2227
  for (const file of files) {
1816
- const filepath = path.join(dirPath, file);
1817
- const stat = await fs4.stat(filepath);
2228
+ const filepath = path2.join(dirPath, file);
2229
+ const stat = await fs5.stat(filepath);
1818
2230
  if (!stat.isFile()) continue;
1819
2231
  const matchesExtension = extensions.some((ext) => file.endsWith(ext));
1820
2232
  if (!matchesExtension) continue;
@@ -1834,20 +2246,20 @@ var ContextScannerService = class {
1834
2246
  async scanSkills(skillsDir, ide) {
1835
2247
  const items = [];
1836
2248
  try {
1837
- const entries = await fs4.readdir(skillsDir, { withFileTypes: true });
2249
+ const entries = await fs5.readdir(skillsDir, { withFileTypes: true });
1838
2250
  for (const entry of entries) {
1839
2251
  if (!entry.isDirectory()) continue;
1840
- const skillPath = path.join(skillsDir, entry.name);
1841
- const skillFilePath = path.join(skillPath, "SKILL.md");
2252
+ const skillPath = path2.join(skillsDir, entry.name);
2253
+ const skillFilePath = path2.join(skillPath, "SKILL.md");
1842
2254
  try {
1843
- await fs4.access(skillFilePath);
2255
+ await fs5.access(skillFilePath);
1844
2256
  } catch {
1845
2257
  continue;
1846
2258
  }
1847
2259
  const item = await this.parseContextItem(skillFilePath, ide, "skills");
1848
- item.hasScripts = await this.pathExists(path.join(skillPath, "scripts"));
1849
- item.hasReferences = await this.pathExists(path.join(skillPath, "references"));
1850
- item.hasAssets = await this.pathExists(path.join(skillPath, "assets"));
2260
+ item.hasScripts = await this.pathExists(path2.join(skillPath, "scripts"));
2261
+ item.hasReferences = await this.pathExists(path2.join(skillPath, "references"));
2262
+ item.hasAssets = await this.pathExists(path2.join(skillPath, "assets"));
1851
2263
  items.push(item);
1852
2264
  }
1853
2265
  } catch (error) {
@@ -1859,9 +2271,9 @@ var ContextScannerService = class {
1859
2271
  * Parse a context item from file
1860
2272
  */
1861
2273
  async parseContextItem(filepath, ide, type) {
1862
- const content = await fs4.readFile(filepath, "utf-8");
1863
- const stat = await fs4.stat(filepath);
1864
- const { data: frontmatter, content: bodyContent } = matter(content);
2274
+ const content = await fs5.readFile(filepath, "utf-8");
2275
+ const stat = await fs5.stat(filepath);
2276
+ const { data: frontmatter, content: bodyContent } = matter2(content);
1865
2277
  const config = IDE_CONFIGS[ide];
1866
2278
  const description = frontmatter[config.frontmatterSchema.descriptionField];
1867
2279
  let globs;
@@ -1877,9 +2289,9 @@ var ContextScannerService = class {
1877
2289
  globs = triggerValue;
1878
2290
  }
1879
2291
  const { previewLines, lineCount } = this.extractPreview(bodyContent, 20);
1880
- const relativePath = path.relative(this.projectPath, filepath);
1881
- const id = `${ide}-${type}-${path.basename(filepath, path.extname(filepath))}`;
1882
- const name = frontmatter.name || path.basename(filepath, path.extname(filepath));
2292
+ const relativePath = path2.relative(this.projectPath, filepath);
2293
+ const id = `${ide}-${type}-${path2.basename(filepath, path2.extname(filepath))}`;
2294
+ const name = frontmatter.name || path2.basename(filepath, path2.extname(filepath));
1883
2295
  return {
1884
2296
  id,
1885
2297
  ide,
@@ -1907,882 +2319,570 @@ var ContextScannerService = class {
1907
2319
  const lineCount = lines.length;
1908
2320
  return { previewLines, lineCount };
1909
2321
  }
1910
- /**
1911
- * Detect available IDEs in project
1912
- */
1913
- async detectIDEs() {
1914
- const ides = [];
1915
- for (const ide of getAllIDEs()) {
1916
- const config = IDE_CONFIGS[ide];
1917
- if (ide === "jai1") {
1918
- const jai1Path = path.join(this.projectPath, ".jai1");
1919
- const frameworkPath = path.join(this.projectPath, "packages/jai1-framework");
1920
- if (await this.pathExists(jai1Path) || await this.pathExists(frameworkPath)) {
1921
- ides.push(ide);
1922
- }
1923
- } else {
1924
- const idePath = path.join(this.projectPath, config.basePath);
1925
- if (await this.pathExists(idePath)) {
1926
- ides.push(ide);
1927
- }
1928
- }
1929
- }
1930
- return ides;
1931
- }
1932
- /**
1933
- * Check if path exists
1934
- */
1935
- async pathExists(filepath) {
1936
- try {
1937
- await fs4.access(filepath);
1938
- return true;
1939
- } catch {
1940
- return false;
1941
- }
1942
- }
1943
- /**
1944
- * Calculate statistics from items
1945
- */
1946
- calculateStats(items) {
1947
- const byType = {};
1948
- for (const item of items) {
1949
- byType[item.type] = (byType[item.type] || 0) + 1;
1950
- }
1951
- const totalSize = items.reduce((sum, item) => sum + item.fileSize, 0);
1952
- const lastModified = items.length > 0 ? items.reduce(
1953
- (latest, item) => item.modifiedAt > latest ? item.modifiedAt : latest,
1954
- items[0].modifiedAt
1955
- ) : null;
1956
- return {
1957
- totalItems: items.length,
1958
- byType,
1959
- totalSize,
1960
- lastModified
1961
- };
1962
- }
1963
- };
1964
-
1965
- // src/ui/context/ContextApp.tsx
1966
- var ContextApp = ({ initialIDE, initialType, onExit }) => {
1967
- const { exit } = useApp2();
1968
- const [screen, setScreen] = useState6("menu");
1969
- const [selectedIDE, setSelectedIDE] = useState6(initialIDE || null);
1970
- const [selectedType, setSelectedType] = useState6(initialType || null);
1971
- const [selectedItem, setSelectedItem] = useState6(null);
1972
- const [ideContexts, setIdeContexts] = useState6([]);
1973
- const [loading, setLoading] = useState6(true);
1974
- const [scrollPosition, setScrollPosition] = useState6(0);
1975
- useEffect2(() => {
1976
- const scanner = new ContextScannerService();
1977
- scanner.scanAll().then((result) => {
1978
- setIdeContexts(result.ides);
1979
- setLoading(false);
1980
- if (initialIDE) {
1981
- setScreen("overview");
1982
- }
1983
- }).catch((error) => {
1984
- console.error("Failed to scan context:", error);
1985
- setLoading(false);
1986
- });
1987
- }, [initialIDE]);
1988
- useInput6((input4, key) => {
1989
- if (input4 === "q") {
1990
- onExit();
1991
- return;
1992
- }
1993
- if (screen !== "detail" && (key.escape || key.backspace)) {
1994
- handleBack();
1995
- return;
1996
- }
1997
- });
1998
- const handleBack = () => {
1999
- if (screen === "detail") {
2000
- setScreen("list");
2001
- setSelectedItem(null);
2002
- setScrollPosition(0);
2003
- } else if (screen === "list") {
2004
- setScreen("overview");
2005
- setSelectedType(null);
2006
- } else if (screen === "overview") {
2007
- setScreen("menu");
2008
- setSelectedIDE(null);
2009
- } else {
2010
- onExit();
2011
- }
2012
- };
2013
- const handleSelectIDE = (ide) => {
2014
- setSelectedIDE(ide);
2015
- setScreen("overview");
2016
- };
2017
- const handleSelectType = (type) => {
2018
- setSelectedType(type);
2019
- setScreen("list");
2020
- };
2021
- const handleSelectItem = (item) => {
2022
- setSelectedItem(item);
2023
- setScreen("detail");
2024
- setScrollPosition(0);
2025
- };
2026
- const currentIDEContext = ideContexts.find((ctx) => ctx.ide === selectedIDE);
2027
- const currentItems = currentIDEContext?.items?.filter((item) => item.type === selectedType) ?? [];
2028
- const renderContent = () => {
2029
- if (loading) {
2030
- return /* @__PURE__ */ React9.createElement(Box7, { padding: 1 }, /* @__PURE__ */ React9.createElement(Text8, null, "\u0110ang qu\xE9t context..."));
2031
- }
2032
- switch (screen) {
2033
- case "menu":
2034
- return /* @__PURE__ */ React9.createElement(MainMenuView, { ideContexts, onSelect: handleSelectIDE });
2035
- case "overview":
2036
- return currentIDEContext ? /* @__PURE__ */ React9.createElement(
2037
- IDEOverviewView,
2038
- {
2039
- ideContext: currentIDEContext,
2040
- onSelectType: handleSelectType,
2041
- onBack: handleBack
2042
- }
2043
- ) : null;
2044
- case "list":
2045
- return /* @__PURE__ */ React9.createElement(
2046
- ListView,
2047
- {
2048
- items: currentItems,
2049
- contentType: selectedType,
2050
- onSelect: handleSelectItem,
2051
- onBack: handleBack
2052
- }
2053
- );
2054
- case "detail":
2055
- return selectedItem ? /* @__PURE__ */ React9.createElement(
2056
- DetailView,
2057
- {
2058
- item: selectedItem,
2059
- scrollPosition,
2060
- onBack: handleBack
2061
- }
2062
- ) : null;
2063
- default:
2064
- return /* @__PURE__ */ React9.createElement(MainMenuView, { ideContexts, onSelect: handleSelectIDE });
2065
- }
2066
- };
2067
- const getFooterHints = () => {
2068
- switch (screen) {
2069
- case "menu":
2070
- return "[\u2191\u2193] Ch\u1ECDn \xB7 [Enter] M\u1EDF \xB7 [q] Tho\xE1t";
2071
- case "overview":
2072
- return "[Tab/1-6] Chuy\u1EC3n tab \xB7 [Enter] Xem danh s\xE1ch \xB7 [Esc/\u2190] Quay l\u1EA1i \xB7 [q] Tho\xE1t";
2073
- case "list":
2074
- return "[\u2191\u2193] Ch\u1ECDn \xB7 [Enter] Chi ti\u1EBFt \xB7 [Esc/\u2190] Quay l\u1EA1i \xB7 [q] Tho\xE1t";
2075
- case "detail":
2076
- return "[\u2191\u2193/j/k] Cu\u1ED9n \xB7 [PgUp/u PgDn/d] Cu\u1ED9n nhanh \xB7 [g/G] \u0110\u1EA7u/Cu\u1ED1i \xB7 [Esc/\u2190] Quay l\u1EA1i \xB7 [q] Tho\xE1t";
2077
- default:
2078
- return "[q] Tho\xE1t";
2079
- }
2080
- };
2081
- return /* @__PURE__ */ React9.createElement(Box7, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React9.createElement(Box7, { marginBottom: 1 }, /* @__PURE__ */ React9.createElement(Text8, { bold: true, color: "cyan" }, "\u{1F4CB} Jai1 Context"), /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, " - Kh\xE1m ph\xE1 context d\u1EF1 \xE1n")), renderContent(), /* @__PURE__ */ React9.createElement(Box7, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1 }, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, getFooterHints())));
2082
- };
2083
-
2084
- // src/commands/ide/context.ts
2085
- function createContextSubcommand() {
2086
- const cmd = new Command6("context").description("Browse and explore IDE context (rules, workflows, skills)").option("--target <ide>", "Open specific IDE context (cursor, windsurf, antigravity, jai1)").option("--type <type>", "Show specific content type (rules, workflows, skills, agents, prompts)").option("--stats", "Show context statistics (non-interactive)").action(async (options) => {
2087
- let initialIDE;
2088
- if (options.target) {
2089
- const validIDEs = ["cursor", "windsurf", "antigravity", "jai1"];
2090
- if (!validIDEs.includes(options.target)) {
2091
- console.error(`\u274C Invalid IDE: ${options.target}`);
2092
- console.error(` Valid IDEs: ${validIDEs.join(", ")}`);
2093
- process.exit(1);
2094
- }
2095
- initialIDE = options.target;
2096
- }
2097
- let initialType;
2098
- if (options.type) {
2099
- const validTypes = ["rules", "workflows", "skills", "agents", "prompts", "context"];
2100
- if (!validTypes.includes(options.type)) {
2101
- console.error(`\u274C Invalid content type: ${options.type}`);
2102
- console.error(` Valid types: ${validTypes.join(", ")}`);
2103
- process.exit(1);
2104
- }
2105
- initialType = options.type;
2106
- }
2107
- if (options.stats) {
2108
- await printStats();
2109
- return;
2110
- }
2111
- const { waitUntilExit } = render2(
2112
- React10.createElement(ContextApp, {
2113
- initialIDE,
2114
- initialType,
2115
- onExit: () => {
2116
- process.exit(0);
2322
+ /**
2323
+ * Detect available IDEs in project
2324
+ */
2325
+ async detectIDEs() {
2326
+ const ides = [];
2327
+ for (const ide of getAllIDEs()) {
2328
+ const config = IDE_CONFIGS[ide];
2329
+ if (ide === "jai1") {
2330
+ const jai1Path = path2.join(this.projectPath, ".jai1");
2331
+ const frameworkPath = path2.join(this.projectPath, "packages/jai1-framework");
2332
+ if (await this.pathExists(jai1Path) || await this.pathExists(frameworkPath)) {
2333
+ ides.push(ide);
2334
+ }
2335
+ } else {
2336
+ const idePath = path2.join(this.projectPath, config.basePath);
2337
+ if (await this.pathExists(idePath)) {
2338
+ ides.push(ide);
2117
2339
  }
2118
- })
2119
- );
2120
- await waitUntilExit();
2121
- });
2122
- return cmd;
2123
- }
2124
- async function printStats() {
2125
- const scanner = new ContextScannerService();
2126
- console.log("\u{1F50D} Scanning context...\n");
2127
- try {
2128
- const context = await scanner.scanAll();
2129
- if (context.ides.length === 0) {
2130
- console.log("\u26A0\uFE0F No context found");
2131
- console.log(" Run `jai1 apply` to install context for your IDE.\n");
2132
- return;
2133
- }
2134
- console.log("\u{1F4CA} Context Statistics\n");
2135
- console.log(`\u{1F4C1} Project: ${context.projectPath}`);
2136
- console.log(`\u{1F550} Scan time: ${context.scanTime.toLocaleString("en-US")}
2137
- `);
2138
- for (const ideContext of context.ides) {
2139
- console.log(`${ideContext.config.icon} ${ideContext.config.name}`);
2140
- console.log(` Path: ${ideContext.config.basePath}`);
2141
- console.log(` Items: ${ideContext.stats.totalItems}`);
2142
- const types = [];
2143
- if (ideContext.stats.byType.rules) types.push(`${ideContext.stats.byType.rules} rules`);
2144
- if (ideContext.stats.byType.workflows) types.push(`${ideContext.stats.byType.workflows} workflows`);
2145
- if (ideContext.stats.byType.skills) types.push(`${ideContext.stats.byType.skills} skills`);
2146
- if (ideContext.stats.byType.agents) types.push(`${ideContext.stats.byType.agents} agents`);
2147
- if (ideContext.stats.byType.prompts) types.push(`${ideContext.stats.byType.prompts} prompts`);
2148
- if (ideContext.stats.byType.context) types.push(`${ideContext.stats.byType.context} context`);
2149
- if (types.length > 0) {
2150
- console.log(` Breakdown: ${types.join(", ")}`);
2151
2340
  }
2152
- console.log("");
2153
2341
  }
2154
- console.log(`\u2705 Total: ${context.totalItems} items
2155
- `);
2156
- } catch (error) {
2157
- console.error("\u274C Error scanning context:", error);
2158
- process.exit(1);
2342
+ return ides;
2159
2343
  }
2160
- }
2161
-
2162
- // src/commands/ide/setup.ts
2163
- import { Command as Command7 } from "commander";
2164
- import { checkbox, confirm as confirm2, select } from "@inquirer/prompts";
2165
- import fs5 from "fs/promises";
2166
- import path2 from "path";
2167
- import { existsSync } from "fs";
2168
- var PERFORMANCE_GROUPS = {
2169
- telemetry: {
2170
- name: "Telemetry",
2171
- description: "Disable all telemetry and data collection",
2172
- settings: {
2173
- "telemetry.telemetryLevel": "off",
2174
- "telemetry.feedback.enabled": false,
2175
- "workbench.enableExperiments": false,
2176
- "workbench.settings.enableNaturalLanguageSearch": false
2177
- }
2178
- },
2179
- languageServers: {
2180
- name: "Language Servers",
2181
- description: "Disable/reduce language server load (tsserver, eslint...)",
2182
- settings: {
2183
- // TypeScript
2184
- "typescript.tsserver.experimental.enableProjectDiagnostics": false,
2185
- "typescript.disableAutomaticTypeAcquisition": true,
2186
- "typescript.tsserver.maxTsServerMemory": 2048,
2187
- "typescript.surveys.enabled": false,
2188
- // JavaScript
2189
- "js/ts.implicitProjectConfig.strictNullChecks": false,
2190
- // ESLint
2191
- "eslint.enable": false,
2192
- // Prettier
2193
- "prettier.enable": false,
2194
- // CSS/HTML
2195
- "css.validate": false,
2196
- "html.validate.scripts": false,
2197
- "html.validate.styles": false,
2198
- // JSON
2199
- "json.schemaDownload.enable": false
2344
+ /**
2345
+ * Check if path exists
2346
+ */
2347
+ async pathExists(filepath) {
2348
+ try {
2349
+ await fs5.access(filepath);
2350
+ return true;
2351
+ } catch {
2352
+ return false;
2200
2353
  }
2201
- },
2202
- git: {
2203
- name: "Git Integration",
2204
- description: "Disable built-in Git for better performance",
2205
- settings: {
2206
- "git.enabled": false,
2207
- "git.autoRepositoryDetection": false,
2208
- "git.decorations.enabled": false,
2209
- "git.path": null,
2210
- "scm.diffDecorations": "none"
2354
+ }
2355
+ /**
2356
+ * Calculate statistics from items
2357
+ */
2358
+ calculateStats(items) {
2359
+ const byType = {};
2360
+ for (const item of items) {
2361
+ byType[item.type] = (byType[item.type] || 0) + 1;
2211
2362
  }
2212
- },
2213
- fileWatcher: {
2214
- name: "File Watcher",
2215
- description: "Exclude directories from file watcher",
2216
- settings: {
2217
- "files.watcherExclude": {
2218
- "**/node_modules/**": true,
2219
- "**/.git/**": true,
2220
- "**/vendor/**": true,
2221
- "**/dist/**": true,
2222
- "**/build/**": true,
2223
- "**/.next/**": true,
2224
- "**/out/**": true,
2225
- "**/.nuxt/**": true,
2226
- "**/.cache/**": true,
2227
- "**/coverage/**": true
2228
- },
2229
- "files.exclude": {
2230
- "**/.git": true,
2231
- "**/.DS_Store": true,
2232
- "**/node_modules": false
2233
- // Keep visible but don't watch
2363
+ const totalSize = items.reduce((sum, item) => sum + item.fileSize, 0);
2364
+ const lastModified = items.length > 0 ? items.reduce(
2365
+ (latest, item) => item.modifiedAt > latest ? item.modifiedAt : latest,
2366
+ items[0].modifiedAt
2367
+ ) : null;
2368
+ return {
2369
+ totalItems: items.length,
2370
+ byType,
2371
+ totalSize,
2372
+ lastModified
2373
+ };
2374
+ }
2375
+ };
2376
+
2377
+ // src/ui/context/ContextApp.tsx
2378
+ var ContextApp = ({ initialIDE, initialType, onExit }) => {
2379
+ const { exit } = useApp2();
2380
+ const [screen, setScreen] = useState6("menu");
2381
+ const [selectedIDE, setSelectedIDE] = useState6(initialIDE || null);
2382
+ const [selectedType, setSelectedType] = useState6(initialType || null);
2383
+ const [selectedItem, setSelectedItem] = useState6(null);
2384
+ const [ideContexts, setIdeContexts] = useState6([]);
2385
+ const [loading, setLoading] = useState6(true);
2386
+ const [scrollPosition, setScrollPosition] = useState6(0);
2387
+ useEffect2(() => {
2388
+ const scanner = new ContextScannerService();
2389
+ scanner.scanAll().then((result) => {
2390
+ setIdeContexts(result.ides);
2391
+ setLoading(false);
2392
+ if (initialIDE) {
2393
+ setScreen("overview");
2234
2394
  }
2395
+ }).catch((error) => {
2396
+ console.error("Failed to scan context:", error);
2397
+ setLoading(false);
2398
+ });
2399
+ }, [initialIDE]);
2400
+ useInput6((input4, key) => {
2401
+ if (input4 === "q") {
2402
+ onExit();
2403
+ return;
2235
2404
  }
2236
- },
2237
- search: {
2238
- name: "Search",
2239
- description: "Exclude directories from search",
2240
- settings: {
2241
- "search.exclude": {
2242
- "**/node_modules": true,
2243
- "**/bower_components": true,
2244
- "**/*.code-search": true,
2245
- "**/dist": true,
2246
- "**/build": true,
2247
- "**/out": true,
2248
- "**/.next": true,
2249
- "**/.nuxt": true,
2250
- "**/coverage": true,
2251
- "**/.cache": true
2252
- },
2253
- "search.followSymlinks": false
2405
+ if (screen !== "detail" && (key.escape || key.backspace)) {
2406
+ handleBack();
2407
+ return;
2254
2408
  }
2255
- },
2256
- extensions: {
2257
- name: "Extensions",
2258
- description: "Disable auto-update extensions",
2259
- settings: {
2260
- "extensions.autoUpdate": false,
2261
- "extensions.autoCheckUpdates": false,
2262
- "update.mode": "manual",
2263
- "extensions.ignoreRecommendations": true
2409
+ });
2410
+ const handleBack = () => {
2411
+ if (screen === "detail") {
2412
+ setScreen("list");
2413
+ setSelectedItem(null);
2414
+ setScrollPosition(0);
2415
+ } else if (screen === "list") {
2416
+ setScreen("overview");
2417
+ setSelectedType(null);
2418
+ } else if (screen === "overview") {
2419
+ setScreen("menu");
2420
+ setSelectedIDE(null);
2421
+ } else {
2422
+ onExit();
2264
2423
  }
2265
- },
2266
- editorRendering: {
2267
- name: "Editor Rendering",
2268
- description: "Optimize rendering (minimap, whitespace, highlights...)",
2269
- settings: {
2270
- "editor.minimap.enabled": false,
2271
- "editor.renderWhitespace": "none",
2272
- "editor.renderControlCharacters": false,
2273
- "editor.renderLineHighlight": "none",
2274
- "editor.cursorBlinking": "solid",
2275
- "editor.smoothScrolling": false,
2276
- "editor.largeFileOptimizations": true,
2277
- "editor.maxTokenizationLineLength": 2e3,
2278
- "editor.quickSuggestions": {
2279
- "other": true,
2280
- "comments": false,
2281
- "strings": false
2282
- },
2283
- "editor.suggest.showStatusBar": false,
2284
- "editor.hover.delay": 500,
2285
- "editor.matchBrackets": "never"
2424
+ };
2425
+ const handleSelectIDE = (ide) => {
2426
+ setSelectedIDE(ide);
2427
+ setScreen("overview");
2428
+ };
2429
+ const handleSelectType = (type) => {
2430
+ setSelectedType(type);
2431
+ setScreen("list");
2432
+ };
2433
+ const handleSelectItem = (item) => {
2434
+ setSelectedItem(item);
2435
+ setScreen("detail");
2436
+ setScrollPosition(0);
2437
+ };
2438
+ const currentIDEContext = ideContexts.find((ctx) => ctx.ide === selectedIDE);
2439
+ const currentItems = currentIDEContext?.items?.filter((item) => item.type === selectedType) ?? [];
2440
+ const renderContent = () => {
2441
+ if (loading) {
2442
+ return /* @__PURE__ */ React9.createElement(Box7, { padding: 1 }, /* @__PURE__ */ React9.createElement(Text8, null, "\u0110ang qu\xE9t context..."));
2286
2443
  }
2287
- },
2288
- uiElements: {
2289
- name: "UI Elements",
2290
- description: "Hide unnecessary UI elements",
2291
- settings: {
2292
- "workbench.activityBar.visible": false,
2293
- "workbench.statusBar.visible": false,
2294
- "breadcrumbs.enabled": false,
2295
- "window.menuBarVisibility": "compact",
2296
- "workbench.editor.showTabs": true,
2297
- // Keep tabs for navigation
2298
- "workbench.startupEditor": "none"
2444
+ switch (screen) {
2445
+ case "menu":
2446
+ return /* @__PURE__ */ React9.createElement(MainMenuView, { ideContexts, onSelect: handleSelectIDE });
2447
+ case "overview":
2448
+ return currentIDEContext ? /* @__PURE__ */ React9.createElement(
2449
+ IDEOverviewView,
2450
+ {
2451
+ ideContext: currentIDEContext,
2452
+ onSelectType: handleSelectType,
2453
+ onBack: handleBack
2454
+ }
2455
+ ) : null;
2456
+ case "list":
2457
+ return /* @__PURE__ */ React9.createElement(
2458
+ ListView,
2459
+ {
2460
+ items: currentItems,
2461
+ contentType: selectedType,
2462
+ onSelect: handleSelectItem,
2463
+ onBack: handleBack
2464
+ }
2465
+ );
2466
+ case "detail":
2467
+ return selectedItem ? /* @__PURE__ */ React9.createElement(
2468
+ DetailView,
2469
+ {
2470
+ item: selectedItem,
2471
+ scrollPosition,
2472
+ onBack: handleBack
2473
+ }
2474
+ ) : null;
2475
+ default:
2476
+ return /* @__PURE__ */ React9.createElement(MainMenuView, { ideContexts, onSelect: handleSelectIDE });
2299
2477
  }
2300
- }
2478
+ };
2479
+ const getFooterHints = () => {
2480
+ switch (screen) {
2481
+ case "menu":
2482
+ return "[\u2191\u2193] Ch\u1ECDn \xB7 [Enter] M\u1EDF \xB7 [q] Tho\xE1t";
2483
+ case "overview":
2484
+ return "[Tab/1-6] Chuy\u1EC3n tab \xB7 [Enter] Xem danh s\xE1ch \xB7 [Esc/\u2190] Quay l\u1EA1i \xB7 [q] Tho\xE1t";
2485
+ case "list":
2486
+ return "[\u2191\u2193] Ch\u1ECDn \xB7 [Enter] Chi ti\u1EBFt \xB7 [Esc/\u2190] Quay l\u1EA1i \xB7 [q] Tho\xE1t";
2487
+ case "detail":
2488
+ return "[\u2191\u2193/j/k] Cu\u1ED9n \xB7 [PgUp/u PgDn/d] Cu\u1ED9n nhanh \xB7 [g/G] \u0110\u1EA7u/Cu\u1ED1i \xB7 [Esc/\u2190] Quay l\u1EA1i \xB7 [q] Tho\xE1t";
2489
+ default:
2490
+ return "[q] Tho\xE1t";
2491
+ }
2492
+ };
2493
+ return /* @__PURE__ */ React9.createElement(Box7, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React9.createElement(Box7, { marginBottom: 1 }, /* @__PURE__ */ React9.createElement(Text8, { bold: true, color: "cyan" }, "\u{1F4CB} Jai1 Context"), /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, " - Kh\xE1m ph\xE1 context d\u1EF1 \xE1n")), renderContent(), /* @__PURE__ */ React9.createElement(Box7, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1 }, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, getFooterHints())));
2301
2494
  };
2302
- function createSetupSubcommand() {
2303
- const setupCommand = new Command7("setup").description("Configure IDE settings and optimizations");
2304
- setupCommand.action(async () => {
2305
- await interactiveMode();
2306
- });
2307
- setupCommand.command("enable").description("Enable specific optimization groups").argument("[groups...]", "Group names to enable (telemetry, languageServers, git, ...)").action(async (groups) => {
2308
- if (groups.length === 0) {
2309
- await selectGroupsToApply("enable");
2310
- } else {
2311
- await applyGroups(groups, "enable");
2495
+
2496
+ // src/commands/ide/context.ts
2497
+ function createContextSubcommand() {
2498
+ const cmd = new Command6("context").description("Browse and explore IDE context (rules, workflows, skills)").option("--target <ide>", "Open specific IDE context (cursor, windsurf, antigravity, jai1)").option("--type <type>", "Show specific content type (rules, workflows, skills, agents, prompts)").option("--stats", "Show context statistics (non-interactive)").action(async (options) => {
2499
+ let initialIDE;
2500
+ if (options.target) {
2501
+ const validIDEs = ["cursor", "windsurf", "antigravity", "jai1"];
2502
+ if (!validIDEs.includes(options.target)) {
2503
+ console.error(`\u274C Invalid IDE: ${options.target}`);
2504
+ console.error(` Valid IDEs: ${validIDEs.join(", ")}`);
2505
+ process.exit(1);
2506
+ }
2507
+ initialIDE = options.target;
2312
2508
  }
2313
- });
2314
- setupCommand.command("disable").description("Disable specific optimization groups (restore to default)").argument("[groups...]", "Group names to disable").action(async (groups) => {
2315
- if (groups.length === 0) {
2316
- await selectGroupsToApply("disable");
2317
- } else {
2318
- await applyGroups(groups, "disable");
2509
+ let initialType;
2510
+ if (options.type) {
2511
+ const validTypes = ["rules", "workflows", "skills", "agents", "prompts", "context"];
2512
+ if (!validTypes.includes(options.type)) {
2513
+ console.error(`\u274C Invalid content type: ${options.type}`);
2514
+ console.error(` Valid types: ${validTypes.join(", ")}`);
2515
+ process.exit(1);
2516
+ }
2517
+ initialType = options.type;
2319
2518
  }
2519
+ if (options.stats) {
2520
+ await printStats();
2521
+ return;
2522
+ }
2523
+ const { waitUntilExit } = render2(
2524
+ React10.createElement(ContextApp, {
2525
+ initialIDE,
2526
+ initialType,
2527
+ onExit: () => {
2528
+ process.exit(0);
2529
+ }
2530
+ })
2531
+ );
2532
+ await waitUntilExit();
2320
2533
  });
2321
- setupCommand.command("max-performance").description("Enable all optimizations for maximum performance").action(async () => {
2322
- const allGroups = Object.keys(PERFORMANCE_GROUPS);
2323
- await applyGroups(allGroups, "enable");
2324
- });
2325
- setupCommand.command("reset").description("Reset to default settings (remove all optimizations)").argument("[groups...]", "Group names to reset (empty = reset all)").action(async (groups) => {
2326
- await resetSettings(groups);
2327
- });
2328
- setupCommand.command("list").description("List all available optimization groups").action(() => {
2329
- console.log("\u{1F527} Available optimization groups:\n");
2330
- Object.entries(PERFORMANCE_GROUPS).forEach(([key, group]) => {
2331
- console.log(` ${key.padEnd(20)} - ${group.name}`);
2332
- console.log(` ${" ".repeat(20)} ${group.description}`);
2333
- console.log();
2334
- });
2335
- });
2336
- return setupCommand;
2337
- }
2338
- async function interactiveMode() {
2339
- console.log("\u{1F527} IDE Settings Manager\n");
2340
- console.log("\u256D\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\u256E");
2341
- console.log("\u2502 \u{1F4CC} Usage: \u2502");
2342
- console.log("\u2502 \u2022 Use \u2191\u2193 keys to navigate \u2502");
2343
- console.log("\u2502 \u2022 Press SPACE to select/deselect group \u2502");
2344
- console.log("\u2502 \u2022 Press ENTER to confirm and apply \u2502");
2345
- console.log("\u2502 \u2022 Press Ctrl+C to cancel \u2502");
2346
- 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");
2347
- const action = await select({
2348
- message: "What do you want to do?",
2349
- choices: [
2350
- { name: "\u2705 Enable optimization groups", value: "enable" },
2351
- { name: "\u274C Disable optimization groups", value: "disable" },
2352
- { name: "\u{1F680} Max Performance (enable all)", value: "max" },
2353
- { name: "\u{1F504} Reset to defaults", value: "reset" }
2354
- ]
2355
- });
2356
- if (action === "max") {
2357
- const allGroups = Object.keys(PERFORMANCE_GROUPS);
2358
- await applyGroups(allGroups, "enable");
2359
- } else if (action === "reset") {
2360
- await resetSettings([]);
2361
- } else {
2362
- await selectGroupsToApply(action);
2363
- }
2534
+ return cmd;
2364
2535
  }
2365
- async function selectGroupsToApply(action) {
2366
- const choices = Object.entries(PERFORMANCE_GROUPS).map(([key, group]) => ({
2367
- name: `${group.name} - ${group.description}`,
2368
- value: key
2369
- }));
2536
+ async function printStats() {
2537
+ const scanner = new ContextScannerService();
2538
+ console.log("\u{1F50D} Scanning context...\n");
2370
2539
  try {
2371
- const selectedGroups = await checkbox({
2372
- message: `Select groups to ${action} (SPACE to select, ENTER to confirm):`,
2373
- choices
2374
- });
2375
- if (selectedGroups.length === 0) {
2376
- console.log("\n\u26A0\uFE0F No groups selected!");
2377
- console.log(" \u{1F4A1} Tip: Press SPACE to select at least 1 group before pressing ENTER.");
2540
+ const context = await scanner.scanAll();
2541
+ if (context.ides.length === 0) {
2542
+ console.log("\u26A0\uFE0F No context found");
2543
+ console.log(" Run `jai1 apply` to install context for your IDE.\n");
2378
2544
  return;
2379
2545
  }
2380
- await applyGroups(selectedGroups, action);
2381
- } catch {
2382
- console.log("\n\u274C Operation cancelled.");
2383
- }
2384
- }
2385
- async function applyGroups(groupKeys, action) {
2386
- const vscodeDir = path2.join(process.cwd(), ".vscode");
2387
- const settingsPath = path2.join(vscodeDir, "settings.json");
2388
- const invalidGroups = groupKeys.filter((key) => !PERFORMANCE_GROUPS[key]);
2389
- if (invalidGroups.length > 0) {
2390
- console.log(`
2391
- \u274C Invalid groups: ${invalidGroups.join(", ")}`);
2392
- console.log(' \u{1F4A1} Run "jai1 ide setup list" to see available groups.');
2393
- return;
2394
- }
2395
- if (!existsSync(vscodeDir)) {
2396
- await fs5.mkdir(vscodeDir, { recursive: true });
2397
- console.log("\u{1F4C1} Created .vscode/ directory");
2398
- }
2399
- let currentSettings = {};
2400
- if (existsSync(settingsPath)) {
2401
- try {
2402
- const content = await fs5.readFile(settingsPath, "utf-8");
2403
- currentSettings = JSON.parse(content);
2404
- console.log("\u{1F4C4} Read current settings from settings.json");
2405
- } catch {
2406
- console.warn("\u26A0\uFE0F Cannot read settings.json (may contain comments).");
2407
- const confirmOverwrite = await confirm2({
2408
- message: "Overwrite current settings.json file?",
2409
- default: false
2410
- });
2411
- if (!confirmOverwrite) {
2412
- console.log("\u274C Operation cancelled.");
2413
- return;
2414
- }
2415
- currentSettings = {};
2416
- }
2417
- }
2418
- const newSettings = { ...currentSettings };
2419
- console.log(`
2420
- \u{1F4DD} ${action === "enable" ? "Enabling" : "Disabling"} groups:
2546
+ console.log("\u{1F4CA} Context Statistics\n");
2547
+ console.log(`\u{1F4C1} Project: ${context.projectPath}`);
2548
+ console.log(`\u{1F550} Scan time: ${context.scanTime.toLocaleString("en-US")}
2421
2549
  `);
2422
- for (const key of groupKeys) {
2423
- const group = PERFORMANCE_GROUPS[key];
2424
- console.log(` ${action === "enable" ? "\u2713" : "\u2717"} ${group.name}`);
2425
- if (action === "enable") {
2426
- for (const [settingKey, settingValue] of Object.entries(group.settings)) {
2427
- if (typeof settingValue === "object" && settingValue !== null && !Array.isArray(settingValue) && typeof newSettings[settingKey] === "object" && newSettings[settingKey] !== null) {
2428
- newSettings[settingKey] = {
2429
- ...newSettings[settingKey],
2430
- ...settingValue
2431
- };
2432
- } else {
2433
- newSettings[settingKey] = settingValue;
2434
- }
2435
- }
2436
- } else {
2437
- for (const settingKey of Object.keys(group.settings)) {
2438
- delete newSettings[settingKey];
2550
+ for (const ideContext of context.ides) {
2551
+ console.log(`${ideContext.config.icon} ${ideContext.config.name}`);
2552
+ console.log(` Path: ${ideContext.config.basePath}`);
2553
+ console.log(` Items: ${ideContext.stats.totalItems}`);
2554
+ const types = [];
2555
+ if (ideContext.stats.byType.rules) types.push(`${ideContext.stats.byType.rules} rules`);
2556
+ if (ideContext.stats.byType.workflows) types.push(`${ideContext.stats.byType.workflows} workflows`);
2557
+ if (ideContext.stats.byType.skills) types.push(`${ideContext.stats.byType.skills} skills`);
2558
+ if (ideContext.stats.byType.agents) types.push(`${ideContext.stats.byType.agents} agents`);
2559
+ if (ideContext.stats.byType.prompts) types.push(`${ideContext.stats.byType.prompts} prompts`);
2560
+ if (ideContext.stats.byType.context) types.push(`${ideContext.stats.byType.context} context`);
2561
+ if (types.length > 0) {
2562
+ console.log(` Breakdown: ${types.join(", ")}`);
2439
2563
  }
2564
+ console.log("");
2440
2565
  }
2566
+ console.log(`\u2705 Total: ${context.totalItems} items
2567
+ `);
2568
+ } catch (error) {
2569
+ console.error("\u274C Error scanning context:", error);
2570
+ process.exit(1);
2441
2571
  }
2442
- await fs5.writeFile(settingsPath, JSON.stringify(newSettings, null, 2));
2443
- console.log(`
2444
- \u2705 Updated IDE settings at: ${settingsPath}`);
2445
- console.log("\u{1F4A1} Tip: Restart your IDE to apply changes.");
2446
- }
2447
- async function resetSettings(groupKeys) {
2448
- const vscodeDir = path2.join(process.cwd(), ".vscode");
2449
- const settingsPath = path2.join(vscodeDir, "settings.json");
2450
- if (!existsSync(settingsPath)) {
2451
- console.log("\n\u26A0\uFE0F No settings.json file found");
2452
- return;
2453
- }
2454
- const confirmReset = await confirm2({
2455
- message: groupKeys.length === 0 ? "Reset ALL settings to default (delete entire file)?" : `Reset groups: ${groupKeys.join(", ")}?`,
2456
- default: false
2457
- });
2458
- if (!confirmReset) {
2459
- console.log("\u274C Operation cancelled.");
2460
- return;
2461
- }
2462
- if (groupKeys.length === 0) {
2463
- await fs5.unlink(settingsPath);
2464
- console.log("\n\u2705 Deleted settings.json file");
2465
- } else {
2466
- await applyGroups(groupKeys, "disable");
2467
- }
2468
- console.log("\u{1F4A1} Tip: Restart your IDE to apply changes.");
2469
2572
  }
2470
2573
 
2471
- // src/commands/ide/sync.ts
2472
- import { Command as Command8 } from "commander";
2473
- import { checkbox as checkbox2, confirm as confirm3 } from "@inquirer/prompts";
2474
-
2475
- // src/services/migrate-ide.service.ts
2476
- import { promises as fs6 } from "fs";
2574
+ // src/commands/ide/setup.ts
2575
+ import { Command as Command7 } from "commander";
2576
+ import { checkbox, confirm as confirm2, select } from "@inquirer/prompts";
2577
+ import fs6 from "fs/promises";
2477
2578
  import path3 from "path";
2478
- import matter2 from "gray-matter";
2479
-
2480
- // src/constants/ide-migration-configs.ts
2481
- var IDE_MIGRATION_CONFIGS = {
2482
- cursor: {
2483
- id: "cursor",
2484
- name: "Cursor",
2485
- icon: "\u{1F52E}",
2486
- basePath: ".cursor",
2487
- rulesPath: "rules",
2488
- workflowsPath: null,
2489
- commandsPath: "commands",
2490
- fileExtension: ".mdc",
2491
- generateFrontmatter: (opts) => {
2492
- const lines = ["---"];
2493
- if (opts.description) {
2494
- lines.push(`description: ${opts.description}`);
2495
- }
2496
- if (opts.globs && opts.globs.length > 0) {
2497
- lines.push(`globs: ${opts.globs.join(", ")}`);
2498
- }
2499
- lines.push(`alwaysApply: ${opts.alwaysApply}`);
2500
- lines.push("---");
2501
- return lines.join("\n");
2579
+ import { existsSync } from "fs";
2580
+ var PERFORMANCE_GROUPS = {
2581
+ telemetry: {
2582
+ name: "Telemetry",
2583
+ description: "Disable all telemetry and data collection",
2584
+ settings: {
2585
+ "telemetry.telemetryLevel": "off",
2586
+ "telemetry.feedback.enabled": false,
2587
+ "workbench.enableExperiments": false,
2588
+ "workbench.settings.enableNaturalLanguageSearch": false
2502
2589
  }
2503
2590
  },
2504
- windsurf: {
2505
- id: "windsurf",
2506
- name: "Windsurf",
2507
- icon: "\u{1F3C4}",
2508
- basePath: ".windsurf",
2509
- rulesPath: "rules",
2510
- workflowsPath: "workflows",
2511
- commandsPath: null,
2512
- fileExtension: ".md",
2513
- generateFrontmatter: (opts) => {
2514
- const trigger = opts.alwaysApply ? "always" : "glob";
2515
- return `---
2516
- trigger: ${trigger}
2517
- ---`;
2591
+ languageServers: {
2592
+ name: "Language Servers",
2593
+ description: "Disable/reduce language server load (tsserver, eslint...)",
2594
+ settings: {
2595
+ // TypeScript
2596
+ "typescript.tsserver.experimental.enableProjectDiagnostics": false,
2597
+ "typescript.disableAutomaticTypeAcquisition": true,
2598
+ "typescript.tsserver.maxTsServerMemory": 2048,
2599
+ "typescript.surveys.enabled": false,
2600
+ // JavaScript
2601
+ "js/ts.implicitProjectConfig.strictNullChecks": false,
2602
+ // ESLint
2603
+ "eslint.enable": false,
2604
+ // Prettier
2605
+ "prettier.enable": false,
2606
+ // CSS/HTML
2607
+ "css.validate": false,
2608
+ "html.validate.scripts": false,
2609
+ "html.validate.styles": false,
2610
+ // JSON
2611
+ "json.schemaDownload.enable": false
2518
2612
  }
2519
2613
  },
2520
- antigravity: {
2521
- id: "antigravity",
2522
- name: "Antigravity",
2523
- icon: "\u{1F680}",
2524
- basePath: ".agent",
2525
- rulesPath: "rules",
2526
- workflowsPath: "workflows",
2527
- commandsPath: null,
2528
- fileExtension: ".md",
2529
- generateFrontmatter: (opts) => {
2530
- const trigger = opts.alwaysApply ? "always" : "glob";
2531
- return `---
2532
- trigger: ${trigger}
2533
- ---`;
2614
+ git: {
2615
+ name: "Git Integration",
2616
+ description: "Disable built-in Git for better performance",
2617
+ settings: {
2618
+ "git.enabled": false,
2619
+ "git.autoRepositoryDetection": false,
2620
+ "git.decorations.enabled": false,
2621
+ "git.path": null,
2622
+ "scm.diffDecorations": "none"
2534
2623
  }
2535
2624
  },
2536
- claudecode: {
2537
- id: "claudecode",
2538
- name: "Claude Code",
2539
- icon: "\u{1F916}",
2540
- basePath: ".claude",
2541
- rulesPath: "rules",
2542
- workflowsPath: null,
2543
- commandsPath: "commands",
2544
- fileExtension: ".md",
2545
- generateFrontmatter: (opts) => {
2546
- const lines = ["---"];
2547
- if (opts.description) {
2548
- lines.push(`description: ${opts.description}`);
2549
- }
2550
- if (opts.globs && opts.globs.length > 0) {
2551
- lines.push(`paths: ${opts.globs.join(", ")}`);
2625
+ fileWatcher: {
2626
+ name: "File Watcher",
2627
+ description: "Exclude directories from file watcher",
2628
+ settings: {
2629
+ "files.watcherExclude": {
2630
+ "**/node_modules/**": true,
2631
+ "**/.git/**": true,
2632
+ "**/vendor/**": true,
2633
+ "**/dist/**": true,
2634
+ "**/build/**": true,
2635
+ "**/.next/**": true,
2636
+ "**/out/**": true,
2637
+ "**/.nuxt/**": true,
2638
+ "**/.cache/**": true,
2639
+ "**/coverage/**": true
2640
+ },
2641
+ "files.exclude": {
2642
+ "**/.git": true,
2643
+ "**/.DS_Store": true,
2644
+ "**/node_modules": false
2645
+ // Keep visible but don't watch
2552
2646
  }
2553
- lines.push("---");
2554
- return lines.join("\n");
2555
2647
  }
2556
2648
  },
2557
- opencode: {
2558
- id: "opencode",
2559
- name: "OpenCode",
2560
- icon: "\u{1F4BB}",
2561
- basePath: ".opencode",
2562
- rulesPath: "rules",
2563
- workflowsPath: "workflows",
2564
- commandsPath: null,
2565
- fileExtension: ".md",
2566
- generateFrontmatter: (opts) => {
2567
- const trigger = opts.alwaysApply ? "always" : "glob";
2568
- return `---
2569
- trigger: ${trigger}
2570
- ---`;
2649
+ search: {
2650
+ name: "Search",
2651
+ description: "Exclude directories from search",
2652
+ settings: {
2653
+ "search.exclude": {
2654
+ "**/node_modules": true,
2655
+ "**/bower_components": true,
2656
+ "**/*.code-search": true,
2657
+ "**/dist": true,
2658
+ "**/build": true,
2659
+ "**/out": true,
2660
+ "**/.next": true,
2661
+ "**/.nuxt": true,
2662
+ "**/coverage": true,
2663
+ "**/.cache": true
2664
+ },
2665
+ "search.followSymlinks": false
2571
2666
  }
2572
- }
2573
- };
2574
- function getMigrationIDEs() {
2575
- return Object.keys(IDE_MIGRATION_CONFIGS);
2576
- }
2577
-
2578
- // src/services/migrate-ide.service.ts
2579
- var MigrateIdeService = class {
2580
- projectPath;
2581
- jai1Path;
2582
- constructor(projectPath = process.cwd()) {
2583
- this.projectPath = projectPath;
2584
- this.jai1Path = path3.join(projectPath, ".jai1");
2585
- }
2586
- /**
2587
- * Scan .jai1/ directory for content
2588
- */
2589
- async scanJai1Content() {
2590
- const manualRules = await this.scanContentType("rules");
2591
- const presetRules = await this.scanRulePreset();
2592
- const rules = [...presetRules, ...manualRules];
2593
- const workflows = await this.scanContentType("workflows");
2594
- const commands = [];
2595
- return {
2596
- rules,
2597
- workflows,
2598
- commands,
2599
- totalCount: rules.length + workflows.length + commands.length
2600
- };
2601
- }
2602
- /**
2603
- * Scan .jai1/rule-preset/ directory for rule preset files
2604
- */
2605
- async scanRulePreset() {
2606
- const items = [];
2607
- const presetDir = path3.join(this.jai1Path, "rule-preset");
2608
- try {
2609
- await fs6.access(presetDir);
2610
- } catch {
2611
- return items;
2667
+ },
2668
+ extensions: {
2669
+ name: "Extensions",
2670
+ description: "Disable auto-update extensions",
2671
+ settings: {
2672
+ "extensions.autoUpdate": false,
2673
+ "extensions.autoCheckUpdates": false,
2674
+ "update.mode": "manual",
2675
+ "extensions.ignoreRecommendations": true
2676
+ }
2677
+ },
2678
+ editorRendering: {
2679
+ name: "Editor Rendering",
2680
+ description: "Optimize rendering (minimap, whitespace, highlights...)",
2681
+ settings: {
2682
+ "editor.minimap.enabled": false,
2683
+ "editor.renderWhitespace": "none",
2684
+ "editor.renderControlCharacters": false,
2685
+ "editor.renderLineHighlight": "none",
2686
+ "editor.cursorBlinking": "solid",
2687
+ "editor.smoothScrolling": false,
2688
+ "editor.largeFileOptimizations": true,
2689
+ "editor.maxTokenizationLineLength": 2e3,
2690
+ "editor.quickSuggestions": {
2691
+ "other": true,
2692
+ "comments": false,
2693
+ "strings": false
2694
+ },
2695
+ "editor.suggest.showStatusBar": false,
2696
+ "editor.hover.delay": 500,
2697
+ "editor.matchBrackets": "never"
2612
2698
  }
2613
- const files = await fs6.readdir(presetDir);
2614
- for (const file of files) {
2615
- if (!file.endsWith(".mdc")) continue;
2616
- const filepath = path3.join(presetDir, file);
2617
- const stat = await fs6.stat(filepath);
2618
- if (!stat.isFile()) continue;
2619
- const content = await fs6.readFile(filepath, "utf-8");
2620
- let frontmatter = {};
2621
- try {
2622
- const { data } = matter2(content);
2623
- frontmatter = data;
2624
- } catch {
2625
- }
2626
- items.push({
2627
- type: "rules",
2628
- name: path3.basename(file, ".mdc"),
2629
- filepath,
2630
- relativePath: path3.relative(this.projectPath, filepath),
2631
- description: frontmatter.description,
2632
- globs: this.extractGlobs(frontmatter),
2633
- alwaysApply: this.extractAlwaysApply(frontmatter)
2634
- });
2699
+ },
2700
+ uiElements: {
2701
+ name: "UI Elements",
2702
+ description: "Hide unnecessary UI elements",
2703
+ settings: {
2704
+ "workbench.activityBar.visible": false,
2705
+ "workbench.statusBar.visible": false,
2706
+ "breadcrumbs.enabled": false,
2707
+ "window.menuBarVisibility": "compact",
2708
+ "workbench.editor.showTabs": true,
2709
+ // Keep tabs for navigation
2710
+ "workbench.startupEditor": "none"
2635
2711
  }
2636
- return items;
2637
2712
  }
2638
- /**
2639
- * Scan a specific content type
2640
- */
2641
- async scanContentType(type) {
2642
- const items = [];
2643
- const dirPath = path3.join(this.jai1Path, type);
2644
- try {
2645
- await fs6.access(dirPath);
2646
- } catch {
2647
- return items;
2713
+ };
2714
+ function createSetupSubcommand() {
2715
+ const setupCommand = new Command7("setup").description("Configure IDE settings and optimizations");
2716
+ setupCommand.action(async () => {
2717
+ await interactiveMode();
2718
+ });
2719
+ setupCommand.command("enable").description("Enable specific optimization groups").argument("[groups...]", "Group names to enable (telemetry, languageServers, git, ...)").action(async (groups) => {
2720
+ if (groups.length === 0) {
2721
+ await selectGroupsToApply("enable");
2722
+ } else {
2723
+ await applyGroups(groups, "enable");
2648
2724
  }
2649
- const files = await fs6.readdir(dirPath);
2650
- for (const file of files) {
2651
- if (!file.endsWith(".md")) continue;
2652
- const filepath = path3.join(dirPath, file);
2653
- const stat = await fs6.stat(filepath);
2654
- if (!stat.isFile()) continue;
2655
- const content = await fs6.readFile(filepath, "utf-8");
2656
- const { data: frontmatter } = matter2(content);
2657
- items.push({
2658
- type,
2659
- name: path3.basename(file, ".md"),
2660
- filepath,
2661
- relativePath: path3.relative(this.projectPath, filepath),
2662
- description: frontmatter.description,
2663
- globs: this.extractGlobs(frontmatter),
2664
- alwaysApply: this.extractAlwaysApply(frontmatter)
2665
- });
2725
+ });
2726
+ setupCommand.command("disable").description("Disable specific optimization groups (restore to default)").argument("[groups...]", "Group names to disable").action(async (groups) => {
2727
+ if (groups.length === 0) {
2728
+ await selectGroupsToApply("disable");
2729
+ } else {
2730
+ await applyGroups(groups, "disable");
2666
2731
  }
2667
- return items;
2732
+ });
2733
+ setupCommand.command("max-performance").description("Enable all optimizations for maximum performance").action(async () => {
2734
+ const allGroups = Object.keys(PERFORMANCE_GROUPS);
2735
+ await applyGroups(allGroups, "enable");
2736
+ });
2737
+ setupCommand.command("reset").description("Reset to default settings (remove all optimizations)").argument("[groups...]", "Group names to reset (empty = reset all)").action(async (groups) => {
2738
+ await resetSettings(groups);
2739
+ });
2740
+ setupCommand.command("list").description("List all available optimization groups").action(() => {
2741
+ console.log("\u{1F527} Available optimization groups:\n");
2742
+ Object.entries(PERFORMANCE_GROUPS).forEach(([key, group]) => {
2743
+ console.log(` ${key.padEnd(20)} - ${group.name}`);
2744
+ console.log(` ${" ".repeat(20)} ${group.description}`);
2745
+ console.log();
2746
+ });
2747
+ });
2748
+ return setupCommand;
2749
+ }
2750
+ async function interactiveMode() {
2751
+ console.log("\u{1F527} IDE Settings Manager\n");
2752
+ console.log("\u256D\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\u256E");
2753
+ console.log("\u2502 \u{1F4CC} Usage: \u2502");
2754
+ console.log("\u2502 \u2022 Use \u2191\u2193 keys to navigate \u2502");
2755
+ console.log("\u2502 \u2022 Press SPACE to select/deselect group \u2502");
2756
+ console.log("\u2502 \u2022 Press ENTER to confirm and apply \u2502");
2757
+ console.log("\u2502 \u2022 Press Ctrl+C to cancel \u2502");
2758
+ 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");
2759
+ const action = await select({
2760
+ message: "What do you want to do?",
2761
+ choices: [
2762
+ { name: "\u2705 Enable optimization groups", value: "enable" },
2763
+ { name: "\u274C Disable optimization groups", value: "disable" },
2764
+ { name: "\u{1F680} Max Performance (enable all)", value: "max" },
2765
+ { name: "\u{1F504} Reset to defaults", value: "reset" }
2766
+ ]
2767
+ });
2768
+ if (action === "max") {
2769
+ const allGroups = Object.keys(PERFORMANCE_GROUPS);
2770
+ await applyGroups(allGroups, "enable");
2771
+ } else if (action === "reset") {
2772
+ await resetSettings([]);
2773
+ } else {
2774
+ await selectGroupsToApply(action);
2668
2775
  }
2669
- /**
2670
- * Generate stub file content with @ reference
2671
- */
2672
- generateStubContent(ide, sourceItem) {
2673
- const config = IDE_MIGRATION_CONFIGS[ide];
2674
- if (!config) throw new Error(`Unknown IDE: ${ide}`);
2675
- const frontmatter = config.generateFrontmatter({
2676
- description: sourceItem.description,
2677
- globs: sourceItem.globs,
2678
- alwaysApply: sourceItem.alwaysApply,
2679
- sourceFile: sourceItem.relativePath
2776
+ }
2777
+ async function selectGroupsToApply(action) {
2778
+ const choices = Object.entries(PERFORMANCE_GROUPS).map(([key, group]) => ({
2779
+ name: `${group.name} - ${group.description}`,
2780
+ value: key
2781
+ }));
2782
+ try {
2783
+ const selectedGroups = await checkbox({
2784
+ message: `Select groups to ${action} (SPACE to select, ENTER to confirm):`,
2785
+ choices
2680
2786
  });
2681
- const reference = `@${sourceItem.relativePath}`;
2682
- if (frontmatter) {
2683
- return `${frontmatter}
2684
-
2685
- ${reference}
2686
- `;
2787
+ if (selectedGroups.length === 0) {
2788
+ console.log("\n\u26A0\uFE0F No groups selected!");
2789
+ console.log(" \u{1F4A1} Tip: Press SPACE to select at least 1 group before pressing ENTER.");
2790
+ return;
2687
2791
  }
2688
- return `${reference}
2689
- `;
2792
+ await applyGroups(selectedGroups, action);
2793
+ } catch {
2794
+ console.log("\n\u274C Operation cancelled.");
2690
2795
  }
2691
- /**
2692
- * Migrate content to specific IDEs
2693
- */
2694
- async migrate(ides, contentTypes, content, onProgress) {
2695
- const results = [];
2696
- for (const ide of ides) {
2697
- const config = IDE_MIGRATION_CONFIGS[ide];
2698
- if (!config) continue;
2699
- for (const type of contentTypes) {
2700
- const items = type === "rules" ? content.rules : type === "workflows" ? content.workflows : content.commands;
2701
- for (const item of items) {
2702
- const result = await this.migrateItem(ide, config, item);
2703
- results.push(result);
2704
- onProgress?.(result);
2705
- }
2706
- }
2707
- }
2708
- return results;
2796
+ }
2797
+ async function applyGroups(groupKeys, action) {
2798
+ const vscodeDir = path3.join(process.cwd(), ".vscode");
2799
+ const settingsPath = path3.join(vscodeDir, "settings.json");
2800
+ const invalidGroups = groupKeys.filter((key) => !PERFORMANCE_GROUPS[key]);
2801
+ if (invalidGroups.length > 0) {
2802
+ console.log(`
2803
+ \u274C Invalid groups: ${invalidGroups.join(", ")}`);
2804
+ console.log(' \u{1F4A1} Run "jai1 ide setup list" to see available groups.');
2805
+ return;
2709
2806
  }
2710
- /**
2711
- * Migrate a single item
2712
- */
2713
- async migrateItem(ide, config, item) {
2807
+ if (!existsSync(vscodeDir)) {
2808
+ await fs6.mkdir(vscodeDir, { recursive: true });
2809
+ console.log("\u{1F4C1} Created .vscode/ directory");
2810
+ }
2811
+ let currentSettings = {};
2812
+ if (existsSync(settingsPath)) {
2714
2813
  try {
2715
- const targetPath = this.getTargetPath(config, item);
2716
- if (!targetPath) {
2717
- return {
2718
- source: item,
2719
- targetIDE: ide,
2720
- targetPath: "",
2721
- status: "skipped",
2722
- error: `IDE ${config.id} does not support ${item.type}`
2723
- };
2724
- }
2725
- const targetDir = path3.dirname(targetPath);
2726
- await fs6.mkdir(targetDir, { recursive: true });
2727
- let status = "created";
2728
- try {
2729
- await fs6.access(targetPath);
2730
- status = "updated";
2731
- } catch {
2814
+ const content = await fs6.readFile(settingsPath, "utf-8");
2815
+ currentSettings = JSON.parse(content);
2816
+ console.log("\u{1F4C4} Read current settings from settings.json");
2817
+ } catch {
2818
+ console.warn("\u26A0\uFE0F Cannot read settings.json (may contain comments).");
2819
+ const confirmOverwrite = await confirm2({
2820
+ message: "Overwrite current settings.json file?",
2821
+ default: false
2822
+ });
2823
+ if (!confirmOverwrite) {
2824
+ console.log("\u274C Operation cancelled.");
2825
+ return;
2732
2826
  }
2733
- const stubContent = this.generateStubContent(ide, item);
2734
- await fs6.writeFile(targetPath, stubContent, "utf-8");
2735
- return {
2736
- source: item,
2737
- targetIDE: ide,
2738
- targetPath,
2739
- status
2740
- };
2741
- } catch (error) {
2742
- return {
2743
- source: item,
2744
- targetIDE: ide,
2745
- targetPath: "",
2746
- status: "error",
2747
- error: error instanceof Error ? error.message : "Unknown error"
2748
- };
2827
+ currentSettings = {};
2749
2828
  }
2750
2829
  }
2751
- /**
2752
- * Get target file path for an item
2753
- */
2754
- getTargetPath(config, item) {
2755
- let contentPath = null;
2756
- switch (item.type) {
2757
- case "rules":
2758
- contentPath = config.rulesPath;
2759
- break;
2760
- case "workflows":
2761
- contentPath = config.workflowsPath ?? config.commandsPath;
2762
- break;
2763
- case "commands":
2764
- contentPath = config.commandsPath;
2765
- break;
2766
- }
2767
- if (!contentPath) {
2768
- return null;
2830
+ const newSettings = { ...currentSettings };
2831
+ console.log(`
2832
+ \u{1F4DD} ${action === "enable" ? "Enabling" : "Disabling"} groups:
2833
+ `);
2834
+ for (const key of groupKeys) {
2835
+ const group = PERFORMANCE_GROUPS[key];
2836
+ console.log(` ${action === "enable" ? "\u2713" : "\u2717"} ${group.name}`);
2837
+ if (action === "enable") {
2838
+ for (const [settingKey, settingValue] of Object.entries(group.settings)) {
2839
+ if (typeof settingValue === "object" && settingValue !== null && !Array.isArray(settingValue) && typeof newSettings[settingKey] === "object" && newSettings[settingKey] !== null) {
2840
+ newSettings[settingKey] = {
2841
+ ...newSettings[settingKey],
2842
+ ...settingValue
2843
+ };
2844
+ } else {
2845
+ newSettings[settingKey] = settingValue;
2846
+ }
2847
+ }
2848
+ } else {
2849
+ for (const settingKey of Object.keys(group.settings)) {
2850
+ delete newSettings[settingKey];
2851
+ }
2769
2852
  }
2770
- const filename = `${item.name}${config.fileExtension}`;
2771
- return path3.join(this.projectPath, config.basePath, contentPath, filename);
2772
2853
  }
2773
- // Helper methods
2774
- extractGlobs(frontmatter) {
2775
- const globs = frontmatter.globs;
2776
- if (typeof globs === "string") return [globs];
2777
- if (Array.isArray(globs)) return globs;
2778
- return void 0;
2854
+ await fs6.writeFile(settingsPath, JSON.stringify(newSettings, null, 2));
2855
+ console.log(`
2856
+ \u2705 Updated IDE settings at: ${settingsPath}`);
2857
+ console.log("\u{1F4A1} Tip: Restart your IDE to apply changes.");
2858
+ }
2859
+ async function resetSettings(groupKeys) {
2860
+ const vscodeDir = path3.join(process.cwd(), ".vscode");
2861
+ const settingsPath = path3.join(vscodeDir, "settings.json");
2862
+ if (!existsSync(settingsPath)) {
2863
+ console.log("\n\u26A0\uFE0F No settings.json file found");
2864
+ return;
2779
2865
  }
2780
- extractAlwaysApply(frontmatter) {
2781
- return frontmatter.alwaysApply === true || frontmatter.trigger === "always" || frontmatter.trigger === "always_on";
2866
+ const confirmReset = await confirm2({
2867
+ message: groupKeys.length === 0 ? "Reset ALL settings to default (delete entire file)?" : `Reset groups: ${groupKeys.join(", ")}?`,
2868
+ default: false
2869
+ });
2870
+ if (!confirmReset) {
2871
+ console.log("\u274C Operation cancelled.");
2872
+ return;
2782
2873
  }
2783
- };
2874
+ if (groupKeys.length === 0) {
2875
+ await fs6.unlink(settingsPath);
2876
+ console.log("\n\u2705 Deleted settings.json file");
2877
+ } else {
2878
+ await applyGroups(groupKeys, "disable");
2879
+ }
2880
+ console.log("\u{1F4A1} Tip: Restart your IDE to apply changes.");
2881
+ }
2784
2882
 
2785
2883
  // src/commands/ide/sync.ts
2884
+ import { Command as Command8 } from "commander";
2885
+ import { checkbox as checkbox2, confirm as confirm3 } from "@inquirer/prompts";
2786
2886
  function createSyncSubcommand() {
2787
2887
  const cmd = new Command8("sync").description("Sync .jai1 content to IDE directories (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) => {
2788
2888
  await runSync(options);
@@ -5414,11 +5514,266 @@ function createTranslateCommand() {
5414
5514
  return cmd;
5415
5515
  }
5416
5516
 
5517
+ // src/commands/image/index.ts
5518
+ import { Command as Command20 } from "commander";
5519
+
5520
+ // src/commands/image/gen.ts
5521
+ import { Command as Command16 } from "commander";
5522
+
5523
+ // src/services/image.service.ts
5524
+ var ImageService = class {
5525
+ /**
5526
+ * Generate image from prompt
5527
+ */
5528
+ async generate(config, options) {
5529
+ const response = await fetch(`${config.apiUrl}/api/image/generate`, {
5530
+ method: "POST",
5531
+ headers: {
5532
+ "Content-Type": "application/json",
5533
+ "JAI1-Access-Key": config.accessKey
5534
+ },
5535
+ body: JSON.stringify(options)
5536
+ });
5537
+ if (!response.ok) {
5538
+ let errorMessage = `HTTP ${response.status}`;
5539
+ try {
5540
+ const error = await response.json();
5541
+ errorMessage = error.message || errorMessage;
5542
+ } catch (e) {
5543
+ }
5544
+ throw new NetworkError(errorMessage);
5545
+ }
5546
+ const data = await response.json();
5547
+ return data.data;
5548
+ }
5549
+ /**
5550
+ * List generated images
5551
+ */
5552
+ async list(config, options) {
5553
+ const params = new URLSearchParams();
5554
+ if (options.model) params.set("model", options.model);
5555
+ if (options.limit) params.set("limit", options.limit.toString());
5556
+ if (options.offset) params.set("offset", options.offset.toString());
5557
+ const url = `${config.apiUrl}/api/image/list?${params}`;
5558
+ const response = await fetch(url, {
5559
+ headers: {
5560
+ "JAI1-Access-Key": config.accessKey
5561
+ }
5562
+ });
5563
+ if (!response.ok) {
5564
+ throw new NetworkError(`Failed to list images: HTTP ${response.status}`);
5565
+ }
5566
+ const data = await response.json();
5567
+ return data.data;
5568
+ }
5569
+ /**
5570
+ * Get image info
5571
+ */
5572
+ async getInfo(config, id) {
5573
+ const response = await fetch(`${config.apiUrl}/api/image/${id}`, {
5574
+ headers: {
5575
+ "JAI1-Access-Key": config.accessKey
5576
+ }
5577
+ });
5578
+ if (!response.ok) {
5579
+ if (response.status === 404) {
5580
+ throw new Error("Image not found");
5581
+ }
5582
+ throw new NetworkError(`Failed to get image: HTTP ${response.status}`);
5583
+ }
5584
+ const data = await response.json();
5585
+ return data.data;
5586
+ }
5587
+ /**
5588
+ * Delete image
5589
+ */
5590
+ async delete(config, id) {
5591
+ const response = await fetch(`${config.apiUrl}/api/image/${id}`, {
5592
+ method: "DELETE",
5593
+ headers: {
5594
+ "JAI1-Access-Key": config.accessKey
5595
+ }
5596
+ });
5597
+ if (!response.ok) {
5598
+ if (response.status === 404) {
5599
+ throw new Error("Image not found");
5600
+ }
5601
+ throw new NetworkError(`Failed to delete image: HTTP ${response.status}`);
5602
+ }
5603
+ }
5604
+ };
5605
+
5606
+ // src/commands/image/gen.ts
5607
+ import terminalImage from "terminal-image";
5608
+ function createImageGenCommand() {
5609
+ return new Command16("gen").description("Generate an image from text prompt").argument("<prompt>", "Image description prompt").option("-m, --model <model>", "Model to use", "gemini-3-flash-preview").option("-w, --width <number>", "Image width").option("-h, --height <number>", "Image height").option("-r, --ratio <ratio>", "Aspect ratio (1:1, 16:9, 9:16, 4:3, 3:4)").option("--negative <prompt>", "Negative prompt").option("--seed <number>", "Random seed").option("--no-display", "Don't display image in terminal").action(async (prompt, options) => {
5610
+ const configService = new ConfigService();
5611
+ const config = await configService.load();
5612
+ if (!config) {
5613
+ throw new ValidationError('Not initialized. Run "jai1 auth" first.');
5614
+ }
5615
+ const imageService = new ImageService();
5616
+ console.log("\u{1F3A8} Generating image...");
5617
+ console.log(` Prompt: ${prompt}`);
5618
+ console.log(` Model: ${options.model}`);
5619
+ try {
5620
+ const result = await imageService.generate(config, {
5621
+ prompt,
5622
+ model: options.model,
5623
+ ...options.width && { width: parseInt(options.width) },
5624
+ ...options.height && { height: parseInt(options.height) },
5625
+ ...options.ratio && { aspectRatio: options.ratio },
5626
+ ...options.negative && { negativePrompt: options.negative },
5627
+ ...options.seed && { seed: parseInt(options.seed) }
5628
+ });
5629
+ console.log("\n\u2705 Image generated successfully!");
5630
+ console.log(` URL: ${result.url}`);
5631
+ console.log(` Size: ${(result.contentSize / 1024).toFixed(2)} KB`);
5632
+ console.log(` ID: ${result.id}`);
5633
+ if (options.display !== false) {
5634
+ try {
5635
+ const imageResponse = await fetch(result.url);
5636
+ const arrayBuffer = await imageResponse.arrayBuffer();
5637
+ const imageBuffer = Buffer.from(arrayBuffer);
5638
+ const rendered = await terminalImage.buffer(imageBuffer, {
5639
+ width: "50%",
5640
+ height: "50%"
5641
+ });
5642
+ console.log("\n" + rendered);
5643
+ } catch (displayError) {
5644
+ }
5645
+ }
5646
+ console.log('\n\u{1F4A1} Tip: Use "jai1 image list" to view all your images');
5647
+ console.log(' Use "jai1 image info <id>" for details');
5648
+ } catch (error) {
5649
+ if (error instanceof Error) {
5650
+ if (error.message.includes("Rate limit")) {
5651
+ console.error("\n\u26A0\uFE0F Rate limit exceeded");
5652
+ console.error(' Use "jai1 image limits" to check your quota');
5653
+ } else {
5654
+ throw error;
5655
+ }
5656
+ }
5657
+ throw error;
5658
+ }
5659
+ });
5660
+ }
5661
+
5662
+ // src/commands/image/list.ts
5663
+ import { Command as Command17 } from "commander";
5664
+ function createImageListCommand() {
5665
+ return new Command17("list").description("List generated images").option("-m, --model <model>", "Filter by model").option("-l, --limit <number>", "Number of results", "20").option("-o, --offset <number>", "Offset for pagination", "0").action(async (options) => {
5666
+ const configService = new ConfigService();
5667
+ const config = await configService.load();
5668
+ if (!config) {
5669
+ throw new ValidationError('Not initialized. Run "jai1 auth" first.');
5670
+ }
5671
+ const imageService = new ImageService();
5672
+ const result = await imageService.list(config, {
5673
+ model: options.model,
5674
+ limit: parseInt(options.limit),
5675
+ offset: parseInt(options.offset)
5676
+ });
5677
+ if (result.images.length === 0) {
5678
+ console.log("No images found.");
5679
+ return;
5680
+ }
5681
+ console.log(`
5682
+ \u{1F4F8} Your Generated Images (${result.total} total):
5683
+ `);
5684
+ for (const img of result.images) {
5685
+ const date = new Date(img.createdAt).toLocaleString();
5686
+ const promptPreview = img.prompt.length > 50 ? img.prompt.slice(0, 50) + "..." : img.prompt;
5687
+ console.log(`ID: ${img.id.toString().padEnd(6)} | ${date}`);
5688
+ console.log(` Model: ${img.model}`);
5689
+ console.log(` Prompt: ${promptPreview}`);
5690
+ console.log(` URL: ${img.url}`);
5691
+ console.log();
5692
+ }
5693
+ if (result.total > result.offset + result.limit) {
5694
+ const nextOffset = result.offset + result.limit;
5695
+ console.log(`\u{1F4A1} Show more: jai1 image list -o ${nextOffset}`);
5696
+ }
5697
+ });
5698
+ }
5699
+
5700
+ // src/commands/image/info.ts
5701
+ import { Command as Command18 } from "commander";
5702
+ function createImageInfoCommand() {
5703
+ return new Command18("info").description("Get detailed info about an image").argument("<id>", "Image ID").action(async (id) => {
5704
+ const configService = new ConfigService();
5705
+ const config = await configService.load();
5706
+ if (!config) {
5707
+ throw new ValidationError('Not initialized. Run "jai1 auth" first.');
5708
+ }
5709
+ const imageService = new ImageService();
5710
+ const image = await imageService.getInfo(config, parseInt(id));
5711
+ console.log("\n\u{1F4F8} Image Details:\n");
5712
+ console.log(`ID: ${image.id}`);
5713
+ console.log(`Model: ${image.model}`);
5714
+ console.log(`Prompt: ${image.prompt}`);
5715
+ console.log(`URL: ${image.url}`);
5716
+ console.log(`Size: ${(image.contentSize / 1024).toFixed(2)} KB`);
5717
+ if (image.width && image.height) {
5718
+ console.log(`Dimensions: ${image.width}x${image.height}`);
5719
+ }
5720
+ console.log(`Created: ${new Date(image.createdAt).toLocaleString()}`);
5721
+ if (image.metadata) {
5722
+ const meta = typeof image.metadata === "string" ? JSON.parse(image.metadata) : image.metadata;
5723
+ if (meta.aspectRatio) {
5724
+ console.log(`Ratio: ${meta.aspectRatio}`);
5725
+ }
5726
+ if (meta.negativePrompt) {
5727
+ console.log(`Negative: ${meta.negativePrompt}`);
5728
+ }
5729
+ if (meta.seed) {
5730
+ console.log(`Seed: ${meta.seed}`);
5731
+ }
5732
+ }
5733
+ });
5734
+ }
5735
+
5736
+ // src/commands/image/delete.ts
5737
+ import { Command as Command19 } from "commander";
5738
+ import { confirm as confirm4 } from "@inquirer/prompts";
5739
+ function createImageDeleteCommand() {
5740
+ return new Command19("delete").description("Delete a generated image").argument("<id>", "Image ID").option("-y, --yes", "Skip confirmation").action(async (id, options) => {
5741
+ const configService = new ConfigService();
5742
+ const config = await configService.load();
5743
+ if (!config) {
5744
+ throw new ValidationError('Not initialized. Run "jai1 auth" first.');
5745
+ }
5746
+ if (!options.yes) {
5747
+ const confirmed = await confirm4({
5748
+ message: `Are you sure you want to delete image #${id}?`,
5749
+ default: false
5750
+ });
5751
+ if (!confirmed) {
5752
+ console.log("Cancelled.");
5753
+ return;
5754
+ }
5755
+ }
5756
+ const imageService = new ImageService();
5757
+ await imageService.delete(config, parseInt(id));
5758
+ console.log(`\u2705 Image #${id} deleted successfully.`);
5759
+ });
5760
+ }
5761
+
5762
+ // src/commands/image/index.ts
5763
+ function createImageCommand() {
5764
+ const cmd = new Command20("image").description("Image generation commands (Coming Soon)");
5765
+ cmd.addCommand(createImageGenCommand());
5766
+ cmd.addCommand(createImageListCommand());
5767
+ cmd.addCommand(createImageInfoCommand());
5768
+ cmd.addCommand(createImageDeleteCommand());
5769
+ return cmd;
5770
+ }
5771
+
5417
5772
  // src/commands/utils/index.ts
5418
- import { Command as Command29 } from "commander";
5773
+ import { Command as Command34 } from "commander";
5419
5774
 
5420
5775
  // src/commands/utils/password.ts
5421
- import { Command as Command16 } from "commander";
5776
+ import { Command as Command21 } from "commander";
5422
5777
 
5423
5778
  // src/services/utils.service.ts
5424
5779
  import crypto from "crypto";
@@ -5687,7 +6042,7 @@ async function handlePasswordGeneration(options) {
5687
6042
  }
5688
6043
  }
5689
6044
  function createPasswordCommand() {
5690
- 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", `
6045
+ const cmd = new Command21("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", `
5691
6046
  Examples:
5692
6047
  $ jai1 utils password
5693
6048
  $ jai1 utils password --length 24
@@ -5705,7 +6060,7 @@ Examples:
5705
6060
  }
5706
6061
 
5707
6062
  // src/commands/utils/uuid.ts
5708
- import { Command as Command17 } from "commander";
6063
+ import { Command as Command22 } from "commander";
5709
6064
  async function handleUuidGeneration(options) {
5710
6065
  const service = new UtilsService();
5711
6066
  try {
@@ -5733,7 +6088,7 @@ async function handleUuidGeneration(options) {
5733
6088
  }
5734
6089
  }
5735
6090
  function createUuidCommand() {
5736
- 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", `
6091
+ const cmd = new Command22("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", `
5737
6092
  Examples:
5738
6093
  $ jai1 utils uuid
5739
6094
  $ jai1 utils uuid --count 10
@@ -5750,7 +6105,7 @@ Examples:
5750
6105
  }
5751
6106
 
5752
6107
  // src/commands/utils/hash.ts
5753
- import { Command as Command18 } from "commander";
6108
+ import { Command as Command23 } from "commander";
5754
6109
  async function handleHashGeneration(input4, options) {
5755
6110
  const service = new UtilsService();
5756
6111
  try {
@@ -5790,7 +6145,7 @@ async function handleHashGeneration(input4, options) {
5790
6145
  }
5791
6146
  }
5792
6147
  function createHashCommand() {
5793
- const cmd = new Command18("hash").description("Generate hash (MD5, SHA, bcrypt)").argument("[input]", "Text to hash").option(
6148
+ const cmd = new Command23("hash").description("Generate hash (MD5, SHA, bcrypt)").argument("[input]", "Text to hash").option(
5794
6149
  "-a, --algorithm <algorithm>",
5795
6150
  "Hash algorithm (md5, sha1, sha256, sha512, bcrypt)",
5796
6151
  "sha256"
@@ -5812,7 +6167,7 @@ Examples:
5812
6167
  }
5813
6168
 
5814
6169
  // src/commands/utils/base64-encode.ts
5815
- import { Command as Command19 } from "commander";
6170
+ import { Command as Command24 } from "commander";
5816
6171
  import { readFile as readFile2 } from "fs/promises";
5817
6172
  async function handleBase64Encode(input4, options) {
5818
6173
  const service = new UtilsService();
@@ -5845,7 +6200,7 @@ async function handleBase64Encode(input4, options) {
5845
6200
  }
5846
6201
  }
5847
6202
  function createBase64EncodeCommand() {
5848
- 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", `
6203
+ const cmd = new Command24("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", `
5849
6204
  Examples:
5850
6205
  $ jai1 utils base64-encode "hello world"
5851
6206
  $ jai1 utils base64-encode "hello world" --url-safe
@@ -5858,7 +6213,7 @@ Examples:
5858
6213
  }
5859
6214
 
5860
6215
  // src/commands/utils/base64-decode.ts
5861
- import { Command as Command20 } from "commander";
6216
+ import { Command as Command25 } from "commander";
5862
6217
  import { readFile as readFile3, writeFile } from "fs/promises";
5863
6218
  async function handleBase64Decode(input4, options) {
5864
6219
  const service = new UtilsService();
@@ -5890,7 +6245,7 @@ async function handleBase64Decode(input4, options) {
5890
6245
  }
5891
6246
  }
5892
6247
  function createBase64DecodeCommand() {
5893
- 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", `
6248
+ const cmd = new Command25("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", `
5894
6249
  Examples:
5895
6250
  $ jai1 utils base64-decode "aGVsbG8gd29ybGQ="
5896
6251
  $ jai1 utils base64-decode --file ./encoded.txt
@@ -5903,7 +6258,7 @@ Examples:
5903
6258
  }
5904
6259
 
5905
6260
  // src/commands/utils/http.ts
5906
- import { Command as Command21 } from "commander";
6261
+ import { Command as Command26 } from "commander";
5907
6262
  import { readFile as readFile4 } from "fs/promises";
5908
6263
  async function handleHttpRequest(url, options) {
5909
6264
  const service = new UtilsService();
@@ -5969,7 +6324,7 @@ async function handleHttpRequest(url, options) {
5969
6324
  }
5970
6325
  }
5971
6326
  function createHttpCommand() {
5972
- 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", `
6327
+ const cmd = new Command26("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", `
5973
6328
  Examples:
5974
6329
  $ jai1 utils http https://api.example.com/users
5975
6330
  $ jai1 utils http https://api.example.com/users -X POST -d '{"name":"John"}'
@@ -5987,7 +6342,7 @@ Examples:
5987
6342
  }
5988
6343
 
5989
6344
  // src/commands/utils/jwt.ts
5990
- import { Command as Command22 } from "commander";
6345
+ import { Command as Command27 } from "commander";
5991
6346
  async function handleJwtDecode(token) {
5992
6347
  const service = new UtilsService();
5993
6348
  try {
@@ -6026,7 +6381,7 @@ async function handleJwtEncode(options) {
6026
6381
  }
6027
6382
  }
6028
6383
  function createJwtCommand() {
6029
- const jwtCommand = new Command22("jwt").description("Decode and encode JWT tokens");
6384
+ const jwtCommand = new Command27("jwt").description("Decode and encode JWT tokens");
6030
6385
  jwtCommand.command("decode").description("Decode JWT token").argument("<token>", "JWT token to decode").addHelpText("after", `
6031
6386
  Examples:
6032
6387
  $ jai1 utils jwt decode "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
@@ -6047,7 +6402,7 @@ Examples:
6047
6402
  }
6048
6403
 
6049
6404
  // src/commands/utils/unix-time.ts
6050
- import { Command as Command23 } from "commander";
6405
+ import { Command as Command28 } from "commander";
6051
6406
  async function handleUnixTime(input4, options) {
6052
6407
  const service = new UtilsService();
6053
6408
  try {
@@ -6095,7 +6450,7 @@ async function handleUnixTime(input4, options) {
6095
6450
  }
6096
6451
  }
6097
6452
  function createUnixTimeCommand() {
6098
- 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", `
6453
+ const cmd = new Command28("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", `
6099
6454
  Examples:
6100
6455
  $ jai1 utils unix-time
6101
6456
  $ jai1 utils unix-time 1702550400
@@ -6110,7 +6465,7 @@ Examples:
6110
6465
  }
6111
6466
 
6112
6467
  // src/commands/utils/timezone.ts
6113
- import { Command as Command24 } from "commander";
6468
+ import { Command as Command29 } from "commander";
6114
6469
  async function handleTimezoneConversion(time, options) {
6115
6470
  const service = new UtilsService();
6116
6471
  try {
@@ -6147,7 +6502,7 @@ async function handleTimezoneConversion(time, options) {
6147
6502
  }
6148
6503
  }
6149
6504
  function createTimezoneCommand() {
6150
- 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", `
6505
+ const cmd = new Command29("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", `
6151
6506
  Examples:
6152
6507
  $ jai1 utils timezone --list
6153
6508
  $ jai1 utils timezone "2024-01-15 10:00" --from "America/New_York" --to "Asia/Tokyo"
@@ -6160,7 +6515,7 @@ Examples:
6160
6515
  }
6161
6516
 
6162
6517
  // src/commands/utils/url-encode.ts
6163
- import { Command as Command25 } from "commander";
6518
+ import { Command as Command30 } from "commander";
6164
6519
  async function handleUrlEncode(input4, options) {
6165
6520
  const service = new UtilsService();
6166
6521
  try {
@@ -6181,7 +6536,7 @@ async function handleUrlEncode(input4, options) {
6181
6536
  }
6182
6537
  }
6183
6538
  function createUrlEncodeCommand() {
6184
- 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", `
6539
+ const cmd = new Command30("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", `
6185
6540
  Examples:
6186
6541
  $ jai1 utils url-encode "hello world"
6187
6542
  $ jai1 utils url-encode "hello world & test"
@@ -6194,7 +6549,7 @@ Examples:
6194
6549
  }
6195
6550
 
6196
6551
  // src/commands/utils/url-decode.ts
6197
- import { Command as Command26 } from "commander";
6552
+ import { Command as Command31 } from "commander";
6198
6553
  async function handleUrlDecode(input4, options) {
6199
6554
  const service = new UtilsService();
6200
6555
  try {
@@ -6215,7 +6570,7 @@ async function handleUrlDecode(input4, options) {
6215
6570
  }
6216
6571
  }
6217
6572
  function createUrlDecodeCommand() {
6218
- 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", `
6573
+ const cmd = new Command31("url-decode").description("Decode URL-encoded string").argument("<input>", "Text to decode").option("--full", "Decode full URL (use decodeURI instead of decodeURIComponent)").addHelpText("after", `
6219
6574
  Examples:
6220
6575
  $ jai1 utils url-decode "hello%20world"
6221
6576
  $ jai1 utils url-decode "hello%20world%20%26%20test"
@@ -6228,7 +6583,7 @@ Examples:
6228
6583
  }
6229
6584
 
6230
6585
  // src/commands/utils/cron.ts
6231
- import { Command as Command27 } from "commander";
6586
+ import { Command as Command32 } from "commander";
6232
6587
  import cronstrue from "cronstrue";
6233
6588
  async function handleCronParse(expression) {
6234
6589
  try {
@@ -6271,7 +6626,7 @@ async function handleCronParse(expression) {
6271
6626
  }
6272
6627
  }
6273
6628
  function createCronCommand() {
6274
- const cmd = new Command27("cron").description("Parse and explain cron expression").argument("<expression>", 'Cron expression (e.g., "0 5 * * *")').addHelpText("after", `
6629
+ const cmd = new Command32("cron").description("Parse and explain cron expression").argument("<expression>", 'Cron expression (e.g., "0 5 * * *")').addHelpText("after", `
6275
6630
  Examples:
6276
6631
  $ jai1 utils cron "0 5 * * *"
6277
6632
  $ jai1 utils cron "*/15 * * * *"
@@ -6285,7 +6640,7 @@ Examples:
6285
6640
  }
6286
6641
 
6287
6642
  // src/commands/utils/markdown-preview.ts
6288
- import { Command as Command28 } from "commander";
6643
+ import { Command as Command33 } from "commander";
6289
6644
  import { readFile as readFile5 } from "fs/promises";
6290
6645
  import { marked } from "marked";
6291
6646
  import TerminalRenderer from "marked-terminal";
@@ -6324,7 +6679,7 @@ ${code.trim()}
6324
6679
  }
6325
6680
  }
6326
6681
  function createMarkdownPreviewCommand() {
6327
- 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", `
6682
+ const cmd = new Command33("markdown-preview").description("Preview markdown file in terminal").argument("<file>", "Markdown file path").option("--raw", "Show raw markdown instead of rendered").addHelpText("after", `
6328
6683
  Examples:
6329
6684
  $ jai1 utils markdown-preview README.md
6330
6685
  $ jai1 utils markdown-preview ./docs/guide.md
@@ -7733,7 +8088,7 @@ async function runInteractiveMode() {
7733
8088
 
7734
8089
  // src/commands/utils/index.ts
7735
8090
  function createUtilsCommand() {
7736
- const utilsCommand = new Command29("utils").description("Developer utilities for common tasks").option("-i, --interactive", "Run in interactive mode").addHelpText("after", `
8091
+ const utilsCommand = new Command34("utils").description("Developer utilities for common tasks").option("-i, --interactive", "Run in interactive mode").addHelpText("after", `
7737
8092
  Interactive Mode:
7738
8093
  $ jai1 utils --interactive
7739
8094
  $ jai1 utils -i
@@ -7768,11 +8123,11 @@ Quick Usage:
7768
8123
  }
7769
8124
 
7770
8125
  // src/commands/deps/index.ts
7771
- import { Command as Command31 } from "commander";
8126
+ import { Command as Command36 } from "commander";
7772
8127
 
7773
8128
  // src/commands/deps/upgrade.ts
7774
- import { Command as Command30 } from "commander";
7775
- import { checkbox as checkbox3, confirm as confirm4 } from "@inquirer/prompts";
8129
+ import { Command as Command35 } from "commander";
8130
+ import { checkbox as checkbox3, confirm as confirm5 } from "@inquirer/prompts";
7776
8131
 
7777
8132
  // src/services/deps.service.ts
7778
8133
  import { promises as fs10 } from "fs";
@@ -7985,7 +8340,7 @@ var colors2 = {
7985
8340
  dim: "\x1B[2m"
7986
8341
  };
7987
8342
  function createDepsUpgradeCommand() {
7988
- 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) => {
8343
+ return new Command35("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) => {
7989
8344
  await handleDepsUpgrade(options);
7990
8345
  });
7991
8346
  }
@@ -8077,7 +8432,7 @@ ${colors2.yellow}\u23F8\uFE0F \u0110\xE3 h\u1EE7y${colors2.reset}
8077
8432
  }
8078
8433
  let shouldProceed;
8079
8434
  try {
8080
- shouldProceed = await confirm4({
8435
+ shouldProceed = await confirm5({
8081
8436
  message: `Ti\u1EBFn h\xE0nh upgrade ${selectedPackages.length} packages?`,
8082
8437
  default: true
8083
8438
  });
@@ -8165,16 +8520,16 @@ function displayUpgradeTable(packages) {
8165
8520
 
8166
8521
  // src/commands/deps/index.ts
8167
8522
  function createDepsCommand() {
8168
- const depsCommand = new Command31("deps").description("Qu\u1EA3n l\xFD dependencies trong project");
8523
+ const depsCommand = new Command36("deps").description("Qu\u1EA3n l\xFD dependencies trong project");
8169
8524
  depsCommand.addCommand(createDepsUpgradeCommand());
8170
8525
  return depsCommand;
8171
8526
  }
8172
8527
 
8173
8528
  // src/commands/kit/index.ts
8174
- import { Command as Command35 } from "commander";
8529
+ import { Command as Command40 } from "commander";
8175
8530
 
8176
8531
  // src/commands/kit/list.ts
8177
- import { Command as Command32 } from "commander";
8532
+ import { Command as Command37 } from "commander";
8178
8533
 
8179
8534
  // src/services/starter-kit.service.ts
8180
8535
  import { promises as fs11 } from "fs";
@@ -8241,7 +8596,7 @@ var StarterKitService = class {
8241
8596
 
8242
8597
  // src/commands/kit/list.ts
8243
8598
  function createKitListCommand() {
8244
- 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) => {
8599
+ return new Command37("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) => {
8245
8600
  const configService = new ConfigService();
8246
8601
  const config = await configService.load();
8247
8602
  if (!config) {
@@ -8278,9 +8633,9 @@ function createKitListCommand() {
8278
8633
  }
8279
8634
 
8280
8635
  // src/commands/kit/info.ts
8281
- import { Command as Command33 } from "commander";
8636
+ import { Command as Command38 } from "commander";
8282
8637
  function createKitInfoCommand() {
8283
- return new Command33("info").description("Show detailed information about a starter kit").argument("<slug>", "Starter kit slug").action(async (slug) => {
8638
+ return new Command38("info").description("Show detailed information about a starter kit").argument("<slug>", "Starter kit slug").action(async (slug) => {
8284
8639
  const configService = new ConfigService();
8285
8640
  const config = await configService.load();
8286
8641
  if (!config) {
@@ -8329,7 +8684,7 @@ Post-Init Commands:`);
8329
8684
  }
8330
8685
 
8331
8686
  // src/commands/kit/create.ts
8332
- import { Command as Command34 } from "commander";
8687
+ import { Command as Command39 } from "commander";
8333
8688
  import { promises as fs12 } from "fs";
8334
8689
  import { join as join6 } from "path";
8335
8690
  import { select as select2, input, checkbox as checkbox4 } from "@inquirer/prompts";
@@ -8374,7 +8729,7 @@ var HookExecutor = class {
8374
8729
 
8375
8730
  // src/commands/kit/create.ts
8376
8731
  function createKitCreateCommand() {
8377
- 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) => {
8732
+ return new Command39("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) => {
8378
8733
  const configService = new ConfigService();
8379
8734
  const config = await configService.load();
8380
8735
  if (!config) {
@@ -8546,7 +8901,7 @@ async function getAllFiles(dir) {
8546
8901
 
8547
8902
  // src/commands/kit/index.ts
8548
8903
  function createKitCommand() {
8549
- const cmd = new Command35("kit").description("Manage starter kits for new projects").action(() => {
8904
+ const cmd = new Command40("kit").description("Manage starter kits for new projects").action(() => {
8550
8905
  cmd.help();
8551
8906
  });
8552
8907
  cmd.addCommand(createKitListCommand());
@@ -8556,12 +8911,12 @@ function createKitCommand() {
8556
8911
  }
8557
8912
 
8558
8913
  // src/commands/rules/index.ts
8559
- import { Command as Command42 } from "commander";
8914
+ import { Command as Command47 } from "commander";
8560
8915
 
8561
8916
  // src/commands/rules/list.ts
8562
- import { Command as Command36 } from "commander";
8917
+ import { Command as Command41 } from "commander";
8563
8918
  function createRulesListCommand() {
8564
- return new Command36("list").description("List available rule presets").option("--json", "Output as JSON").action(async (options) => {
8919
+ return new Command41("list").description("List available rule presets").option("--json", "Output as JSON").action(async (options) => {
8565
8920
  const configService = new ConfigService();
8566
8921
  const config = await configService.load();
8567
8922
  if (!config) {
@@ -8616,12 +8971,12 @@ function createRulesListCommand() {
8616
8971
  }
8617
8972
 
8618
8973
  // src/commands/rules/init.ts
8619
- import { Command as Command37 } from "commander";
8974
+ import { Command as Command42 } from "commander";
8620
8975
  import { promises as fs13 } from "fs";
8621
8976
  import { join as join7 } from "path";
8622
- import { select as select3, confirm as confirm5 } from "@inquirer/prompts";
8977
+ import { select as select3, confirm as confirm6 } from "@inquirer/prompts";
8623
8978
  function createRulesInitCommand() {
8624
- 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) => {
8979
+ return new Command42("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) => {
8625
8980
  const configService = new ConfigService();
8626
8981
  const config = await configService.load();
8627
8982
  if (!config) {
@@ -8681,7 +9036,7 @@ function createRulesInitCommand() {
8681
9036
  });
8682
9037
  }
8683
9038
  if (!options.yes) {
8684
- const proceed = await confirm5({
9039
+ const proceed = await confirm6({
8685
9040
  message: `Apply preset '${bundle.preset.name}' to current directory?`,
8686
9041
  default: true
8687
9042
  });
@@ -8749,10 +9104,10 @@ async function applyAgentsMdFormat(bundle) {
8749
9104
  }
8750
9105
 
8751
9106
  // src/commands/rules/apply.ts
8752
- import { Command as Command38 } from "commander";
9107
+ import { Command as Command43 } from "commander";
8753
9108
  import { promises as fs15 } from "fs";
8754
9109
  import { join as join9 } from "path";
8755
- import { select as select4, confirm as confirm6, checkbox as checkbox5 } from "@inquirer/prompts";
9110
+ import { select as select4, confirm as confirm7, checkbox as checkbox5 } from "@inquirer/prompts";
8756
9111
 
8757
9112
  // src/services/rules-generator.service.ts
8758
9113
  var RulesGeneratorService = class {
@@ -9332,7 +9687,7 @@ Restoring backup from ${metadata.timestamp}...`);
9332
9687
 
9333
9688
  // src/commands/rules/apply.ts
9334
9689
  function createRulesApplyCommand() {
9335
- 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) => {
9690
+ return new Command43("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) => {
9336
9691
  const configService = new ConfigService();
9337
9692
  const config = await configService.load();
9338
9693
  if (!config) {
@@ -9472,7 +9827,7 @@ function createRulesApplyCommand() {
9472
9827
  if (backupPath) {
9473
9828
  console.log(` Backup: ${backupPath}`);
9474
9829
  }
9475
- const proceed = await confirm6({
9830
+ const proceed = await confirm7({
9476
9831
  message: "Apply these rules to the current directory?",
9477
9832
  default: true
9478
9833
  });
@@ -9577,11 +9932,11 @@ function createRulesApplyCommand() {
9577
9932
  }
9578
9933
 
9579
9934
  // src/commands/rules/restore.ts
9580
- import { Command as Command39 } from "commander";
9935
+ import { Command as Command44 } from "commander";
9581
9936
  import { join as join10 } from "path";
9582
- import { select as select5, confirm as confirm7 } from "@inquirer/prompts";
9937
+ import { select as select5, confirm as confirm8 } from "@inquirer/prompts";
9583
9938
  function createRulesRestoreCommand() {
9584
- 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) => {
9939
+ return new Command44("restore").description("Restore rules from a backup").option("--latest", "Restore the most recent backup").option("-y, --yes", "Skip confirmation").action(async (options) => {
9585
9940
  const backupService = new BackupService();
9586
9941
  const backups = await backupService.listBackups();
9587
9942
  if (backups.length === 0) {
@@ -9613,7 +9968,7 @@ function createRulesRestoreCommand() {
9613
9968
  console.log(` IDEs: ${selectedBackup.ides.join(", ")}`);
9614
9969
  console.log(` Files: ${selectedBackup.files.length}`);
9615
9970
  if (!options.yes) {
9616
- const proceed = await confirm7({
9971
+ const proceed = await confirm8({
9617
9972
  message: "This will overwrite current rules. Continue?",
9618
9973
  default: false
9619
9974
  });
@@ -9649,12 +10004,12 @@ function formatTimestamp(timestamp) {
9649
10004
  }
9650
10005
 
9651
10006
  // src/commands/rules/sync.ts
9652
- import { Command as Command40 } from "commander";
10007
+ import { Command as Command45 } from "commander";
9653
10008
  import { promises as fs16 } from "fs";
9654
10009
  import { join as join11 } from "path";
9655
- import { confirm as confirm8 } from "@inquirer/prompts";
10010
+ import { confirm as confirm9 } from "@inquirer/prompts";
9656
10011
  function createRulesSyncCommand() {
9657
- 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) => {
10012
+ return new Command45("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) => {
9658
10013
  const configPath = join11(process.cwd(), "jai1-rules.json");
9659
10014
  let projectConfig;
9660
10015
  try {
@@ -9685,7 +10040,7 @@ Detected ${detected.length} active IDE(s):
9685
10040
  console.log(` ${confidence} ${d.name} - ${d.ruleCount} rules`);
9686
10041
  });
9687
10042
  if (!options.yes) {
9688
- const proceed = await confirm8({
10043
+ const proceed = await confirm9({
9689
10044
  message: "\nSync these detected IDEs?",
9690
10045
  default: true
9691
10046
  });
@@ -9799,11 +10154,11 @@ async function checkPathExists(absolutePath) {
9799
10154
  }
9800
10155
 
9801
10156
  // src/commands/rules/info.ts
9802
- import { Command as Command41 } from "commander";
10157
+ import { Command as Command46 } from "commander";
9803
10158
  import { promises as fs17 } from "fs";
9804
10159
  import { join as join12 } from "path";
9805
10160
  function createRulesInfoCommand() {
9806
- return new Command41("info").description("Show current preset information").option("--json", "Output as JSON").action(async (options) => {
10161
+ return new Command46("info").description("Show current preset information").option("--json", "Output as JSON").action(async (options) => {
9807
10162
  const configPath = join12(process.cwd(), "jai1-rules.json");
9808
10163
  let projectConfig;
9809
10164
  try {
@@ -9909,7 +10264,7 @@ async function checkIdeFilesExist(ideId, format) {
9909
10264
 
9910
10265
  // src/commands/rules/index.ts
9911
10266
  function createRulesCommand() {
9912
- const rulesCommand = new Command42("rules").description("Manage rule presets for AI agents");
10267
+ const rulesCommand = new Command47("rules").description("Manage rule presets for AI agents");
9913
10268
  rulesCommand.addCommand(createRulesListCommand());
9914
10269
  rulesCommand.addCommand(createRulesInitCommand());
9915
10270
  rulesCommand.addCommand(createRulesApplyCommand());
@@ -9920,8 +10275,8 @@ function createRulesCommand() {
9920
10275
  }
9921
10276
 
9922
10277
  // src/commands/upgrade.ts
9923
- import { Command as Command43 } from "commander";
9924
- import { confirm as confirm9 } from "@inquirer/prompts";
10278
+ import { Command as Command48 } from "commander";
10279
+ import { confirm as confirm10 } from "@inquirer/prompts";
9925
10280
  import { execSync as execSync2 } from "child_process";
9926
10281
  var colors3 = {
9927
10282
  yellow: "\x1B[33m",
@@ -9932,7 +10287,7 @@ var colors3 = {
9932
10287
  bold: "\x1B[1m"
9933
10288
  };
9934
10289
  function createUpgradeCommand() {
9935
- 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) => {
10290
+ return new Command48("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) => {
9936
10291
  await handleUpgrade(options);
9937
10292
  });
9938
10293
  }
@@ -9976,7 +10331,7 @@ ${colors3.bold}Current version:${colors3.reset} ${currentVersion}`);
9976
10331
  return;
9977
10332
  }
9978
10333
  if (!options.force) {
9979
- const shouldUpdate = await confirm9({
10334
+ const shouldUpdate = await confirm10({
9980
10335
  message: "Update to the latest version now?",
9981
10336
  default: true
9982
10337
  });
@@ -10080,11 +10435,11 @@ function getInstallCommand(packageManager2) {
10080
10435
  }
10081
10436
 
10082
10437
  // src/commands/clean.ts
10083
- import { Command as Command44 } from "commander";
10084
- import { confirm as confirm10, select as select6 } from "@inquirer/prompts";
10438
+ import { Command as Command49 } from "commander";
10439
+ import { confirm as confirm11, select as select6 } from "@inquirer/prompts";
10085
10440
  import { join as join13 } from "path";
10086
10441
  function createCleanCommand() {
10087
- 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) => {
10442
+ return new Command49("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) => {
10088
10443
  await handleClean(options);
10089
10444
  });
10090
10445
  }
@@ -10179,7 +10534,7 @@ async function cleanTarget(target, skipConfirm) {
10179
10534
  }
10180
10535
  const countStr = info.count ? ` (${info.count} items)` : "";
10181
10536
  if (!skipConfirm) {
10182
- const confirmed = await confirm10({
10537
+ const confirmed = await confirm11({
10183
10538
  message: `Delete ${target.name}${countStr}?`,
10184
10539
  default: false
10185
10540
  });
@@ -10197,7 +10552,7 @@ async function cleanTarget(target, skipConfirm) {
10197
10552
  }
10198
10553
 
10199
10554
  // src/commands/redmine/check.ts
10200
- import { Command as Command45 } from "commander";
10555
+ import { Command as Command50 } from "commander";
10201
10556
 
10202
10557
  // src/services/redmine-config.service.ts
10203
10558
  import { readFile as readFile6 } from "fs/promises";
@@ -10504,7 +10859,7 @@ async function checkConnectivity(config) {
10504
10859
 
10505
10860
  // src/commands/redmine/check.ts
10506
10861
  function createRedmineCheckCommand() {
10507
- 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) => {
10862
+ const cmd = new Command50("check").description("Check Redmine connectivity").option("-c, --config <path>", "Config file path", "redmine.config.yaml").option("--json", "Output as JSON").action(async (options) => {
10508
10863
  await handleRedmineCheck(options);
10509
10864
  });
10510
10865
  return cmd;
@@ -10532,7 +10887,7 @@ async function handleRedmineCheck(options) {
10532
10887
  }
10533
10888
 
10534
10889
  // src/commands/redmine/sync-issue.ts
10535
- import { Command as Command46 } from "commander";
10890
+ import { Command as Command51 } from "commander";
10536
10891
 
10537
10892
  // src/sync-issue.ts
10538
10893
  import { resolve as resolve3, relative } from "path";
@@ -10908,7 +11263,7 @@ function extractIssueIdFromUrl(url) {
10908
11263
 
10909
11264
  // src/commands/redmine/sync-issue.ts
10910
11265
  function createSyncIssueCommand() {
10911
- 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) => {
11266
+ const cmd = new Command51("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) => {
10912
11267
  await handleSyncIssue(options);
10913
11268
  });
10914
11269
  return cmd;
@@ -10952,7 +11307,7 @@ async function handleSyncIssue(options) {
10952
11307
  }
10953
11308
 
10954
11309
  // src/commands/redmine/sync-project.ts
10955
- import { Command as Command47 } from "commander";
11310
+ import { Command as Command52 } from "commander";
10956
11311
 
10957
11312
  // src/sync-project.ts
10958
11313
  async function syncProject(config, options = {}) {
@@ -11022,7 +11377,7 @@ async function syncProject(config, options = {}) {
11022
11377
 
11023
11378
  // src/commands/redmine/sync-project.ts
11024
11379
  function createSyncProjectCommand() {
11025
- 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) => {
11380
+ const cmd = new Command52("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) => {
11026
11381
  await handleSyncProject(options);
11027
11382
  });
11028
11383
  return cmd;
@@ -11077,12 +11432,12 @@ async function handleSyncProject(options) {
11077
11432
  }
11078
11433
 
11079
11434
  // src/commands/framework/info.ts
11080
- import { Command as Command48 } from "commander";
11435
+ import { Command as Command53 } from "commander";
11081
11436
  import { promises as fs18 } from "fs";
11082
11437
  import { join as join14 } from "path";
11083
11438
  import { homedir as homedir5 } from "os";
11084
11439
  function createInfoCommand() {
11085
- 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) => {
11440
+ const cmd = new Command53("info").description("Show jai1-client configuration and status").option("--json", "Output as JSON").option("--verbose", "Show detailed information").action(async (options) => {
11086
11441
  await handleInfo(options);
11087
11442
  });
11088
11443
  return cmd;
@@ -11138,8 +11493,8 @@ async function getProjectStatus2() {
11138
11493
  }
11139
11494
 
11140
11495
  // src/commands/self-update.ts
11141
- import { Command as Command49 } from "commander";
11142
- import { confirm as confirm11 } from "@inquirer/prompts";
11496
+ import { Command as Command54 } from "commander";
11497
+ import { confirm as confirm12 } from "@inquirer/prompts";
11143
11498
  import { execSync as execSync3 } from "child_process";
11144
11499
  var colors4 = {
11145
11500
  yellow: "\x1B[33m",
@@ -11150,7 +11505,7 @@ var colors4 = {
11150
11505
  bold: "\x1B[1m"
11151
11506
  };
11152
11507
  function createSelfUpdateCommand() {
11153
- 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) => {
11508
+ return new Command54("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) => {
11154
11509
  await handleSelfUpdate(options);
11155
11510
  });
11156
11511
  }
@@ -11194,7 +11549,7 @@ ${colors4.bold}Current version:${colors4.reset} ${currentVersion}`);
11194
11549
  return;
11195
11550
  }
11196
11551
  if (!options.force) {
11197
- const shouldUpdate = await confirm11({
11552
+ const shouldUpdate = await confirm12({
11198
11553
  message: "Update to the latest version now?",
11199
11554
  default: true
11200
11555
  });
@@ -11290,10 +11645,10 @@ function getInstallCommand2(packageManager2) {
11290
11645
  }
11291
11646
 
11292
11647
  // src/commands/clear-backups.ts
11293
- import { Command as Command50 } from "commander";
11294
- import { confirm as confirm12 } from "@inquirer/prompts";
11648
+ import { Command as Command55 } from "commander";
11649
+ import { confirm as confirm13 } from "@inquirer/prompts";
11295
11650
  function createClearBackupsCommand() {
11296
- return new Command50("clear-backups").description("Clear backup files").option("-y, --yes", "Skip confirmation").action(async (options) => {
11651
+ return new Command55("clear-backups").description("Clear backup files").option("-y, --yes", "Skip confirmation").action(async (options) => {
11297
11652
  const service = new ComponentsService();
11298
11653
  const backups = await service.listBackups(process.cwd());
11299
11654
  if (backups.length === 0) {
@@ -11309,7 +11664,7 @@ function createClearBackupsCommand() {
11309
11664
  }
11310
11665
  console.log();
11311
11666
  if (!options.yes) {
11312
- const ok = await confirm12({ message: "Delete all backups?", default: false });
11667
+ const ok = await confirm13({ message: "Delete all backups?", default: false });
11313
11668
  if (!ok) return;
11314
11669
  }
11315
11670
  await service.clearBackups(process.cwd());
@@ -11318,8 +11673,8 @@ function createClearBackupsCommand() {
11318
11673
  }
11319
11674
 
11320
11675
  // src/commands/vscode/index.ts
11321
- import { Command as Command51 } from "commander";
11322
- import { checkbox as checkbox7, confirm as confirm13, select as select7 } from "@inquirer/prompts";
11676
+ import { Command as Command56 } from "commander";
11677
+ import { checkbox as checkbox7, confirm as confirm14, select as select7 } from "@inquirer/prompts";
11323
11678
  import fs19 from "fs/promises";
11324
11679
  import path7 from "path";
11325
11680
  import { existsSync as existsSync3 } from "fs";
@@ -11458,7 +11813,7 @@ var PERFORMANCE_GROUPS2 = {
11458
11813
  }
11459
11814
  };
11460
11815
  function createVSCodeCommand() {
11461
- const vscodeCommand = new Command51("vscode").description("Qu\u1EA3n l\xFD c\xE0i \u0111\u1EB7t VSCode cho d\u1EF1 \xE1n hi\u1EC7n t\u1EA1i");
11816
+ const vscodeCommand = new Command56("vscode").description("Qu\u1EA3n l\xFD c\xE0i \u0111\u1EB7t VSCode cho d\u1EF1 \xE1n hi\u1EC7n t\u1EA1i");
11462
11817
  vscodeCommand.action(async () => {
11463
11818
  await interactiveMode2();
11464
11819
  });
@@ -11562,7 +11917,7 @@ async function applyGroups2(groupKeys, action) {
11562
11917
  console.log("\u{1F4C4} \u0110\xE3 \u0111\u1ECDc c\xE0i \u0111\u1EB7t hi\u1EC7n t\u1EA1i t\u1EEB settings.json");
11563
11918
  } catch {
11564
11919
  console.warn("\u26A0\uFE0F Kh\xF4ng th\u1EC3 \u0111\u1ECDc settings.json (c\xF3 th\u1EC3 ch\u1EE9a comments).");
11565
- const confirmOverwrite = await confirm13({
11920
+ const confirmOverwrite = await confirm14({
11566
11921
  message: "Ghi \u0111\xE8 file settings.json hi\u1EC7n t\u1EA1i?",
11567
11922
  default: false
11568
11923
  });
@@ -11609,7 +11964,7 @@ async function resetSettings2(groupKeys) {
11609
11964
  console.log("\n\u26A0\uFE0F Kh\xF4ng t\xECm th\u1EA5y file settings.json");
11610
11965
  return;
11611
11966
  }
11612
- const confirmReset = await confirm13({
11967
+ const confirmReset = await confirm14({
11613
11968
  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(", ")}?`,
11614
11969
  default: false
11615
11970
  });
@@ -11629,9 +11984,9 @@ async function resetSettings2(groupKeys) {
11629
11984
  // src/commands/guide.ts
11630
11985
  import React40 from "react";
11631
11986
  import { render as render6 } from "ink";
11632
- import { Command as Command52 } from "commander";
11987
+ import { Command as Command57 } from "commander";
11633
11988
  function createGuideCommand() {
11634
- 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) => {
11989
+ const cmd = new Command57("guide").description("Interactive guide center for Agentic Coding").option("--topic <topic>", "Open specific topic (intro, rules, workflows, prompts, skills)").action(async (options) => {
11635
11990
  const { waitUntilExit } = render6(
11636
11991
  React40.createElement(GuideApp, {
11637
11992
  initialTopic: options.topic,
@@ -11648,9 +12003,9 @@ function createGuideCommand() {
11648
12003
  // src/commands/context.ts
11649
12004
  import React41 from "react";
11650
12005
  import { render as render7 } from "ink";
11651
- import { Command as Command53 } from "commander";
12006
+ import { Command as Command58 } from "commander";
11652
12007
  function createContextCommand() {
11653
- 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) => {
12008
+ const cmd = new Command58("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) => {
11654
12009
  let initialIDE;
11655
12010
  if (options.ide) {
11656
12011
  const validIDEs = ["cursor", "windsurf", "antigravity", "jai1"];
@@ -11727,10 +12082,10 @@ async function printStats2() {
11727
12082
  }
11728
12083
 
11729
12084
  // src/commands/migrate-ide.ts
11730
- import { Command as Command54 } from "commander";
11731
- import { checkbox as checkbox8, confirm as confirm14 } from "@inquirer/prompts";
12085
+ import { Command as Command59 } from "commander";
12086
+ import { checkbox as checkbox8, confirm as confirm15 } from "@inquirer/prompts";
11732
12087
  function createMigrateIdeCommand() {
11733
- 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) => {
12088
+ const cmd = new Command59("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) => {
11734
12089
  await runMigrateIde(options);
11735
12090
  });
11736
12091
  return cmd;
@@ -11799,7 +12154,7 @@ async function runMigrateIde(options) {
11799
12154
  if (options.dryRun) {
11800
12155
  console.log("\u{1F50D} DRY RUN - No files will be written\n");
11801
12156
  }
11802
- const confirmed = await confirm14({
12157
+ const confirmed = await confirm15({
11803
12158
  message: "Proceed with migration?",
11804
12159
  default: true
11805
12160
  });
@@ -11836,7 +12191,7 @@ async function runMigrateIde(options) {
11836
12191
  }
11837
12192
 
11838
12193
  // src/cli.ts
11839
- var program = new Command55();
12194
+ var program = new Command60();
11840
12195
  if (process.argv.includes("-v") || process.argv.includes("--version")) {
11841
12196
  console.log(package_default.version);
11842
12197
  if (!process.argv.includes("--skip-update-check")) {
@@ -11857,15 +12212,16 @@ program.addCommand(createChatCommand());
11857
12212
  program.addCommand(createOpenAiKeysCommand());
11858
12213
  program.addCommand(createStatsCommand());
11859
12214
  program.addCommand(createTranslateCommand());
12215
+ program.addCommand(createImageCommand());
11860
12216
  program.addCommand(createUtilsCommand());
11861
12217
  program.addCommand(createDepsCommand());
11862
12218
  program.addCommand(createKitCommand());
11863
12219
  program.addCommand(createRulesCommand());
11864
12220
  program.addCommand(createUpgradeCommand());
11865
12221
  program.addCommand(createCleanCommand());
11866
- var redmineCommand = new Command55("redmine").description("Redmine context sync commands");
12222
+ var redmineCommand = new Command60("redmine").description("Redmine context sync commands");
11867
12223
  redmineCommand.addCommand(createRedmineCheckCommand());
11868
- var syncCommand = new Command55("sync").description("Sync Redmine issues to markdown files");
12224
+ var syncCommand = new Command60("sync").description("Sync Redmine issues to markdown files");
11869
12225
  syncCommand.addCommand(createSyncIssueCommand());
11870
12226
  syncCommand.addCommand(createSyncProjectCommand());
11871
12227
  redmineCommand.addCommand(syncCommand);