@jvittechs/jai1-cli 0.1.92 → 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.
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.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: {
@@ -772,6 +772,316 @@ 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
+ 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}`);
795
+ }
796
+ if (opts.globs && opts.globs.length > 0) {
797
+ lines.push(`globs: ${opts.globs.join(", ")}`);
798
+ }
799
+ lines.push(`alwaysApply: ${opts.alwaysApply}`);
800
+ lines.push("---");
801
+ return lines.join("\n");
802
+ }
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
+ ---`;
818
+ }
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
+ ---`;
834
+ }
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}`);
849
+ }
850
+ if (opts.globs && opts.globs.length > 0) {
851
+ lines.push(`paths: ${opts.globs.join(", ")}`);
852
+ }
853
+ lines.push("---");
854
+ return lines.join("\n");
855
+ }
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
+ ---`;
871
+ }
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;
912
+ }
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 = {};
921
+ try {
922
+ const { data } = matter(content);
923
+ frontmatter = data;
924
+ } catch {
925
+ }
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
+ });
935
+ }
936
+ return items;
937
+ }
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;
948
+ }
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
+ });
966
+ }
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
+ `;
987
+ }
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);
1005
+ }
1006
+ }
1007
+ }
1008
+ return results;
1009
+ }
1010
+ /**
1011
+ * Migrate a single item
1012
+ */
1013
+ async migrateItem(ide, config, item) {
1014
+ try {
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
+ };
1024
+ }
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
+ };
1041
+ } catch (error) {
1042
+ return {
1043
+ source: item,
1044
+ targetIDE: ide,
1045
+ targetPath: "",
1046
+ status: "error",
1047
+ error: error instanceof Error ? error.message : "Unknown error"
1048
+ };
1049
+ }
1050
+ }
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;
1066
+ }
1067
+ if (!contentPath) {
1068
+ return null;
1069
+ }
1070
+ const filename = `${item.name}${config.fileExtension}`;
1071
+ return path.join(this.projectPath, config.basePath, contentPath, filename);
1072
+ }
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;
1079
+ }
1080
+ extractAlwaysApply(frontmatter) {
1081
+ return frontmatter.alwaysApply === true || frontmatter.trigger === "always" || frontmatter.trigger === "always_on";
1082
+ }
1083
+ };
1084
+
775
1085
  // src/ui/shared/ProgressBar.tsx
776
1086
  import React from "react";
777
1087
  import { Box, Text } from "ink";
@@ -888,11 +1198,15 @@ var UnifiedApplyApp = ({
888
1198
  const [selectedPackageIndex, setSelectedPackageIndex] = useState(0);
889
1199
  const [installProgress, setInstallProgress] = useState([]);
890
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 });
891
1206
  const service = new ComponentsService();
892
1207
  useEffect(() => {
893
1208
  const loadData = async () => {
894
1209
  try {
895
- setLoading(true);
896
1210
  const [comps, tagList, installed] = await Promise.all([
897
1211
  service.list(config),
898
1212
  service.listTags(config),
@@ -901,28 +1215,71 @@ var UnifiedApplyApp = ({
901
1215
  setComponents(comps);
902
1216
  setTags(tagList);
903
1217
  setInstalledPaths(new Set(Object.keys(installed)));
1218
+ setLoading(false);
904
1219
  } catch (err) {
905
- setError(err instanceof Error ? err.message : "Failed to load data");
906
- } finally {
1220
+ setError(err instanceof Error ? err.message : "Failed to load");
907
1221
  setLoading(false);
908
1222
  }
909
1223
  };
910
1224
  loadData();
1225
+ setAvailableIdes(getMigrationIDEs());
911
1226
  }, []);
912
1227
  const filteredComponents = useMemo(() => {
913
1228
  if (!searchQuery.trim()) return components;
914
1229
  const query = searchQuery.toLowerCase();
915
1230
  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))
1231
+ (c) => c.filepath.toLowerCase().includes(query) || c.tags?.some((t) => t.toLowerCase().includes(query))
917
1232
  );
918
1233
  }, [components, searchQuery]);
919
1234
  useInput((input4, key) => {
920
- if (viewState === "summary") {
1235
+ if (viewState === "done") {
921
1236
  if (key.return || input4 === "q" || key.escape) {
922
1237
  onExit();
923
1238
  }
924
1239
  return;
925
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
+ }
926
1283
  if (viewState === "installing") {
927
1284
  return;
928
1285
  }
@@ -1037,6 +1394,44 @@ var UnifiedApplyApp = ({
1037
1394
  }
1038
1395
  setViewState("summary");
1039
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
+ };
1040
1435
  if (loading) {
1041
1436
  return /* @__PURE__ */ React3.createElement(Box2, { padding: 1 }, /* @__PURE__ */ React3.createElement(Text3, { color: "cyan" }, /* @__PURE__ */ React3.createElement(Spinner, { type: "dots" }), " \u0110ang t\u1EA3i components..."));
1042
1437
  }
@@ -1047,7 +1442,21 @@ var UnifiedApplyApp = ({
1047
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"))));
1048
1443
  }
1049
1444
  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")));
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")));
1051
1460
  }
1052
1461
  const visibleComponents = filteredComponents.slice(0, 12);
1053
1462
  const hasMore = filteredComponents.length > 12;
@@ -1155,6 +1564,8 @@ async function nonInteractiveApply(config, items, options) {
1155
1564
  console.log(`
1156
1565
  \u2705 Complete: ${added} added, ${updated} updated, ${skipped} skipped`);
1157
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)`);
1158
1569
  }
1159
1570
 
1160
1571
  // src/commands/update.ts
@@ -1624,9 +2035,9 @@ var DetailView = ({ item, scrollPosition: initialScroll, onBack }) => {
1624
2035
  };
1625
2036
 
1626
2037
  // src/services/context-scanner.service.ts
1627
- import { promises as fs4 } from "fs";
1628
- import path from "path";
1629
- import matter from "gray-matter";
2038
+ import { promises as fs5 } from "fs";
2039
+ import path2 from "path";
2040
+ import matter2 from "gray-matter";
1630
2041
 
1631
2042
  // src/constants/ide-configs.ts
1632
2043
  var IDE_CONFIGS = {
@@ -1789,8 +2200,8 @@ var ContextScannerService = class {
1789
2200
  if (!relativePath) return [];
1790
2201
  let dirPath;
1791
2202
  if (ide === "jai1") {
1792
- const jai1Path = path.join(this.projectPath, ".jai1", relativePath);
1793
- 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);
1794
2205
  if (await this.pathExists(jai1Path)) {
1795
2206
  dirPath = jai1Path;
1796
2207
  } else if (await this.pathExists(frameworkPath)) {
@@ -1799,9 +2210,9 @@ var ContextScannerService = class {
1799
2210
  return [];
1800
2211
  }
1801
2212
  } else {
1802
- dirPath = path.join(this.projectPath, config.basePath, relativePath);
2213
+ dirPath = path2.join(this.projectPath, config.basePath, relativePath);
1803
2214
  try {
1804
- await fs4.access(dirPath);
2215
+ await fs5.access(dirPath);
1805
2216
  } catch {
1806
2217
  return [];
1807
2218
  }
@@ -1812,10 +2223,10 @@ var ContextScannerService = class {
1812
2223
  const skillItems = await this.scanSkills(dirPath, ide);
1813
2224
  items.push(...skillItems);
1814
2225
  } else {
1815
- const files = await fs4.readdir(dirPath);
2226
+ const files = await fs5.readdir(dirPath);
1816
2227
  for (const file of files) {
1817
- const filepath = path.join(dirPath, file);
1818
- const stat = await fs4.stat(filepath);
2228
+ const filepath = path2.join(dirPath, file);
2229
+ const stat = await fs5.stat(filepath);
1819
2230
  if (!stat.isFile()) continue;
1820
2231
  const matchesExtension = extensions.some((ext) => file.endsWith(ext));
1821
2232
  if (!matchesExtension) continue;
@@ -1835,20 +2246,20 @@ var ContextScannerService = class {
1835
2246
  async scanSkills(skillsDir, ide) {
1836
2247
  const items = [];
1837
2248
  try {
1838
- const entries = await fs4.readdir(skillsDir, { withFileTypes: true });
2249
+ const entries = await fs5.readdir(skillsDir, { withFileTypes: true });
1839
2250
  for (const entry of entries) {
1840
2251
  if (!entry.isDirectory()) continue;
1841
- const skillPath = path.join(skillsDir, entry.name);
1842
- const skillFilePath = path.join(skillPath, "SKILL.md");
2252
+ const skillPath = path2.join(skillsDir, entry.name);
2253
+ const skillFilePath = path2.join(skillPath, "SKILL.md");
1843
2254
  try {
1844
- await fs4.access(skillFilePath);
2255
+ await fs5.access(skillFilePath);
1845
2256
  } catch {
1846
2257
  continue;
1847
2258
  }
1848
2259
  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"));
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"));
1852
2263
  items.push(item);
1853
2264
  }
1854
2265
  } catch (error) {
@@ -1860,9 +2271,9 @@ var ContextScannerService = class {
1860
2271
  * Parse a context item from file
1861
2272
  */
1862
2273
  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);
2274
+ const content = await fs5.readFile(filepath, "utf-8");
2275
+ const stat = await fs5.stat(filepath);
2276
+ const { data: frontmatter, content: bodyContent } = matter2(content);
1866
2277
  const config = IDE_CONFIGS[ide];
1867
2278
  const description = frontmatter[config.frontmatterSchema.descriptionField];
1868
2279
  let globs;
@@ -1878,9 +2289,9 @@ var ContextScannerService = class {
1878
2289
  globs = triggerValue;
1879
2290
  }
1880
2291
  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));
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));
1884
2295
  return {
1885
2296
  id,
1886
2297
  ide,
@@ -1916,13 +2327,13 @@ var ContextScannerService = class {
1916
2327
  for (const ide of getAllIDEs()) {
1917
2328
  const config = IDE_CONFIGS[ide];
1918
2329
  if (ide === "jai1") {
1919
- const jai1Path = path.join(this.projectPath, ".jai1");
1920
- const frameworkPath = path.join(this.projectPath, "packages/jai1-framework");
2330
+ const jai1Path = path2.join(this.projectPath, ".jai1");
2331
+ const frameworkPath = path2.join(this.projectPath, "packages/jai1-framework");
1921
2332
  if (await this.pathExists(jai1Path) || await this.pathExists(frameworkPath)) {
1922
2333
  ides.push(ide);
1923
2334
  }
1924
2335
  } else {
1925
- const idePath = path.join(this.projectPath, config.basePath);
2336
+ const idePath = path2.join(this.projectPath, config.basePath);
1926
2337
  if (await this.pathExists(idePath)) {
1927
2338
  ides.push(ide);
1928
2339
  }
@@ -1935,7 +2346,7 @@ var ContextScannerService = class {
1935
2346
  */
1936
2347
  async pathExists(filepath) {
1937
2348
  try {
1938
- await fs4.access(filepath);
2349
+ await fs5.access(filepath);
1939
2350
  return true;
1940
2351
  } catch {
1941
2352
  return false;
@@ -2163,8 +2574,8 @@ async function printStats() {
2163
2574
  // src/commands/ide/setup.ts
2164
2575
  import { Command as Command7 } from "commander";
2165
2576
  import { checkbox, confirm as confirm2, select } from "@inquirer/prompts";
2166
- import fs5 from "fs/promises";
2167
- import path2 from "path";
2577
+ import fs6 from "fs/promises";
2578
+ import path3 from "path";
2168
2579
  import { existsSync } from "fs";
2169
2580
  var PERFORMANCE_GROUPS = {
2170
2581
  telemetry: {
@@ -2358,432 +2769,120 @@ async function interactiveMode() {
2358
2769
  const allGroups = Object.keys(PERFORMANCE_GROUPS);
2359
2770
  await applyGroups(allGroups, "enable");
2360
2771
  } 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
- }
2772
+ await resetSettings([]);
2773
+ } else {
2774
+ await selectGroupsToApply(action);
2573
2775
  }
2574
- };
2575
- function getMigrationIDEs() {
2576
- return Object.keys(IDE_MIGRATION_CONFIGS);
2577
2776
  }
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");
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
2786
+ });
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;
2791
+ }
2792
+ await applyGroups(selectedGroups, action);
2793
+ } catch {
2794
+ console.log("\n\u274C Operation cancelled.");
2586
2795
  }
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
- };
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;
2602
2806
  }
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;
2807
+ if (!existsSync(vscodeDir)) {
2808
+ await fs6.mkdir(vscodeDir, { recursive: true });
2809
+ console.log("\u{1F4C1} Created .vscode/ directory");
2638
2810
  }
2639
- /**
2640
- * Scan a specific content type
2641
- */
2642
- async scanContentType(type) {
2643
- const items = [];
2644
- const dirPath = path3.join(this.jai1Path, type);
2811
+ let currentSettings = {};
2812
+ if (existsSync(settingsPath)) {
2645
2813
  try {
2646
- await fs6.access(dirPath);
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");
2647
2817
  } 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)
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
2666
2822
  });
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
- }
2823
+ if (!confirmOverwrite) {
2824
+ console.log("\u274C Operation cancelled.");
2825
+ return;
2707
2826
  }
2827
+ currentSettings = {};
2708
2828
  }
2709
- return results;
2710
2829
  }
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
- };
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
+ }
2725
2847
  }
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 {
2848
+ } else {
2849
+ for (const settingKey of Object.keys(group.settings)) {
2850
+ delete newSettings[settingKey];
2733
2851
  }
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
2852
  }
2751
2853
  }
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);
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;
2773
2865
  }
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;
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;
2780
2873
  }
2781
- extractAlwaysApply(frontmatter) {
2782
- return frontmatter.alwaysApply === true || frontmatter.trigger === "always" || frontmatter.trigger === "always_on";
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");
2783
2879
  }
2784
- };
2880
+ console.log("\u{1F4A1} Tip: Restart your IDE to apply changes.");
2881
+ }
2785
2882
 
2786
2883
  // src/commands/ide/sync.ts
2884
+ import { Command as Command8 } from "commander";
2885
+ import { checkbox as checkbox2, confirm as confirm3 } from "@inquirer/prompts";
2787
2886
  function createSyncSubcommand() {
2788
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) => {
2789
2888
  await runSync(options);
@@ -5662,7 +5761,7 @@ function createImageDeleteCommand() {
5662
5761
 
5663
5762
  // src/commands/image/index.ts
5664
5763
  function createImageCommand() {
5665
- const cmd = new Command20("image").description("Image generation commands");
5764
+ const cmd = new Command20("image").description("Image generation commands (Coming Soon)");
5666
5765
  cmd.addCommand(createImageGenCommand());
5667
5766
  cmd.addCommand(createImageListCommand());
5668
5767
  cmd.addCommand(createImageInfoCommand());