@jvittechs/jai1-cli 0.1.92 → 0.1.94

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -33,7 +33,7 @@ var NetworkError = class extends Jai1Error {
33
33
  // package.json
34
34
  var package_default = {
35
35
  name: "@jvittechs/jai1-cli",
36
- version: "0.1.92",
36
+ version: "0.1.94",
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: {
@@ -772,6 +772,392 @@ import { Box as Box2, Text as Text3, useInput, useApp } from "ink";
772
772
  import Spinner from "ink-spinner";
773
773
  import TextInput from "ink-text-input";
774
774
 
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";
779
+
780
+ // src/constants/ide-migration-configs.ts
781
+ function isAlwaysTrigger(trigger) {
782
+ return trigger === "always_on" || trigger === "always";
783
+ }
784
+ function getGlobFromTrigger(trigger, globs) {
785
+ if (globs && globs.length > 0) return globs;
786
+ if (trigger && !isAlwaysTrigger(trigger) && trigger !== "manual") {
787
+ return [trigger];
788
+ }
789
+ return void 0;
790
+ }
791
+ var IDE_MIGRATION_CONFIGS = {
792
+ cursor: {
793
+ id: "cursor",
794
+ name: "Cursor",
795
+ icon: "\u{1F52E}",
796
+ basePath: ".cursor",
797
+ rulesPath: "rules",
798
+ workflowsPath: null,
799
+ commandsPath: "commands",
800
+ fileExtension: ".mdc",
801
+ generateFrontmatter: (opts) => {
802
+ const lines = ["---"];
803
+ if (opts.description) {
804
+ lines.push(`description: ${opts.description}`);
805
+ }
806
+ const alwaysApply = opts.alwaysApply || isAlwaysTrigger(opts.trigger);
807
+ const globs = getGlobFromTrigger(opts.trigger, opts.globs);
808
+ if (globs && globs.length > 0) {
809
+ lines.push(`globs: ${globs.join(", ")}`);
810
+ }
811
+ lines.push(`alwaysApply: ${alwaysApply}`);
812
+ lines.push("---");
813
+ return lines.join("\n");
814
+ }
815
+ },
816
+ windsurf: {
817
+ id: "windsurf",
818
+ name: "Windsurf",
819
+ icon: "\u{1F3C4}",
820
+ basePath: ".windsurf",
821
+ rulesPath: "rules",
822
+ workflowsPath: "workflows",
823
+ commandsPath: null,
824
+ fileExtension: ".md",
825
+ generateFrontmatter: (opts) => {
826
+ let trigger;
827
+ if (opts.trigger) {
828
+ trigger = opts.trigger === "always" ? "always_on" : opts.trigger;
829
+ } else if (opts.alwaysApply) {
830
+ trigger = "always_on";
831
+ } else if (opts.globs && opts.globs.length > 0) {
832
+ trigger = opts.globs[0];
833
+ } else {
834
+ trigger = "always_on";
835
+ }
836
+ return `---
837
+ trigger: ${trigger}
838
+ ---`;
839
+ }
840
+ },
841
+ antigravity: {
842
+ id: "antigravity",
843
+ name: "Antigravity",
844
+ icon: "\u{1F680}",
845
+ basePath: ".agent",
846
+ rulesPath: "rules",
847
+ workflowsPath: "workflows",
848
+ commandsPath: null,
849
+ fileExtension: ".md",
850
+ generateFrontmatter: (opts) => {
851
+ let trigger;
852
+ if (opts.trigger) {
853
+ trigger = opts.trigger === "always" ? "always_on" : opts.trigger;
854
+ } else if (opts.alwaysApply) {
855
+ trigger = "always_on";
856
+ } else if (opts.globs && opts.globs.length > 0) {
857
+ trigger = opts.globs[0];
858
+ } else {
859
+ trigger = "always_on";
860
+ }
861
+ return `---
862
+ trigger: ${trigger}
863
+ ---`;
864
+ }
865
+ },
866
+ claudecode: {
867
+ id: "claudecode",
868
+ name: "Claude Code",
869
+ icon: "\u{1F916}",
870
+ basePath: ".claude",
871
+ rulesPath: "rules",
872
+ workflowsPath: null,
873
+ commandsPath: "commands",
874
+ fileExtension: ".md",
875
+ generateFrontmatter: (opts) => {
876
+ const lines = ["---"];
877
+ if (opts.description) {
878
+ lines.push(`description: ${opts.description}`);
879
+ }
880
+ const isAlways = opts.alwaysApply || isAlwaysTrigger(opts.trigger);
881
+ if (!isAlways) {
882
+ const paths = getGlobFromTrigger(opts.trigger, opts.globs);
883
+ if (paths && paths.length > 0) {
884
+ lines.push(`paths: ${paths.join(", ")}`);
885
+ }
886
+ }
887
+ lines.push("---");
888
+ return lines.join("\n");
889
+ }
890
+ },
891
+ opencode: {
892
+ id: "opencode",
893
+ name: "OpenCode",
894
+ icon: "\u{1F4BB}",
895
+ basePath: ".opencode",
896
+ rulesPath: "rules",
897
+ workflowsPath: "workflows",
898
+ commandsPath: null,
899
+ fileExtension: ".md",
900
+ generateFrontmatter: (opts) => {
901
+ let trigger;
902
+ if (opts.trigger) {
903
+ trigger = opts.trigger === "always" ? "always_on" : opts.trigger;
904
+ } else if (opts.alwaysApply) {
905
+ trigger = "always_on";
906
+ } else if (opts.globs && opts.globs.length > 0) {
907
+ trigger = opts.globs[0];
908
+ } else {
909
+ trigger = "always_on";
910
+ }
911
+ return `---
912
+ trigger: ${trigger}
913
+ ---`;
914
+ }
915
+ }
916
+ };
917
+ function getMigrationIDEs() {
918
+ return Object.keys(IDE_MIGRATION_CONFIGS);
919
+ }
920
+
921
+ // src/services/migrate-ide.service.ts
922
+ var MigrateIdeService = class {
923
+ projectPath;
924
+ jai1Path;
925
+ constructor(projectPath = process.cwd()) {
926
+ this.projectPath = projectPath;
927
+ this.jai1Path = path.join(projectPath, ".jai1");
928
+ }
929
+ /**
930
+ * Scan .jai1/ directory for content
931
+ */
932
+ async scanJai1Content() {
933
+ const manualRules = await this.scanContentType("rules");
934
+ const presetRules = await this.scanRulePreset();
935
+ const rules = [...presetRules, ...manualRules];
936
+ const workflows = await this.scanContentType("workflows");
937
+ const commands = [];
938
+ return {
939
+ rules,
940
+ workflows,
941
+ commands,
942
+ totalCount: rules.length + workflows.length + commands.length
943
+ };
944
+ }
945
+ /**
946
+ * Scan .jai1/rule-preset/ directory for rule preset files
947
+ */
948
+ async scanRulePreset() {
949
+ const items = [];
950
+ const presetDir = path.join(this.jai1Path, "rule-preset");
951
+ try {
952
+ await fs4.access(presetDir);
953
+ } catch {
954
+ return items;
955
+ }
956
+ const files = await fs4.readdir(presetDir);
957
+ for (const file of files) {
958
+ if (!file.endsWith(".mdc")) continue;
959
+ const filepath = path.join(presetDir, file);
960
+ const stat = await fs4.stat(filepath);
961
+ if (!stat.isFile()) continue;
962
+ const content = await fs4.readFile(filepath, "utf-8");
963
+ let frontmatter = {};
964
+ try {
965
+ const { data } = matter(content);
966
+ frontmatter = data;
967
+ } catch {
968
+ }
969
+ const name = path.basename(file, ".mdc");
970
+ const trigger = this.extractTrigger(frontmatter);
971
+ const alwaysApply = this.isAlwaysTrigger(trigger);
972
+ items.push({
973
+ type: "rules",
974
+ name,
975
+ filepath,
976
+ relativePath: path.relative(this.projectPath, filepath),
977
+ description: frontmatter.description,
978
+ globs: this.extractGlobs(frontmatter),
979
+ trigger,
980
+ alwaysApply
981
+ });
982
+ }
983
+ return items;
984
+ }
985
+ /**
986
+ * Scan a specific content type
987
+ */
988
+ async scanContentType(type) {
989
+ const items = [];
990
+ const dirPath = path.join(this.jai1Path, type);
991
+ try {
992
+ await fs4.access(dirPath);
993
+ } catch {
994
+ return items;
995
+ }
996
+ const files = await fs4.readdir(dirPath);
997
+ for (const file of files) {
998
+ if (!file.endsWith(".md")) continue;
999
+ const filepath = path.join(dirPath, file);
1000
+ const stat = await fs4.stat(filepath);
1001
+ if (!stat.isFile()) continue;
1002
+ const content = await fs4.readFile(filepath, "utf-8");
1003
+ const { data: frontmatter } = matter(content);
1004
+ const name = path.basename(file, ".md");
1005
+ const trigger = this.extractTrigger(frontmatter);
1006
+ const alwaysApply = this.isAlwaysTrigger(trigger);
1007
+ items.push({
1008
+ type,
1009
+ name,
1010
+ filepath,
1011
+ relativePath: path.relative(this.projectPath, filepath),
1012
+ description: frontmatter.description,
1013
+ globs: this.extractGlobs(frontmatter),
1014
+ trigger,
1015
+ alwaysApply
1016
+ });
1017
+ }
1018
+ return items;
1019
+ }
1020
+ /**
1021
+ * Generate stub file content with @ reference
1022
+ */
1023
+ generateStubContent(ide, sourceItem) {
1024
+ const config = IDE_MIGRATION_CONFIGS[ide];
1025
+ if (!config) throw new Error(`Unknown IDE: ${ide}`);
1026
+ const frontmatter = config.generateFrontmatter({
1027
+ description: sourceItem.description,
1028
+ globs: sourceItem.globs,
1029
+ trigger: sourceItem.trigger,
1030
+ alwaysApply: sourceItem.alwaysApply,
1031
+ sourceFile: sourceItem.relativePath
1032
+ });
1033
+ const reference = `@${sourceItem.relativePath}`;
1034
+ if (frontmatter) {
1035
+ return `${frontmatter}
1036
+
1037
+ ${reference}
1038
+ `;
1039
+ }
1040
+ return `${reference}
1041
+ `;
1042
+ }
1043
+ /**
1044
+ * Migrate content to specific IDEs
1045
+ */
1046
+ async migrate(ides, contentTypes, content, onProgress) {
1047
+ const results = [];
1048
+ for (const ide of ides) {
1049
+ const config = IDE_MIGRATION_CONFIGS[ide];
1050
+ if (!config) continue;
1051
+ for (const type of contentTypes) {
1052
+ const items = type === "rules" ? content.rules : type === "workflows" ? content.workflows : content.commands;
1053
+ for (const item of items) {
1054
+ const result = await this.migrateItem(ide, config, item);
1055
+ results.push(result);
1056
+ onProgress?.(result);
1057
+ }
1058
+ }
1059
+ }
1060
+ return results;
1061
+ }
1062
+ /**
1063
+ * Migrate a single item
1064
+ */
1065
+ async migrateItem(ide, config, item) {
1066
+ try {
1067
+ const targetPath = this.getTargetPath(config, item);
1068
+ if (!targetPath) {
1069
+ return {
1070
+ source: item,
1071
+ targetIDE: ide,
1072
+ targetPath: "",
1073
+ status: "skipped",
1074
+ error: `IDE ${config.id} does not support ${item.type}`
1075
+ };
1076
+ }
1077
+ const targetDir = path.dirname(targetPath);
1078
+ await fs4.mkdir(targetDir, { recursive: true });
1079
+ let status = "created";
1080
+ try {
1081
+ await fs4.access(targetPath);
1082
+ status = "updated";
1083
+ } catch {
1084
+ }
1085
+ const stubContent = this.generateStubContent(ide, item);
1086
+ await fs4.writeFile(targetPath, stubContent, "utf-8");
1087
+ return {
1088
+ source: item,
1089
+ targetIDE: ide,
1090
+ targetPath,
1091
+ status
1092
+ };
1093
+ } catch (error) {
1094
+ return {
1095
+ source: item,
1096
+ targetIDE: ide,
1097
+ targetPath: "",
1098
+ status: "error",
1099
+ error: error instanceof Error ? error.message : "Unknown error"
1100
+ };
1101
+ }
1102
+ }
1103
+ /**
1104
+ * Get target file path for an item
1105
+ */
1106
+ getTargetPath(config, item) {
1107
+ let contentPath = null;
1108
+ switch (item.type) {
1109
+ case "rules":
1110
+ contentPath = config.rulesPath;
1111
+ break;
1112
+ case "workflows":
1113
+ contentPath = config.workflowsPath ?? config.commandsPath;
1114
+ break;
1115
+ case "commands":
1116
+ contentPath = config.commandsPath;
1117
+ break;
1118
+ }
1119
+ if (!contentPath) {
1120
+ return null;
1121
+ }
1122
+ const filename = `${item.name}${config.fileExtension}`;
1123
+ return path.join(this.projectPath, config.basePath, contentPath, filename);
1124
+ }
1125
+ // Helper methods
1126
+ /**
1127
+ * Extract trigger from frontmatter
1128
+ * Jai1 source format uses 'trigger' field (Windsurf/Antigravity compatible)
1129
+ */
1130
+ extractTrigger(frontmatter) {
1131
+ const trigger = frontmatter.trigger;
1132
+ if (typeof trigger === "string") {
1133
+ return trigger;
1134
+ }
1135
+ if (frontmatter.alwaysApply === true) {
1136
+ return "always_on";
1137
+ }
1138
+ const globs = this.extractGlobs(frontmatter);
1139
+ if (globs && globs.length > 0) {
1140
+ return globs[0];
1141
+ }
1142
+ return void 0;
1143
+ }
1144
+ /**
1145
+ * Extract globs from frontmatter
1146
+ */
1147
+ extractGlobs(frontmatter) {
1148
+ const globs = frontmatter.globs;
1149
+ if (typeof globs === "string") return [globs];
1150
+ if (Array.isArray(globs)) return globs;
1151
+ return void 0;
1152
+ }
1153
+ /**
1154
+ * Check if trigger indicates "always apply"
1155
+ */
1156
+ isAlwaysTrigger(trigger) {
1157
+ return trigger === "always" || trigger === "always_on";
1158
+ }
1159
+ };
1160
+
775
1161
  // src/ui/shared/ProgressBar.tsx
776
1162
  import React from "react";
777
1163
  import { Box, Text } from "ink";
@@ -888,11 +1274,15 @@ var UnifiedApplyApp = ({
888
1274
  const [selectedPackageIndex, setSelectedPackageIndex] = useState(0);
889
1275
  const [installProgress, setInstallProgress] = useState([]);
890
1276
  const [installStats, setInstallStats] = useState({ total: 0, completed: 0, added: 0, updated: 0, failed: 0 });
1277
+ const [availableIdes, setAvailableIdes] = useState([]);
1278
+ const [selectedIdes, setSelectedIdes] = useState(/* @__PURE__ */ new Set());
1279
+ const [ideCursorIndex, setIdeCursorIndex] = useState(0);
1280
+ const [syncProgress, setSyncProgress] = useState([]);
1281
+ const [syncStats, setSyncStats] = useState({ total: 0, completed: 0, created: 0, updated: 0, skipped: 0, errors: 0 });
891
1282
  const service = new ComponentsService();
892
1283
  useEffect(() => {
893
1284
  const loadData = async () => {
894
1285
  try {
895
- setLoading(true);
896
1286
  const [comps, tagList, installed] = await Promise.all([
897
1287
  service.list(config),
898
1288
  service.listTags(config),
@@ -901,28 +1291,71 @@ var UnifiedApplyApp = ({
901
1291
  setComponents(comps);
902
1292
  setTags(tagList);
903
1293
  setInstalledPaths(new Set(Object.keys(installed)));
1294
+ setLoading(false);
904
1295
  } catch (err) {
905
- setError(err instanceof Error ? err.message : "Failed to load data");
906
- } finally {
1296
+ setError(err instanceof Error ? err.message : "Failed to load");
907
1297
  setLoading(false);
908
1298
  }
909
1299
  };
910
1300
  loadData();
1301
+ setAvailableIdes(getMigrationIDEs());
911
1302
  }, []);
912
1303
  const filteredComponents = useMemo(() => {
913
1304
  if (!searchQuery.trim()) return components;
914
1305
  const query = searchQuery.toLowerCase();
915
1306
  return components.filter(
916
- (c) => c.filepath.toLowerCase().includes(query) || c.name.toLowerCase().includes(query) || c.tags && c.tags.some((t) => t.toLowerCase().includes(query))
1307
+ (c) => c.filepath.toLowerCase().includes(query) || c.tags?.some((t) => t.toLowerCase().includes(query))
917
1308
  );
918
1309
  }, [components, searchQuery]);
919
1310
  useInput((input4, key) => {
920
- if (viewState === "summary") {
1311
+ if (viewState === "done") {
921
1312
  if (key.return || input4 === "q" || key.escape) {
922
1313
  onExit();
923
1314
  }
924
1315
  return;
925
1316
  }
1317
+ if (viewState === "summary") {
1318
+ if (key.return) {
1319
+ setViewState("ide-sync");
1320
+ } else if (input4 === "s" || input4 === "q" || key.escape) {
1321
+ onExit();
1322
+ }
1323
+ return;
1324
+ }
1325
+ if (viewState === "ide-sync") {
1326
+ if (key.upArrow) {
1327
+ setIdeCursorIndex((prev) => Math.max(0, prev - 1));
1328
+ } else if (key.downArrow) {
1329
+ setIdeCursorIndex((prev) => Math.min(availableIdes.length - 1, prev + 1));
1330
+ } else if (input4 === " ") {
1331
+ const ide = availableIdes[ideCursorIndex];
1332
+ if (ide) {
1333
+ setSelectedIdes((prev) => {
1334
+ const next = new Set(prev);
1335
+ if (next.has(ide)) {
1336
+ next.delete(ide);
1337
+ } else {
1338
+ next.add(ide);
1339
+ }
1340
+ return next;
1341
+ });
1342
+ }
1343
+ } else if (input4 === "a") {
1344
+ setSelectedIdes(new Set(availableIdes));
1345
+ } else if (input4 === "c") {
1346
+ setSelectedIdes(/* @__PURE__ */ new Set());
1347
+ } else if (key.return) {
1348
+ if (selectedIdes.size > 0) {
1349
+ handleIdeSync();
1350
+ }
1351
+ } else if (input4 === "s" || input4 === "q" || key.escape) {
1352
+ onExit();
1353
+ }
1354
+ return;
1355
+ }
1356
+ if (viewState === "syncing") {
1357
+ return;
1358
+ }
926
1359
  if (viewState === "installing") {
927
1360
  return;
928
1361
  }
@@ -1037,6 +1470,44 @@ var UnifiedApplyApp = ({
1037
1470
  }
1038
1471
  setViewState("summary");
1039
1472
  };
1473
+ const handleIdeSync = async () => {
1474
+ setViewState("syncing");
1475
+ const migrateService = new MigrateIdeService();
1476
+ const content = await migrateService.scanJai1Content();
1477
+ if (content.totalCount === 0) {
1478
+ setSyncStats({ total: 0, completed: 0, created: 0, updated: 0, skipped: 0, errors: 0 });
1479
+ setViewState("done");
1480
+ return;
1481
+ }
1482
+ const selectedIdeList = Array.from(selectedIdes);
1483
+ const contentTypes = ["rules", "workflows"];
1484
+ const totalItems = contentTypes.reduce((sum, type) => {
1485
+ return sum + (type === "rules" ? content.rules.length : type === "workflows" ? content.workflows.length : content.commands.length);
1486
+ }, 0);
1487
+ const totalFiles = totalItems * selectedIdeList.length;
1488
+ setSyncStats({ total: totalFiles, completed: 0, created: 0, updated: 0, skipped: 0, errors: 0 });
1489
+ let created = 0, updated = 0, skipped = 0, errors = 0;
1490
+ let completed = 0;
1491
+ const results = await migrateService.migrate(
1492
+ selectedIdeList,
1493
+ contentTypes,
1494
+ content,
1495
+ (result) => {
1496
+ completed++;
1497
+ if (result.status === "created") created++;
1498
+ else if (result.status === "updated") updated++;
1499
+ else if (result.status === "skipped") skipped++;
1500
+ else if (result.status === "error") errors++;
1501
+ setSyncProgress((prev) => [...prev.slice(-7), {
1502
+ targetPath: result.targetPath || result.source.relativePath,
1503
+ status: result.status === "error" ? "error" : "success",
1504
+ error: result.error
1505
+ }]);
1506
+ setSyncStats({ total: totalFiles, completed, created, updated, skipped, errors });
1507
+ }
1508
+ );
1509
+ setViewState("done");
1510
+ };
1040
1511
  if (loading) {
1041
1512
  return /* @__PURE__ */ React3.createElement(Box2, { padding: 1 }, /* @__PURE__ */ React3.createElement(Text3, { color: "cyan" }, /* @__PURE__ */ React3.createElement(Spinner, { type: "dots" }), " \u0110ang t\u1EA3i components..."));
1042
1513
  }
@@ -1047,7 +1518,21 @@ var UnifiedApplyApp = ({
1047
1518
  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"))));
1048
1519
  }
1049
1520
  if (viewState === "summary") {
1050
- 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")));
1521
+ 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")));
1522
+ }
1523
+ if (viewState === "ide-sync") {
1524
+ 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) => {
1525
+ const ideConfig = IDE_MIGRATION_CONFIGS[ide];
1526
+ const isCursor = i === ideCursorIndex;
1527
+ const isChecked = selectedIdes.has(ide);
1528
+ return /* @__PURE__ */ React3.createElement(Box2, { key: ide }, /* @__PURE__ */ React3.createElement(Text3, { color: isCursor ? "cyan" : "white" }, isCursor ? "\u276F " : " ", isChecked ? "[\u2713]" : "[ ]", " ", ideConfig.icon, " ", ideConfig.name));
1529
+ })), 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")));
1530
+ }
1531
+ if (viewState === "syncing") {
1532
+ 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")))));
1533
+ }
1534
+ if (viewState === "done") {
1535
+ 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")));
1051
1536
  }
1052
1537
  const visibleComponents = filteredComponents.slice(0, 12);
1053
1538
  const hasMore = filteredComponents.length > 12;
@@ -1155,6 +1640,8 @@ async function nonInteractiveApply(config, items, options) {
1155
1640
  console.log(`
1156
1641
  \u2705 Complete: ${added} added, ${updated} updated, ${skipped} skipped`);
1157
1642
  console.log(`\u{1F4C1} Location: ${targetDir}`);
1643
+ console.log(`
1644
+ \u{1F4A1} Next step: Run "jai1 ide sync" to sync content to your IDE(s)`);
1158
1645
  }
1159
1646
 
1160
1647
  // src/commands/update.ts
@@ -1624,9 +2111,9 @@ var DetailView = ({ item, scrollPosition: initialScroll, onBack }) => {
1624
2111
  };
1625
2112
 
1626
2113
  // src/services/context-scanner.service.ts
1627
- import { promises as fs4 } from "fs";
1628
- import path from "path";
1629
- import matter from "gray-matter";
2114
+ import { promises as fs5 } from "fs";
2115
+ import path2 from "path";
2116
+ import matter2 from "gray-matter";
1630
2117
 
1631
2118
  // src/constants/ide-configs.ts
1632
2119
  var IDE_CONFIGS = {
@@ -1789,8 +2276,8 @@ var ContextScannerService = class {
1789
2276
  if (!relativePath) return [];
1790
2277
  let dirPath;
1791
2278
  if (ide === "jai1") {
1792
- const jai1Path = path.join(this.projectPath, ".jai1", relativePath);
1793
- const frameworkPath = path.join(this.projectPath, "packages/jai1-framework", relativePath);
2279
+ const jai1Path = path2.join(this.projectPath, ".jai1", relativePath);
2280
+ const frameworkPath = path2.join(this.projectPath, "packages/jai1-framework", relativePath);
1794
2281
  if (await this.pathExists(jai1Path)) {
1795
2282
  dirPath = jai1Path;
1796
2283
  } else if (await this.pathExists(frameworkPath)) {
@@ -1799,9 +2286,9 @@ var ContextScannerService = class {
1799
2286
  return [];
1800
2287
  }
1801
2288
  } else {
1802
- dirPath = path.join(this.projectPath, config.basePath, relativePath);
2289
+ dirPath = path2.join(this.projectPath, config.basePath, relativePath);
1803
2290
  try {
1804
- await fs4.access(dirPath);
2291
+ await fs5.access(dirPath);
1805
2292
  } catch {
1806
2293
  return [];
1807
2294
  }
@@ -1812,10 +2299,10 @@ var ContextScannerService = class {
1812
2299
  const skillItems = await this.scanSkills(dirPath, ide);
1813
2300
  items.push(...skillItems);
1814
2301
  } else {
1815
- const files = await fs4.readdir(dirPath);
2302
+ const files = await fs5.readdir(dirPath);
1816
2303
  for (const file of files) {
1817
- const filepath = path.join(dirPath, file);
1818
- const stat = await fs4.stat(filepath);
2304
+ const filepath = path2.join(dirPath, file);
2305
+ const stat = await fs5.stat(filepath);
1819
2306
  if (!stat.isFile()) continue;
1820
2307
  const matchesExtension = extensions.some((ext) => file.endsWith(ext));
1821
2308
  if (!matchesExtension) continue;
@@ -1835,20 +2322,20 @@ var ContextScannerService = class {
1835
2322
  async scanSkills(skillsDir, ide) {
1836
2323
  const items = [];
1837
2324
  try {
1838
- const entries = await fs4.readdir(skillsDir, { withFileTypes: true });
2325
+ const entries = await fs5.readdir(skillsDir, { withFileTypes: true });
1839
2326
  for (const entry of entries) {
1840
2327
  if (!entry.isDirectory()) continue;
1841
- const skillPath = path.join(skillsDir, entry.name);
1842
- const skillFilePath = path.join(skillPath, "SKILL.md");
2328
+ const skillPath = path2.join(skillsDir, entry.name);
2329
+ const skillFilePath = path2.join(skillPath, "SKILL.md");
1843
2330
  try {
1844
- await fs4.access(skillFilePath);
2331
+ await fs5.access(skillFilePath);
1845
2332
  } catch {
1846
2333
  continue;
1847
2334
  }
1848
2335
  const item = await this.parseContextItem(skillFilePath, ide, "skills");
1849
- item.hasScripts = await this.pathExists(path.join(skillPath, "scripts"));
1850
- item.hasReferences = await this.pathExists(path.join(skillPath, "references"));
1851
- item.hasAssets = await this.pathExists(path.join(skillPath, "assets"));
2336
+ item.hasScripts = await this.pathExists(path2.join(skillPath, "scripts"));
2337
+ item.hasReferences = await this.pathExists(path2.join(skillPath, "references"));
2338
+ item.hasAssets = await this.pathExists(path2.join(skillPath, "assets"));
1852
2339
  items.push(item);
1853
2340
  }
1854
2341
  } catch (error) {
@@ -1860,9 +2347,9 @@ var ContextScannerService = class {
1860
2347
  * Parse a context item from file
1861
2348
  */
1862
2349
  async parseContextItem(filepath, ide, type) {
1863
- const content = await fs4.readFile(filepath, "utf-8");
1864
- const stat = await fs4.stat(filepath);
1865
- const { data: frontmatter, content: bodyContent } = matter(content);
2350
+ const content = await fs5.readFile(filepath, "utf-8");
2351
+ const stat = await fs5.stat(filepath);
2352
+ const { data: frontmatter, content: bodyContent } = matter2(content);
1866
2353
  const config = IDE_CONFIGS[ide];
1867
2354
  const description = frontmatter[config.frontmatterSchema.descriptionField];
1868
2355
  let globs;
@@ -1878,9 +2365,9 @@ var ContextScannerService = class {
1878
2365
  globs = triggerValue;
1879
2366
  }
1880
2367
  const { previewLines, lineCount } = this.extractPreview(bodyContent, 20);
1881
- const relativePath = path.relative(this.projectPath, filepath);
1882
- const id = `${ide}-${type}-${path.basename(filepath, path.extname(filepath))}`;
1883
- const name = frontmatter.name || path.basename(filepath, path.extname(filepath));
2368
+ const relativePath = path2.relative(this.projectPath, filepath);
2369
+ const id = `${ide}-${type}-${path2.basename(filepath, path2.extname(filepath))}`;
2370
+ const name = frontmatter.name || path2.basename(filepath, path2.extname(filepath));
1884
2371
  return {
1885
2372
  id,
1886
2373
  ide,
@@ -1916,13 +2403,13 @@ var ContextScannerService = class {
1916
2403
  for (const ide of getAllIDEs()) {
1917
2404
  const config = IDE_CONFIGS[ide];
1918
2405
  if (ide === "jai1") {
1919
- const jai1Path = path.join(this.projectPath, ".jai1");
1920
- const frameworkPath = path.join(this.projectPath, "packages/jai1-framework");
2406
+ const jai1Path = path2.join(this.projectPath, ".jai1");
2407
+ const frameworkPath = path2.join(this.projectPath, "packages/jai1-framework");
1921
2408
  if (await this.pathExists(jai1Path) || await this.pathExists(frameworkPath)) {
1922
2409
  ides.push(ide);
1923
2410
  }
1924
2411
  } else {
1925
- const idePath = path.join(this.projectPath, config.basePath);
2412
+ const idePath = path2.join(this.projectPath, config.basePath);
1926
2413
  if (await this.pathExists(idePath)) {
1927
2414
  ides.push(ide);
1928
2415
  }
@@ -1935,7 +2422,7 @@ var ContextScannerService = class {
1935
2422
  */
1936
2423
  async pathExists(filepath) {
1937
2424
  try {
1938
- await fs4.access(filepath);
2425
+ await fs5.access(filepath);
1939
2426
  return true;
1940
2427
  } catch {
1941
2428
  return false;
@@ -2163,8 +2650,8 @@ async function printStats() {
2163
2650
  // src/commands/ide/setup.ts
2164
2651
  import { Command as Command7 } from "commander";
2165
2652
  import { checkbox, confirm as confirm2, select } from "@inquirer/prompts";
2166
- import fs5 from "fs/promises";
2167
- import path2 from "path";
2653
+ import fs6 from "fs/promises";
2654
+ import path3 from "path";
2168
2655
  import { existsSync } from "fs";
2169
2656
  var PERFORMANCE_GROUPS = {
2170
2657
  telemetry: {
@@ -2354,436 +2841,124 @@ async function interactiveMode() {
2354
2841
  { name: "\u{1F504} Reset to defaults", value: "reset" }
2355
2842
  ]
2356
2843
  });
2357
- if (action === "max") {
2358
- const allGroups = Object.keys(PERFORMANCE_GROUPS);
2359
- await applyGroups(allGroups, "enable");
2360
- } else if (action === "reset") {
2361
- await resetSettings([]);
2362
- } else {
2363
- await selectGroupsToApply(action);
2364
- }
2365
- }
2366
- async function selectGroupsToApply(action) {
2367
- const choices = Object.entries(PERFORMANCE_GROUPS).map(([key, group]) => ({
2368
- name: `${group.name} - ${group.description}`,
2369
- value: key
2370
- }));
2371
- try {
2372
- const selectedGroups = await checkbox({
2373
- message: `Select groups to ${action} (SPACE to select, ENTER to confirm):`,
2374
- choices
2375
- });
2376
- if (selectedGroups.length === 0) {
2377
- console.log("\n\u26A0\uFE0F No groups selected!");
2378
- console.log(" \u{1F4A1} Tip: Press SPACE to select at least 1 group before pressing ENTER.");
2379
- return;
2380
- }
2381
- await applyGroups(selectedGroups, action);
2382
- } catch {
2383
- console.log("\n\u274C Operation cancelled.");
2384
- }
2385
- }
2386
- async function applyGroups(groupKeys, action) {
2387
- const vscodeDir = path2.join(process.cwd(), ".vscode");
2388
- const settingsPath = path2.join(vscodeDir, "settings.json");
2389
- const invalidGroups = groupKeys.filter((key) => !PERFORMANCE_GROUPS[key]);
2390
- if (invalidGroups.length > 0) {
2391
- console.log(`
2392
- \u274C Invalid groups: ${invalidGroups.join(", ")}`);
2393
- console.log(' \u{1F4A1} Run "jai1 ide setup list" to see available groups.');
2394
- return;
2395
- }
2396
- if (!existsSync(vscodeDir)) {
2397
- await fs5.mkdir(vscodeDir, { recursive: true });
2398
- console.log("\u{1F4C1} Created .vscode/ directory");
2399
- }
2400
- let currentSettings = {};
2401
- if (existsSync(settingsPath)) {
2402
- try {
2403
- const content = await fs5.readFile(settingsPath, "utf-8");
2404
- currentSettings = JSON.parse(content);
2405
- console.log("\u{1F4C4} Read current settings from settings.json");
2406
- } catch {
2407
- console.warn("\u26A0\uFE0F Cannot read settings.json (may contain comments).");
2408
- const confirmOverwrite = await confirm2({
2409
- message: "Overwrite current settings.json file?",
2410
- default: false
2411
- });
2412
- if (!confirmOverwrite) {
2413
- console.log("\u274C Operation cancelled.");
2414
- return;
2415
- }
2416
- currentSettings = {};
2417
- }
2418
- }
2419
- const newSettings = { ...currentSettings };
2420
- console.log(`
2421
- \u{1F4DD} ${action === "enable" ? "Enabling" : "Disabling"} groups:
2422
- `);
2423
- for (const key of groupKeys) {
2424
- const group = PERFORMANCE_GROUPS[key];
2425
- console.log(` ${action === "enable" ? "\u2713" : "\u2717"} ${group.name}`);
2426
- if (action === "enable") {
2427
- for (const [settingKey, settingValue] of Object.entries(group.settings)) {
2428
- if (typeof settingValue === "object" && settingValue !== null && !Array.isArray(settingValue) && typeof newSettings[settingKey] === "object" && newSettings[settingKey] !== null) {
2429
- newSettings[settingKey] = {
2430
- ...newSettings[settingKey],
2431
- ...settingValue
2432
- };
2433
- } else {
2434
- newSettings[settingKey] = settingValue;
2435
- }
2436
- }
2437
- } else {
2438
- for (const settingKey of Object.keys(group.settings)) {
2439
- delete newSettings[settingKey];
2440
- }
2441
- }
2442
- }
2443
- await fs5.writeFile(settingsPath, JSON.stringify(newSettings, null, 2));
2444
- console.log(`
2445
- \u2705 Updated IDE settings at: ${settingsPath}`);
2446
- console.log("\u{1F4A1} Tip: Restart your IDE to apply changes.");
2447
- }
2448
- async function resetSettings(groupKeys) {
2449
- const vscodeDir = path2.join(process.cwd(), ".vscode");
2450
- const settingsPath = path2.join(vscodeDir, "settings.json");
2451
- if (!existsSync(settingsPath)) {
2452
- console.log("\n\u26A0\uFE0F No settings.json file found");
2453
- return;
2454
- }
2455
- const confirmReset = await confirm2({
2456
- message: groupKeys.length === 0 ? "Reset ALL settings to default (delete entire file)?" : `Reset groups: ${groupKeys.join(", ")}?`,
2457
- default: false
2458
- });
2459
- if (!confirmReset) {
2460
- console.log("\u274C Operation cancelled.");
2461
- return;
2462
- }
2463
- if (groupKeys.length === 0) {
2464
- await fs5.unlink(settingsPath);
2465
- console.log("\n\u2705 Deleted settings.json file");
2466
- } else {
2467
- await applyGroups(groupKeys, "disable");
2468
- }
2469
- console.log("\u{1F4A1} Tip: Restart your IDE to apply changes.");
2470
- }
2471
-
2472
- // src/commands/ide/sync.ts
2473
- import { Command as Command8 } from "commander";
2474
- import { checkbox as checkbox2, confirm as confirm3 } from "@inquirer/prompts";
2475
-
2476
- // src/services/migrate-ide.service.ts
2477
- import { promises as fs6 } from "fs";
2478
- import path3 from "path";
2479
- import matter2 from "gray-matter";
2480
-
2481
- // src/constants/ide-migration-configs.ts
2482
- var IDE_MIGRATION_CONFIGS = {
2483
- cursor: {
2484
- id: "cursor",
2485
- name: "Cursor",
2486
- icon: "\u{1F52E}",
2487
- basePath: ".cursor",
2488
- rulesPath: "rules",
2489
- workflowsPath: null,
2490
- commandsPath: "commands",
2491
- fileExtension: ".mdc",
2492
- generateFrontmatter: (opts) => {
2493
- const lines = ["---"];
2494
- if (opts.description) {
2495
- lines.push(`description: ${opts.description}`);
2496
- }
2497
- if (opts.globs && opts.globs.length > 0) {
2498
- lines.push(`globs: ${opts.globs.join(", ")}`);
2499
- }
2500
- lines.push(`alwaysApply: ${opts.alwaysApply}`);
2501
- lines.push("---");
2502
- return lines.join("\n");
2503
- }
2504
- },
2505
- windsurf: {
2506
- id: "windsurf",
2507
- name: "Windsurf",
2508
- icon: "\u{1F3C4}",
2509
- basePath: ".windsurf",
2510
- rulesPath: "rules",
2511
- workflowsPath: "workflows",
2512
- commandsPath: null,
2513
- fileExtension: ".md",
2514
- generateFrontmatter: (opts) => {
2515
- const trigger = opts.alwaysApply ? "always" : "glob";
2516
- return `---
2517
- trigger: ${trigger}
2518
- ---`;
2519
- }
2520
- },
2521
- antigravity: {
2522
- id: "antigravity",
2523
- name: "Antigravity",
2524
- icon: "\u{1F680}",
2525
- basePath: ".agent",
2526
- rulesPath: "rules",
2527
- workflowsPath: "workflows",
2528
- commandsPath: null,
2529
- fileExtension: ".md",
2530
- generateFrontmatter: (opts) => {
2531
- const trigger = opts.alwaysApply ? "always" : "glob";
2532
- return `---
2533
- trigger: ${trigger}
2534
- ---`;
2535
- }
2536
- },
2537
- claudecode: {
2538
- id: "claudecode",
2539
- name: "Claude Code",
2540
- icon: "\u{1F916}",
2541
- basePath: ".claude",
2542
- rulesPath: "rules",
2543
- workflowsPath: null,
2544
- commandsPath: "commands",
2545
- fileExtension: ".md",
2546
- generateFrontmatter: (opts) => {
2547
- const lines = ["---"];
2548
- if (opts.description) {
2549
- lines.push(`description: ${opts.description}`);
2550
- }
2551
- if (opts.globs && opts.globs.length > 0) {
2552
- lines.push(`paths: ${opts.globs.join(", ")}`);
2553
- }
2554
- lines.push("---");
2555
- return lines.join("\n");
2556
- }
2557
- },
2558
- opencode: {
2559
- id: "opencode",
2560
- name: "OpenCode",
2561
- icon: "\u{1F4BB}",
2562
- basePath: ".opencode",
2563
- rulesPath: "rules",
2564
- workflowsPath: "workflows",
2565
- commandsPath: null,
2566
- fileExtension: ".md",
2567
- generateFrontmatter: (opts) => {
2568
- const trigger = opts.alwaysApply ? "always" : "glob";
2569
- return `---
2570
- trigger: ${trigger}
2571
- ---`;
2572
- }
2844
+ if (action === "max") {
2845
+ const allGroups = Object.keys(PERFORMANCE_GROUPS);
2846
+ await applyGroups(allGroups, "enable");
2847
+ } else if (action === "reset") {
2848
+ await resetSettings([]);
2849
+ } else {
2850
+ await selectGroupsToApply(action);
2573
2851
  }
2574
- };
2575
- function getMigrationIDEs() {
2576
- return Object.keys(IDE_MIGRATION_CONFIGS);
2577
2852
  }
2578
-
2579
- // src/services/migrate-ide.service.ts
2580
- var MigrateIdeService = class {
2581
- projectPath;
2582
- jai1Path;
2583
- constructor(projectPath = process.cwd()) {
2584
- this.projectPath = projectPath;
2585
- this.jai1Path = path3.join(projectPath, ".jai1");
2853
+ async function selectGroupsToApply(action) {
2854
+ const choices = Object.entries(PERFORMANCE_GROUPS).map(([key, group]) => ({
2855
+ name: `${group.name} - ${group.description}`,
2856
+ value: key
2857
+ }));
2858
+ try {
2859
+ const selectedGroups = await checkbox({
2860
+ message: `Select groups to ${action} (SPACE to select, ENTER to confirm):`,
2861
+ choices
2862
+ });
2863
+ if (selectedGroups.length === 0) {
2864
+ console.log("\n\u26A0\uFE0F No groups selected!");
2865
+ console.log(" \u{1F4A1} Tip: Press SPACE to select at least 1 group before pressing ENTER.");
2866
+ return;
2867
+ }
2868
+ await applyGroups(selectedGroups, action);
2869
+ } catch {
2870
+ console.log("\n\u274C Operation cancelled.");
2586
2871
  }
2587
- /**
2588
- * Scan .jai1/ directory for content
2589
- */
2590
- async scanJai1Content() {
2591
- const manualRules = await this.scanContentType("rules");
2592
- const presetRules = await this.scanRulePreset();
2593
- const rules = [...presetRules, ...manualRules];
2594
- const workflows = await this.scanContentType("workflows");
2595
- const commands = [];
2596
- return {
2597
- rules,
2598
- workflows,
2599
- commands,
2600
- totalCount: rules.length + workflows.length + commands.length
2601
- };
2872
+ }
2873
+ async function applyGroups(groupKeys, action) {
2874
+ const vscodeDir = path3.join(process.cwd(), ".vscode");
2875
+ const settingsPath = path3.join(vscodeDir, "settings.json");
2876
+ const invalidGroups = groupKeys.filter((key) => !PERFORMANCE_GROUPS[key]);
2877
+ if (invalidGroups.length > 0) {
2878
+ console.log(`
2879
+ \u274C Invalid groups: ${invalidGroups.join(", ")}`);
2880
+ console.log(' \u{1F4A1} Run "jai1 ide setup list" to see available groups.');
2881
+ return;
2602
2882
  }
2603
- /**
2604
- * Scan .jai1/rule-preset/ directory for rule preset files
2605
- */
2606
- async scanRulePreset() {
2607
- const items = [];
2608
- const presetDir = path3.join(this.jai1Path, "rule-preset");
2609
- try {
2610
- await fs6.access(presetDir);
2611
- } catch {
2612
- return items;
2613
- }
2614
- const files = await fs6.readdir(presetDir);
2615
- for (const file of files) {
2616
- if (!file.endsWith(".mdc")) continue;
2617
- const filepath = path3.join(presetDir, file);
2618
- const stat = await fs6.stat(filepath);
2619
- if (!stat.isFile()) continue;
2620
- const content = await fs6.readFile(filepath, "utf-8");
2621
- let frontmatter = {};
2622
- try {
2623
- const { data } = matter2(content);
2624
- frontmatter = data;
2625
- } catch {
2626
- }
2627
- items.push({
2628
- type: "rules",
2629
- name: path3.basename(file, ".mdc"),
2630
- filepath,
2631
- relativePath: path3.relative(this.projectPath, filepath),
2632
- description: frontmatter.description,
2633
- globs: this.extractGlobs(frontmatter),
2634
- alwaysApply: this.extractAlwaysApply(frontmatter)
2635
- });
2636
- }
2637
- return items;
2883
+ if (!existsSync(vscodeDir)) {
2884
+ await fs6.mkdir(vscodeDir, { recursive: true });
2885
+ console.log("\u{1F4C1} Created .vscode/ directory");
2638
2886
  }
2639
- /**
2640
- * Scan a specific content type
2641
- */
2642
- async scanContentType(type) {
2643
- const items = [];
2644
- const dirPath = path3.join(this.jai1Path, type);
2887
+ let currentSettings = {};
2888
+ if (existsSync(settingsPath)) {
2645
2889
  try {
2646
- await fs6.access(dirPath);
2890
+ const content = await fs6.readFile(settingsPath, "utf-8");
2891
+ currentSettings = JSON.parse(content);
2892
+ console.log("\u{1F4C4} Read current settings from settings.json");
2647
2893
  } catch {
2648
- return items;
2649
- }
2650
- const files = await fs6.readdir(dirPath);
2651
- for (const file of files) {
2652
- if (!file.endsWith(".md")) continue;
2653
- const filepath = path3.join(dirPath, file);
2654
- const stat = await fs6.stat(filepath);
2655
- if (!stat.isFile()) continue;
2656
- const content = await fs6.readFile(filepath, "utf-8");
2657
- const { data: frontmatter } = matter2(content);
2658
- items.push({
2659
- type,
2660
- name: path3.basename(file, ".md"),
2661
- filepath,
2662
- relativePath: path3.relative(this.projectPath, filepath),
2663
- description: frontmatter.description,
2664
- globs: this.extractGlobs(frontmatter),
2665
- alwaysApply: this.extractAlwaysApply(frontmatter)
2894
+ console.warn("\u26A0\uFE0F Cannot read settings.json (may contain comments).");
2895
+ const confirmOverwrite = await confirm2({
2896
+ message: "Overwrite current settings.json file?",
2897
+ default: false
2666
2898
  });
2667
- }
2668
- return items;
2669
- }
2670
- /**
2671
- * Generate stub file content with @ reference
2672
- */
2673
- generateStubContent(ide, sourceItem) {
2674
- const config = IDE_MIGRATION_CONFIGS[ide];
2675
- if (!config) throw new Error(`Unknown IDE: ${ide}`);
2676
- const frontmatter = config.generateFrontmatter({
2677
- description: sourceItem.description,
2678
- globs: sourceItem.globs,
2679
- alwaysApply: sourceItem.alwaysApply,
2680
- sourceFile: sourceItem.relativePath
2681
- });
2682
- const reference = `@${sourceItem.relativePath}`;
2683
- if (frontmatter) {
2684
- return `${frontmatter}
2685
-
2686
- ${reference}
2687
- `;
2688
- }
2689
- return `${reference}
2690
- `;
2691
- }
2692
- /**
2693
- * Migrate content to specific IDEs
2694
- */
2695
- async migrate(ides, contentTypes, content, onProgress) {
2696
- const results = [];
2697
- for (const ide of ides) {
2698
- const config = IDE_MIGRATION_CONFIGS[ide];
2699
- if (!config) continue;
2700
- for (const type of contentTypes) {
2701
- const items = type === "rules" ? content.rules : type === "workflows" ? content.workflows : content.commands;
2702
- for (const item of items) {
2703
- const result = await this.migrateItem(ide, config, item);
2704
- results.push(result);
2705
- onProgress?.(result);
2706
- }
2899
+ if (!confirmOverwrite) {
2900
+ console.log("\u274C Operation cancelled.");
2901
+ return;
2707
2902
  }
2903
+ currentSettings = {};
2708
2904
  }
2709
- return results;
2710
2905
  }
2711
- /**
2712
- * Migrate a single item
2713
- */
2714
- async migrateItem(ide, config, item) {
2715
- try {
2716
- const targetPath = this.getTargetPath(config, item);
2717
- if (!targetPath) {
2718
- return {
2719
- source: item,
2720
- targetIDE: ide,
2721
- targetPath: "",
2722
- status: "skipped",
2723
- error: `IDE ${config.id} does not support ${item.type}`
2724
- };
2906
+ const newSettings = { ...currentSettings };
2907
+ console.log(`
2908
+ \u{1F4DD} ${action === "enable" ? "Enabling" : "Disabling"} groups:
2909
+ `);
2910
+ for (const key of groupKeys) {
2911
+ const group = PERFORMANCE_GROUPS[key];
2912
+ console.log(` ${action === "enable" ? "\u2713" : "\u2717"} ${group.name}`);
2913
+ if (action === "enable") {
2914
+ for (const [settingKey, settingValue] of Object.entries(group.settings)) {
2915
+ if (typeof settingValue === "object" && settingValue !== null && !Array.isArray(settingValue) && typeof newSettings[settingKey] === "object" && newSettings[settingKey] !== null) {
2916
+ newSettings[settingKey] = {
2917
+ ...newSettings[settingKey],
2918
+ ...settingValue
2919
+ };
2920
+ } else {
2921
+ newSettings[settingKey] = settingValue;
2922
+ }
2725
2923
  }
2726
- const targetDir = path3.dirname(targetPath);
2727
- await fs6.mkdir(targetDir, { recursive: true });
2728
- let status = "created";
2729
- try {
2730
- await fs6.access(targetPath);
2731
- status = "updated";
2732
- } catch {
2924
+ } else {
2925
+ for (const settingKey of Object.keys(group.settings)) {
2926
+ delete newSettings[settingKey];
2733
2927
  }
2734
- const stubContent = this.generateStubContent(ide, item);
2735
- await fs6.writeFile(targetPath, stubContent, "utf-8");
2736
- return {
2737
- source: item,
2738
- targetIDE: ide,
2739
- targetPath,
2740
- status
2741
- };
2742
- } catch (error) {
2743
- return {
2744
- source: item,
2745
- targetIDE: ide,
2746
- targetPath: "",
2747
- status: "error",
2748
- error: error instanceof Error ? error.message : "Unknown error"
2749
- };
2750
2928
  }
2751
2929
  }
2752
- /**
2753
- * Get target file path for an item
2754
- */
2755
- getTargetPath(config, item) {
2756
- let contentPath = null;
2757
- switch (item.type) {
2758
- case "rules":
2759
- contentPath = config.rulesPath;
2760
- break;
2761
- case "workflows":
2762
- contentPath = config.workflowsPath ?? config.commandsPath;
2763
- break;
2764
- case "commands":
2765
- contentPath = config.commandsPath;
2766
- break;
2767
- }
2768
- if (!contentPath) {
2769
- return null;
2770
- }
2771
- const filename = `${item.name}${config.fileExtension}`;
2772
- return path3.join(this.projectPath, config.basePath, contentPath, filename);
2930
+ await fs6.writeFile(settingsPath, JSON.stringify(newSettings, null, 2));
2931
+ console.log(`
2932
+ \u2705 Updated IDE settings at: ${settingsPath}`);
2933
+ console.log("\u{1F4A1} Tip: Restart your IDE to apply changes.");
2934
+ }
2935
+ async function resetSettings(groupKeys) {
2936
+ const vscodeDir = path3.join(process.cwd(), ".vscode");
2937
+ const settingsPath = path3.join(vscodeDir, "settings.json");
2938
+ if (!existsSync(settingsPath)) {
2939
+ console.log("\n\u26A0\uFE0F No settings.json file found");
2940
+ return;
2773
2941
  }
2774
- // Helper methods
2775
- extractGlobs(frontmatter) {
2776
- const globs = frontmatter.globs;
2777
- if (typeof globs === "string") return [globs];
2778
- if (Array.isArray(globs)) return globs;
2779
- return void 0;
2942
+ const confirmReset = await confirm2({
2943
+ message: groupKeys.length === 0 ? "Reset ALL settings to default (delete entire file)?" : `Reset groups: ${groupKeys.join(", ")}?`,
2944
+ default: false
2945
+ });
2946
+ if (!confirmReset) {
2947
+ console.log("\u274C Operation cancelled.");
2948
+ return;
2780
2949
  }
2781
- extractAlwaysApply(frontmatter) {
2782
- return frontmatter.alwaysApply === true || frontmatter.trigger === "always" || frontmatter.trigger === "always_on";
2950
+ if (groupKeys.length === 0) {
2951
+ await fs6.unlink(settingsPath);
2952
+ console.log("\n\u2705 Deleted settings.json file");
2953
+ } else {
2954
+ await applyGroups(groupKeys, "disable");
2783
2955
  }
2784
- };
2956
+ console.log("\u{1F4A1} Tip: Restart your IDE to apply changes.");
2957
+ }
2785
2958
 
2786
2959
  // src/commands/ide/sync.ts
2960
+ import { Command as Command8 } from "commander";
2961
+ import { checkbox as checkbox2, confirm as confirm3 } from "@inquirer/prompts";
2787
2962
  function createSyncSubcommand() {
2788
2963
  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) => {
2789
2964
  await runSync(options);
@@ -5662,7 +5837,7 @@ function createImageDeleteCommand() {
5662
5837
 
5663
5838
  // src/commands/image/index.ts
5664
5839
  function createImageCommand() {
5665
- const cmd = new Command20("image").description("Image generation commands");
5840
+ const cmd = new Command20("image").description("Image generation commands (Coming Soon)");
5666
5841
  cmd.addCommand(createImageGenCommand());
5667
5842
  cmd.addCommand(createImageListCommand());
5668
5843
  cmd.addCommand(createImageInfoCommand());