@jvittechs/jai1-cli 0.1.105 → 1.0.0

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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { Command as Command60 } from "commander";
4
+ import { Command as Command61 } from "commander";
5
5
 
6
6
  // src/errors/index.ts
7
7
  var Jai1Error = class extends Error {
@@ -33,7 +33,7 @@ var NetworkError = class extends Jai1Error {
33
33
  // package.json
34
34
  var package_default = {
35
35
  name: "@jvittechs/jai1-cli",
36
- version: "0.1.105",
36
+ version: "1.0.0",
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: {
@@ -101,6 +101,7 @@ var package_default = {
101
101
  marked: "^12.0.0",
102
102
  "marked-terminal": "^7.0.0",
103
103
  open: "^10.1.0",
104
+ ora: "^9.0.0",
104
105
  "p-limit": "^5.0.0",
105
106
  "p-queue": "^7.4.1",
106
107
  "p-retry": "^6.2.0",
@@ -479,6 +480,34 @@ var ComponentsService = class {
479
480
  this.cacheDir = join2(homedir3(), ".jai1", "cache");
480
481
  this.manifestFile = join2(projectRoot, ".jai1", "manifest.json");
481
482
  }
483
+ /**
484
+ * Expand component paths with special prefixes
485
+ *
486
+ * Supported formats:
487
+ * - Standard paths: "rule-presets/react-spa-zustand", "rules/jai1.md", "workflows/commit-it.md"
488
+ * - Package prefix: "package:core" -> expands to all components in that package
489
+ *
490
+ * @param config - Jai1 config
491
+ * @param paths - Array of component paths (may include special prefixes)
492
+ * @returns Array of expanded component paths
493
+ */
494
+ async expandPaths(config, paths) {
495
+ const expandedPaths = [];
496
+ for (const path13 of paths) {
497
+ if (path13.startsWith("package:")) {
498
+ const packageName = path13.substring("package:".length);
499
+ const components = await this.list(config);
500
+ if (packageName === "core") {
501
+ expandedPaths.push(...components.map((c) => c.filepath));
502
+ } else {
503
+ console.warn(`Warning: Unknown package '${packageName}', skipping`);
504
+ }
505
+ } else {
506
+ expandedPaths.push(path13);
507
+ }
508
+ }
509
+ return expandedPaths;
510
+ }
482
511
  /**
483
512
  * List components from API
484
513
  */
@@ -546,9 +575,6 @@ var ComponentsService = class {
546
575
  if (!component.content) {
547
576
  throw new Error(`Component ${filepath} has no content`);
548
577
  }
549
- const targetPath = join2(targetDir, filepath);
550
- const targetFolder = join2(targetPath, "..");
551
- await fs2.mkdir(targetFolder, { recursive: true });
552
578
  let checksumContent = component.content;
553
579
  if (component.contentType === "bundle" || component.contentType === "zip") {
554
580
  let bundleJson;
@@ -593,6 +619,9 @@ var ComponentsService = class {
593
619
  if (component.contentType === "markdown") {
594
620
  checksumContent = component.content;
595
621
  }
622
+ const targetPath = join2(targetDir, filepath);
623
+ const targetFolder = join2(targetPath, "..");
624
+ await fs2.mkdir(targetFolder, { recursive: true });
596
625
  await fs2.writeFile(targetPath, component.content);
597
626
  }
598
627
  await this.markInstalled(filepath, component.version, this.calculateChecksum(checksumContent));
@@ -1358,12 +1387,18 @@ var UnifiedApplyApp = ({
1358
1387
  setAvailableIdes(getMigrationIDEs());
1359
1388
  }, []);
1360
1389
  const filteredComponents = useMemo(() => {
1390
+ if (focusArea === "packages" && tags.length > 0) {
1391
+ const selectedTag = tags[selectedPackageIndex];
1392
+ if (selectedTag) {
1393
+ return components.filter((c) => c.tags?.includes(selectedTag.tag));
1394
+ }
1395
+ }
1361
1396
  if (!searchQuery.trim()) return components;
1362
1397
  const query = searchQuery.toLowerCase();
1363
1398
  return components.filter(
1364
1399
  (c) => c.filepath.toLowerCase().includes(query) || c.tags?.some((t) => t.toLowerCase().includes(query))
1365
1400
  );
1366
- }, [components, searchQuery]);
1401
+ }, [components, searchQuery, focusArea, selectedPackageIndex, tags]);
1367
1402
  useInput((input5, key) => {
1368
1403
  if (viewState === "done") {
1369
1404
  if (key.return || input5 === "q" || key.escape) {
@@ -1479,7 +1514,6 @@ var UnifiedApplyApp = ({
1479
1514
  packageComponents.forEach((c) => next.add(c.filepath));
1480
1515
  return next;
1481
1516
  });
1482
- setSearchQuery(tag.tag);
1483
1517
  }
1484
1518
  }
1485
1519
  }
@@ -1600,7 +1634,7 @@ var UnifiedApplyApp = ({
1600
1634
  onChange: setSearchQuery,
1601
1635
  placeholder: "Type to filter..."
1602
1636
  }
1603
- ) : /* @__PURE__ */ React3.createElement(Text3, null, searchQuery || /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "Press Tab to search"))), /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1, flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true, dimColor: true }, "Quick Apply (Packages):"), /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1, flexWrap: "wrap" }, tags.slice(0, 6).map((tag, i) => {
1637
+ ) : /* @__PURE__ */ React3.createElement(Text3, null, searchQuery || /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "Press Tab to search"))), /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1, flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true, dimColor: true }, "Quick Apply (Packages):"), /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1, flexWrap: "wrap" }, tags.map((tag, i) => {
1604
1638
  const isSelected = focusArea === "packages" && i === selectedPackageIndex;
1605
1639
  return /* @__PURE__ */ React3.createElement(Box2, { key: tag.tag, marginRight: 1 }, /* @__PURE__ */ React3.createElement(
1606
1640
  Text3,
@@ -1614,12 +1648,12 @@ var UnifiedApplyApp = ({
1614
1648
  tag.count,
1615
1649
  "]"
1616
1650
  ));
1617
- }))), /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", borderStyle: "round", borderColor: focusArea === "components" ? "cyan" : "gray", padding: 1 }, /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Components "), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "(", filteredComponents.length, " shown", hasMore ? `, scroll for more` : "", ")")), visibleComponents.map((comp, i) => {
1651
+ })), focusArea === "packages" && tags.length > 0 && /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "\u2190 \u2192 to browse packages \xB7 Space/Enter to select package"))), /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", borderStyle: "round", borderColor: focusArea === "components" ? "cyan" : "gray", padding: 1 }, /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Components "), focusArea === "packages" && tags.length > 0 && /* @__PURE__ */ React3.createElement(Text3, { color: "cyan" }, "[", tags[selectedPackageIndex]?.tag, "] "), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "(", filteredComponents.length, " shown", hasMore ? `, scroll for more` : "", ")")), visibleComponents.map((comp, i) => {
1618
1652
  const isCursor = i === cursorIndex && focusArea === "components";
1619
1653
  const isChecked = selectedPaths.has(comp.filepath);
1620
1654
  const isInstalled = installedPaths.has(comp.filepath);
1621
1655
  return /* @__PURE__ */ React3.createElement(Box2, { key: comp.filepath }, /* @__PURE__ */ React3.createElement(Text3, { color: isCursor ? "cyan" : "white" }, isCursor ? "\u276F " : " ", isChecked ? "[\u2713]" : "[ ]", " ", comp.filepath), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, " ", isInstalled ? "\u2713 installed" : "\u25CB new"));
1622
- }), filteredComponents.length === 0 && /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "No components match your search")), selectedPaths.size > 0 && /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Selected: ", selectedPaths.size, " components"), /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", marginLeft: 2 }, Array.from(selectedPaths).slice(0, 4).map((fp) => /* @__PURE__ */ React3.createElement(Text3, { key: fp, dimColor: true }, "\u{1F4CC} ", fp)), selectedPaths.size > 4 && /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, " ... and ", selectedPaths.size - 4, " more"))), /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "[Tab] Switch area \xB7 [\u2191\u2193/\u2190\u2192] Navigate \xB7 [\u2423] Toggle \xB7 [A] Select all \xB7 [C] Clear \xB7 [Enter] Apply \xB7 [Q] Quit")));
1656
+ }), filteredComponents.length === 0 && /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "No components match your search")), selectedPaths.size > 0 && /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Selected: ", selectedPaths.size, " components"), /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", marginLeft: 2 }, Array.from(selectedPaths).slice(0, 4).map((fp) => /* @__PURE__ */ React3.createElement(Text3, { key: fp, dimColor: true }, "\u{1F4CC} ", fp)), selectedPaths.size > 4 && /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, " ... and ", selectedPaths.size - 4, " more"))), /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, focusArea === "packages" && "[\u2190\u2192] Browse packages \xB7 [\u2423/Enter] Select package", focusArea === "components" && "[\u2191\u2193] Navigate \xB7 [\u2423] Toggle", focusArea === "search" && "Type to search...", " \xB7 [Tab] Switch area \xB7 [A] All \xB7 [C] Clear \xB7 [Enter] Apply \xB7 [Q] Quit")));
1623
1657
  };
1624
1658
 
1625
1659
  // src/commands/apply.ts
@@ -1966,7 +2000,7 @@ var MainMenuView = ({ ideContexts, onSelect }) => {
1966
2000
  {
1967
2001
  ide: "antigravity",
1968
2002
  icon: "\u{1F680}",
1969
- title: "Antigravity (Claude)",
2003
+ title: "Antigravity",
1970
2004
  itemCount: ideContexts.find((ctx) => ctx.ide === "antigravity")?.stats.totalItems || 0,
1971
2005
  available: ideContexts.some((ctx) => ctx.ide === "antigravity")
1972
2006
  },
@@ -2293,7 +2327,7 @@ var IDE_CONFIGS = {
2293
2327
  },
2294
2328
  antigravity: {
2295
2329
  id: "antigravity",
2296
- name: "Antigravity (Claude)",
2330
+ name: "Antigravity",
2297
2331
  icon: "\u{1F680}",
2298
2332
  basePath: ".agent",
2299
2333
  contentPaths: {
@@ -2794,6 +2828,25 @@ import { checkbox, confirm as confirm2, select } from "@inquirer/prompts";
2794
2828
  import fs6 from "fs/promises";
2795
2829
  import path3 from "path";
2796
2830
  import { existsSync } from "fs";
2831
+
2832
+ // src/utils/prompt-theme.ts
2833
+ var keysHelpTipWithQuit = (keys) => {
2834
+ const keyStrings = keys.map(([key, action]) => `${key} ${action}`);
2835
+ keyStrings.push("Ctrl+C quit");
2836
+ return keyStrings.join(" \u2022 ");
2837
+ };
2838
+ var checkboxTheme = {
2839
+ style: {
2840
+ keysHelpTip: keysHelpTipWithQuit
2841
+ }
2842
+ };
2843
+ var selectTheme = {
2844
+ style: {
2845
+ keysHelpTip: keysHelpTipWithQuit
2846
+ }
2847
+ };
2848
+
2849
+ // src/commands/ide/setup.ts
2797
2850
  var PERFORMANCE_GROUPS = {
2798
2851
  telemetry: {
2799
2852
  name: "Telemetry",
@@ -2980,7 +3033,8 @@ async function interactiveMode() {
2980
3033
  { name: "\u274C Disable optimization groups", value: "disable" },
2981
3034
  { name: "\u{1F680} Max Performance (enable all)", value: "max" },
2982
3035
  { name: "\u{1F504} Reset to defaults", value: "reset" }
2983
- ]
3036
+ ],
3037
+ theme: selectTheme
2984
3038
  });
2985
3039
  if (action === "max") {
2986
3040
  const allGroups = Object.keys(PERFORMANCE_GROUPS);
@@ -2999,7 +3053,8 @@ async function selectGroupsToApply(action) {
2999
3053
  try {
3000
3054
  const selectedGroups = await checkbox({
3001
3055
  message: `Select groups to ${action} (SPACE to select, ENTER to confirm):`,
3002
- choices
3056
+ choices,
3057
+ theme: checkboxTheme
3003
3058
  });
3004
3059
  if (selectedGroups.length === 0) {
3005
3060
  console.log("\n\u26A0\uFE0F No groups selected!");
@@ -3135,7 +3190,8 @@ async function runSync(options) {
3135
3190
  });
3136
3191
  selectedIdes = await checkbox2({
3137
3192
  message: "Select target IDE(s) (SPACE to select, ENTER to confirm):",
3138
- choices: ideChoices
3193
+ choices: ideChoices,
3194
+ theme: checkboxTheme
3139
3195
  });
3140
3196
  if (selectedIdes.length === 0) {
3141
3197
  console.log("\n\u26A0\uFE0F No IDE selected!");
@@ -3154,7 +3210,8 @@ async function runSync(options) {
3154
3210
  ];
3155
3211
  selectedTypes = await checkbox2({
3156
3212
  message: "Select content types to sync:",
3157
- choices: typeChoices
3213
+ choices: typeChoices,
3214
+ theme: checkboxTheme
3158
3215
  });
3159
3216
  if (selectedTypes.length === 0) {
3160
3217
  console.log("\n\u26A0\uFE0F No content type selected!");
@@ -3280,78 +3337,6 @@ var IDE_FORMATS = {
3280
3337
  // Gemini requires AGENTS.md
3281
3338
  }
3282
3339
  };
3283
- var JAI1_BASE_RULE = `# Jai1 Framework Agent
3284
-
3285
- You are an AI coding assistant integrated with the Jai1 Framework.
3286
-
3287
- ## Skills Reference
3288
-
3289
- The Jai1 Framework provides specialized skills that you can reference and apply:
3290
-
3291
- ### Pattern Detection
3292
-
3293
- When you encounter:
3294
- - **Component creation**: Reference \`@jai1-component-patterns\`
3295
- - **API development**: Reference \`@jai1-api-patterns\`
3296
- - **Database operations**: Reference \`@jai1-database-patterns\`
3297
- - **Testing**: Reference \`@jai1-testing-patterns\`
3298
- - **Deployment**: Reference \`@jai1-deployment-patterns\`
3299
-
3300
- ### Skill Loading Procedure
3301
-
3302
- 1. **Detect the task type** from user request
3303
- 2. **Load relevant skill** using \`@jai1-[skill-name]\` syntax
3304
- 3. **Apply patterns** from the skill to your response
3305
- 4. **Follow conventions** defined in the skill
3306
-
3307
- Example:
3308
- \`\`\`
3309
- User: "Create a new API endpoint for users"
3310
- Assistant:
3311
- 1. Loading @jai1-api-patterns
3312
- 2. Applying RESTful conventions
3313
- 3. Following project structure from @jai1-component-patterns
3314
- 4. Implementing validation using @jai1-testing-patterns
3315
- \`\`\`
3316
-
3317
- ### Application
3318
-
3319
- - **Always reference skills** before implementing patterns
3320
- - **Follow naming conventions** from loaded skills
3321
- - **Apply best practices** defined in skills
3322
- - **Maintain consistency** with existing codebase patterns
3323
-
3324
- ### Naming Convention
3325
-
3326
- Skills are named using the pattern: \`@jai1-[domain]-patterns\`
3327
-
3328
- Examples:
3329
- - \`@jai1-component-patterns\`
3330
- - \`@jai1-api-patterns\`
3331
- - \`@jai1-database-patterns\`
3332
- - \`@jai1-testing-patterns\`
3333
- - \`@jai1-deployment-patterns\`
3334
-
3335
- ## Best Practices
3336
-
3337
- 1. **Load before applying**: Always reference the appropriate skill before implementing
3338
- 2. **Follow project structure**: Use patterns from \`@jai1-component-patterns\`
3339
- 3. **Maintain consistency**: Apply conventions consistently across the codebase
3340
- 4. **Document decisions**: Explain why specific patterns are used
3341
-
3342
- ## Integration
3343
-
3344
- This base rule works in conjunction with:
3345
- - Project-specific rules (e.g., 01-project.md)
3346
- - Technology-specific rules (e.g., 03-frontend.md)
3347
- - Custom rules (e.g., 09-custom.md)
3348
- - AGENTS.md (if present)
3349
-
3350
- When conflicts arise, prioritize:
3351
- 1. Custom rules (09-custom.*)
3352
- 2. Project rules (01-project.*)
3353
- 3. This base rule (00-jai1.*)
3354
- `;
3355
3340
 
3356
3341
  // src/services/ide-detection.service.ts
3357
3342
  var IdeDetectionService = class {
@@ -3507,9 +3492,9 @@ var IdeDetectionService = class {
3507
3492
  /**
3508
3493
  * Check if a path exists
3509
3494
  */
3510
- async pathExists(path10) {
3495
+ async pathExists(path13) {
3511
3496
  try {
3512
- await fs7.access(path10);
3497
+ await fs7.access(path13);
3513
3498
  return true;
3514
3499
  } catch {
3515
3500
  return false;
@@ -4401,6 +4386,27 @@ AI: \u0110\u1ECDc rules \u2192 Bi\u1EBFt d\xF9ng HonoJS, c\u1EA5u tr\xFAc th\u01
4401
4386
 
4402
4387
  > \u{1F4A1} **Tip:** Context l\xE0 ch\xECa kh\xF3a \u0111\u1EC3 AI l\xE0m vi\u1EC7c hi\u1EC7u qu\u1EA3!
4403
4388
 
4389
+ ## Y\xEAu c\u1EA7u m\xF4i tr\u01B0\u1EDDng
4390
+
4391
+ ### Windows
4392
+
4393
+ \u26A0\uFE0F **B\u1EAFt bu\u1ED9c:** C\xE0i \u0111\u1EB7t **Git for Windows** (bao g\u1ED3m Git Bash)
4394
+
4395
+ Workflows c\u1EE7a Jai1 s\u1EED d\u1EE5ng c\xE1c Unix commands (\`grep\`, \`wc\`, \`xargs\`...) kh\xF4ng c\xF3 s\u1EB5n tr\xEAn PowerShell/CMD.
4396
+
4397
+ 1. Download: https://git-scm.com/download/win
4398
+ 2. Khi c\xE0i \u0111\u1EB7t, ch\u1ECDn **"Git Bash Here"** context menu
4399
+ 3. Trong VSCode, c\u1EA5u h\xECnh terminal m\u1EB7c \u0111\u1ECBnh:
4400
+ \`\`\`json
4401
+ {
4402
+ "terminal.integrated.defaultProfile.windows": "Git Bash"
4403
+ }
4404
+ \`\`\`
4405
+
4406
+ ### macOS / Linux
4407
+
4408
+ \u2705 S\u1EB5n s\xE0ng - C\xE1c Unix commands \u0111\xE3 c\xF3 s\u1EB5n trong terminal.
4409
+
4404
4410
  ## B\u1EAFt \u0111\u1EA7u nhanh
4405
4411
 
4406
4412
  1. **Kh\u1EDFi t\u1EA1o Jai1** trong d\u1EF1 \xE1n:
@@ -4598,6 +4604,52 @@ V\xED d\u1EE5 minh h\u1ECDa (n\u1EBFu c\u1EA7n)
4598
4604
  - G\u1ECDi khi c\u1EA7n b\u1EB1ng \`@ruleName\`
4599
4605
  - V\xED d\u1EE5: \`@database-migration\`, \`@api-design\`
4600
4606
 
4607
+ ## Qu\u1EA3n l\xFD Rules v\u1EDBi CLI
4608
+
4609
+ ### Apply Rules Preset
4610
+
4611
+ \`\`\`bash
4612
+ # Apply preset c\xF3 s\u1EB5n
4613
+ jai1 rules apply nextjs-saas
4614
+
4615
+ # Apply cho nhi\u1EC1u IDE c\xF9ng l\xFAc
4616
+ jai1 rules apply nextjs-saas --ides cursor,windsurf,agentsmd
4617
+ \`\`\`
4618
+
4619
+ ### Sync Rules (NEW!)
4620
+
4621
+ Sau khi apply rules, b\u1EA1n c\xF3 th\u1EC3 sync sang IDE kh\xE1c ho\u1EB7c regenerate:
4622
+
4623
+ \`\`\`bash
4624
+ # Interactive mode - ch\u1ECDn IDE t\u1EEB menu
4625
+ jai1 rules sync
4626
+
4627
+ # Auto-detect active IDEs
4628
+ jai1 rules sync --detect
4629
+
4630
+ # Sync specific IDEs
4631
+ jai1 rules sync --ides cursor,windsurf
4632
+
4633
+ # Auto mode (no prompts)
4634
+ jai1 rules sync -y
4635
+ \`\`\`
4636
+
4637
+ **Interactive Mode Flow:**
4638
+ 1. Hi\u1EC3n th\u1ECB current IDE configuration
4639
+ 2. Smart suggestions d\u1EF1a tr\xEAn project structure
4640
+ 3. Checkbox \u0111\u1EC3 ch\u1ECDn IDE (pre-selected current + suggested)
4641
+ 4. Regenerate rules cho c\xE1c IDE \u0111\xE3 ch\u1ECDn
4642
+
4643
+ ### Restore Rules
4644
+
4645
+ \`\`\`bash
4646
+ # List available backups
4647
+ jai1 rules restore --list
4648
+
4649
+ # Restore from backup
4650
+ jai1 rules restore
4651
+ \`\`\`
4652
+
4601
4653
  ## Best Practices
4602
4654
 
4603
4655
  ### \u2705 N\xCAN l\xE0m:
@@ -4606,6 +4658,8 @@ V\xED d\u1EE5 minh h\u1ECDa (n\u1EBFu c\u1EA7n)
4606
4658
  - Focus v\xE0o "what to do", kh\xF4ng ph\u1EA3i "how to do"
4607
4659
  - C\u1EADp nh\u1EADt rules khi d\u1EF1 \xE1n thay \u0111\u1ED5i
4608
4660
  - Chia nh\u1ECF rules theo domain/feature
4661
+ - Edit rules trong \`.jai1/rule-preset/\` (source of truth)
4662
+ - Ch\u1EA1y \`jai1 rules sync\` sau khi edit \u0111\u1EC3 regenerate
4609
4663
 
4610
4664
  ### \u274C KH\xD4NG N\xCAN:
4611
4665
 
@@ -4613,8 +4667,9 @@ V\xED d\u1EE5 minh h\u1ECDa (n\u1EBFu c\u1EA7n)
4613
4667
  - Copy-paste to\xE0n b\u1ED9 documentation
4614
4668
  - Include code examples qu\xE1 chi ti\u1EBFt
4615
4669
  - Hardcode paths ho\u1EB7c values
4670
+ - Edit tr\u1EF1c ti\u1EBFp trong \`.cursor/rules/\` (s\u1EBD b\u1ECB overwrite khi sync)
4616
4671
 
4617
- > \u{1F4A1} **Tip:** D\xF9ng \`jai1 apply rules/...\` \u0111\u1EC3 c\xE0i \u0111\u1EB7t rules m\u1EABu cho tech stack c\u1EE7a b\u1EA1n!
4672
+ > \u{1F4A1} **Tip:** D\xF9ng \`jai1 rules apply\` \u0111\u1EC3 c\xE0i \u0111\u1EB7t rules m\u1EABu cho tech stack c\u1EE7a b\u1EA1n!
4618
4673
  `,
4619
4674
  "skills": `---
4620
4675
  title: Skills
@@ -7759,7 +7814,8 @@ async function handleInteractiveFeedback(config) {
7759
7814
  name: "\u{1F4DD} Suggestion - Share ideas or improvements",
7760
7815
  value: "suggestion"
7761
7816
  }
7762
- ]
7817
+ ],
7818
+ theme: selectTheme
7763
7819
  });
7764
7820
  const title = await input({
7765
7821
  message: "Title (max 200 characters):",
@@ -10293,27 +10349,147 @@ function createUtilsCommand() {
10293
10349
  }
10294
10350
 
10295
10351
  // src/commands/deps/index.ts
10296
- import { Command as Command37 } from "commander";
10297
- import chalk11 from "chalk";
10352
+ import { Command as Command38 } from "commander";
10353
+ import chalk13 from "chalk";
10298
10354
 
10299
- // src/commands/deps/upgrade.ts
10355
+ // src/commands/deps/check.ts
10300
10356
  import { Command as Command36 } from "commander";
10301
- import { checkbox as checkbox3, confirm as confirm6 } from "@inquirer/prompts";
10357
+ import chalk11 from "chalk";
10358
+ import Table3 from "cli-table3";
10359
+ import ora from "ora";
10360
+
10361
+ // src/services/deps-detector.service.ts
10362
+ import * as fs13 from "fs/promises";
10363
+ import * as path8 from "path";
10364
+ var DepsDetectorService = class {
10365
+ /**
10366
+ * Detect all project types in the given directory
10367
+ */
10368
+ async detectProjects(cwd) {
10369
+ const projects = [];
10370
+ if (await this.fileExists(cwd, "package.json")) {
10371
+ const manager = await this.detectNodePackageManager(cwd);
10372
+ projects.push({
10373
+ ecosystem: "node",
10374
+ manager,
10375
+ configFile: "package.json"
10376
+ });
10377
+ }
10378
+ if (await this.fileExists(cwd, "composer.json") || await this.fileExists(cwd, "composer.lock")) {
10379
+ const isLaravel = await this.detectLaravel(cwd);
10380
+ projects.push({
10381
+ ecosystem: "php",
10382
+ manager: "composer",
10383
+ isLaravel,
10384
+ configFile: "composer.json"
10385
+ });
10386
+ }
10387
+ if (await this.fileExists(cwd, "Pipfile")) {
10388
+ projects.push({
10389
+ ecosystem: "python",
10390
+ manager: "pipenv",
10391
+ configFile: "Pipfile"
10392
+ });
10393
+ } else if (await this.fileExists(cwd, "requirements.txt")) {
10394
+ projects.push({
10395
+ ecosystem: "python",
10396
+ manager: "pip",
10397
+ configFile: "requirements.txt"
10398
+ });
10399
+ }
10400
+ return projects;
10401
+ }
10402
+ /**
10403
+ * Detect Node.js package manager
10404
+ */
10405
+ async detectNodePackageManager(cwd) {
10406
+ if (await this.fileExists(cwd, "pnpm-lock.yaml")) {
10407
+ return "pnpm";
10408
+ }
10409
+ if (await this.fileExists(cwd, "yarn.lock")) {
10410
+ return "yarn";
10411
+ }
10412
+ if (await this.fileExists(cwd, "bun.lockb")) {
10413
+ return "bun";
10414
+ }
10415
+ if (await this.fileExists(cwd, "package-lock.json")) {
10416
+ return "npm";
10417
+ }
10418
+ const userAgent = process.env.npm_config_user_agent || "";
10419
+ if (userAgent.includes("pnpm")) return "pnpm";
10420
+ if (userAgent.includes("yarn")) return "yarn";
10421
+ if (userAgent.includes("bun")) return "bun";
10422
+ return "npm";
10423
+ }
10424
+ /**
10425
+ * Detect if project is Laravel
10426
+ */
10427
+ async detectLaravel(cwd) {
10428
+ try {
10429
+ const composerPath = path8.join(cwd, "composer.json");
10430
+ const content = await fs13.readFile(composerPath, "utf-8");
10431
+ const composerJson = JSON.parse(content);
10432
+ const deps = {
10433
+ ...composerJson.require,
10434
+ ...composerJson["require-dev"]
10435
+ };
10436
+ return "laravel/framework" in deps;
10437
+ } catch {
10438
+ return false;
10439
+ }
10440
+ }
10441
+ /**
10442
+ * Check if file exists
10443
+ */
10444
+ async fileExists(cwd, filename) {
10445
+ try {
10446
+ await fs13.access(path8.join(cwd, filename));
10447
+ return true;
10448
+ } catch {
10449
+ return false;
10450
+ }
10451
+ }
10452
+ };
10302
10453
 
10303
10454
  // src/services/deps.service.ts
10304
- import { promises as fs13 } from "fs";
10305
- import path8 from "path";
10455
+ import { promises as fs14 } from "fs";
10456
+ import path9 from "path";
10306
10457
  import { execSync } from "child_process";
10307
10458
  import pLimit2 from "p-limit";
10308
10459
  var DepsService = class {
10460
+ ecosystem = "node";
10309
10461
  versionCache = /* @__PURE__ */ new Map();
10310
- /**
10311
- * Đọc package.json từ thư mục
10312
- */
10462
+ async check(cwd, onProgress) {
10463
+ const pkgJson = await this.readPackageJson(cwd);
10464
+ const manager = await this.detectPackageManager(cwd);
10465
+ const packages = [];
10466
+ if (pkgJson.dependencies) {
10467
+ for (const [name, version] of Object.entries(pkgJson.dependencies)) {
10468
+ if (this.isValidNpmDependency(version)) {
10469
+ packages.push({ name, current: version, type: "dep" });
10470
+ }
10471
+ }
10472
+ }
10473
+ if (pkgJson.devDependencies) {
10474
+ for (const [name, version] of Object.entries(pkgJson.devDependencies)) {
10475
+ if (this.isValidNpmDependency(version)) {
10476
+ packages.push({ name, current: version, type: "dev" });
10477
+ }
10478
+ }
10479
+ }
10480
+ const upgradablePackages = await this.fetchBulkVersions(packages, onProgress);
10481
+ return { ecosystem: this.ecosystem, manager, packages: upgradablePackages };
10482
+ }
10483
+ async upgrade(cwd, options) {
10484
+ const manager = await this.detectPackageManager(cwd);
10485
+ const commands = this.buildUpgradeCommands(options.packages, manager);
10486
+ if (commands.deps) this.executeUpgrade(commands.deps);
10487
+ if (commands.devDeps) this.executeUpgrade(commands.devDeps);
10488
+ }
10313
10489
  async readPackageJson(cwd) {
10314
- const pkgPath = path8.join(cwd, "package.json");
10490
+ const pkgPath = path9.join(cwd, "package.json");
10315
10491
  try {
10316
- const content = await fs13.readFile(pkgPath, "utf-8");
10492
+ const content = await fs14.readFile(pkgPath, "utf-8");
10317
10493
  return JSON.parse(content);
10318
10494
  } catch (error) {
10319
10495
  if (error.code === "ENOENT") {
@@ -10322,9 +10498,6 @@ var DepsService = class {
10322
10498
  throw new Error(`L\u1ED7i \u0111\u1ECDc package.json: ${error.message}`);
10323
10499
  }
10324
10500
  }
10325
- /**
10326
- * Lấy version mới nhất từ npm registry
10327
- */
10328
10501
  async fetchLatestVersion(packageName) {
10329
10502
  if (this.versionCache.has(packageName)) {
10330
10503
  return this.versionCache.get(packageName);
@@ -10334,9 +10507,7 @@ var DepsService = class {
10334
10507
  `https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`,
10335
10508
  { signal: AbortSignal.timeout(1e4) }
10336
10509
  );
10337
- if (!response.ok) {
10338
- return null;
10339
- }
10510
+ if (!response.ok) return null;
10340
10511
  const data = await response.json();
10341
10512
  this.versionCache.set(packageName, data.version);
10342
10513
  return data.version;
@@ -10344,54 +10515,51 @@ var DepsService = class {
10344
10515
  return null;
10345
10516
  }
10346
10517
  }
10347
- /**
10348
- * Fetch nhiều packages cùng lúc với giới hạn concurrent
10349
- */
10350
10518
  async fetchBulkVersions(packages, onProgress) {
10351
10519
  const limit = pLimit2(10);
10352
10520
  const total = packages.length;
10353
10521
  let completed = 0;
10354
10522
  const results = await Promise.all(
10355
- packages.map(
10356
- (pkg) => limit(async () => {
10357
- const latest = await this.fetchLatestVersion(pkg.name);
10358
- completed++;
10359
- onProgress?.(completed, total);
10360
- if (!latest) {
10361
- return null;
10362
- }
10363
- const currentClean = this.stripSemverPrefix(pkg.current);
10364
- const canUpgrade = this.isNewerVersion(latest, currentClean);
10365
- return {
10366
- name: pkg.name,
10367
- current: pkg.current,
10368
- latest,
10369
- latestWithPrefix: this.preserveSemverPrefix(pkg.current, latest),
10370
- type: pkg.type,
10371
- canUpgrade
10372
- };
10373
- })
10374
- )
10523
+ packages.map((pkg) => limit(async () => {
10524
+ const latest = await this.fetchLatestVersion(pkg.name);
10525
+ completed++;
10526
+ onProgress?.(completed, total);
10527
+ if (!latest) return null;
10528
+ const currentClean = this.stripSemverPrefix(pkg.current);
10529
+ const canUpgrade = this.isNewerVersion(latest, currentClean);
10530
+ const upgradeType = this.getUpgradeType(currentClean, latest);
10531
+ return {
10532
+ name: pkg.name,
10533
+ current: pkg.current,
10534
+ latest,
10535
+ latestWithPrefix: this.preserveSemverPrefix(pkg.current, latest),
10536
+ type: pkg.type,
10537
+ canUpgrade,
10538
+ upgradeType
10539
+ };
10540
+ }))
10375
10541
  );
10376
10542
  return results.filter((r) => r !== null && r.canUpgrade);
10377
10543
  }
10378
- /**
10379
- * Loại bỏ semver prefix (^, ~, >=, etc.)
10380
- */
10544
+ isValidNpmDependency(version) {
10545
+ const invalidPrefixes = ["git+", "git:", "file:", "link:", "workspace:"];
10546
+ return !invalidPrefixes.some((prefix) => version.startsWith(prefix));
10547
+ }
10548
+ getUpgradeType(current, latest) {
10549
+ const currentParts = current.split(".").map(Number);
10550
+ const latestParts = latest.split(".").map(Number);
10551
+ if ((latestParts[0] || 0) > (currentParts[0] || 0)) return "major";
10552
+ if ((latestParts[1] || 0) > (currentParts[1] || 0)) return "minor";
10553
+ return "patch";
10554
+ }
10381
10555
  stripSemverPrefix(version) {
10382
10556
  return version.replace(/^[~^>=<]+/, "");
10383
10557
  }
10384
- /**
10385
- * Giữ nguyên semver prefix khi upgrade
10386
- */
10387
10558
  preserveSemverPrefix(current, latest) {
10388
10559
  const match = current.match(/^([~^>=<]*)/);
10389
10560
  const prefix = match?.[1] || "";
10390
10561
  return prefix + latest;
10391
10562
  }
10392
- /**
10393
- * So sánh version để xác định có cần upgrade không
10394
- */
10395
10563
  isNewerVersion(remote, local) {
10396
10564
  const remoteParts = remote.split(".").map(Number);
10397
10565
  const localParts = local.split(".").map(Number);
@@ -10403,13 +10571,6 @@ var DepsService = class {
10403
10571
  }
10404
10572
  return false;
10405
10573
  }
10406
- /**
10407
- * Detect package manager từ lock files
10408
- * Priority:
10409
- * 1. Lock file trong project
10410
- * 2. pnpm nếu có cài đặt trong máy
10411
- * 3. npm (fallback)
10412
- */
10413
10574
  async detectPackageManager(cwd) {
10414
10575
  const lockFiles = [
10415
10576
  { file: "pnpm-lock.yaml", pm: "pnpm" },
@@ -10419,7 +10580,7 @@ var DepsService = class {
10419
10580
  ];
10420
10581
  for (const { file, pm } of lockFiles) {
10421
10582
  try {
10422
- await fs13.access(path8.join(cwd, file));
10583
+ await fs14.access(path9.join(cwd, file));
10423
10584
  return pm;
10424
10585
  } catch {
10425
10586
  }
@@ -10428,27 +10589,22 @@ var DepsService = class {
10428
10589
  if (userAgent.includes("pnpm")) return "pnpm";
10429
10590
  if (userAgent.includes("yarn")) return "yarn";
10430
10591
  if (userAgent.includes("bun")) return "bun";
10431
- if (userAgent.includes("npm")) return "npm";
10432
10592
  if (this.isCommandAvailable("pnpm")) return "pnpm";
10433
10593
  return "npm";
10434
10594
  }
10435
- /**
10436
- * Kiểm tra command có sẵn trong hệ thống không
10437
- */
10438
10595
  isCommandAvailable(command) {
10439
10596
  try {
10440
- execSync(`${command} --version`, {
10441
- stdio: ["pipe", "pipe", "pipe"]
10442
- });
10597
+ execSync(`${command} --version`, { stdio: ["pipe", "pipe", "pipe"] });
10443
10598
  return true;
10444
10599
  } catch {
10445
10600
  return false;
10446
10601
  }
10447
10602
  }
10448
- /**
10449
- * Tạo command để upgrade packages
10450
- */
10451
- getUpgradeCommands(packages, pm) {
10603
+ getUpgradeCommands(packages) {
10604
+ const pm = process.env.npm_config_user_agent?.includes("pnpm") ? "pnpm" : "npm";
10605
+ return this.buildUpgradeCommands(packages, pm);
10606
+ }
10607
+ buildUpgradeCommands(packages, pm) {
10452
10608
  const deps = packages.filter((p) => p.type === "dep");
10453
10609
  const devDeps = packages.filter((p) => p.type === "dev");
10454
10610
  const formatPackages = (pkgs) => pkgs.map((p) => `${p.name}@${p.latestWithPrefix}`).join(" ");
@@ -10488,240 +10644,655 @@ var DepsService = class {
10488
10644
  }
10489
10645
  return { deps: depsCmd, devDeps: devDepsCmd };
10490
10646
  }
10491
- /**
10492
- * Thực thi upgrade command
10493
- */
10494
10647
  executeUpgrade(command) {
10495
- execSync(command, {
10496
- stdio: "inherit",
10497
- env: { ...process.env, FORCE_COLOR: "1" }
10498
- });
10648
+ execSync(command, { stdio: "inherit", env: { ...process.env, FORCE_COLOR: "1" } });
10499
10649
  }
10500
10650
  };
10501
10651
 
10502
- // src/commands/deps/upgrade.ts
10503
- var colors3 = {
10504
- yellow: "\x1B[33m",
10505
- green: "\x1B[32m",
10506
- cyan: "\x1B[36m",
10507
- red: "\x1B[31m",
10508
- gray: "\x1B[90m",
10509
- reset: "\x1B[0m",
10510
- bold: "\x1B[1m",
10511
- dim: "\x1B[2m"
10512
- };
10513
- function createDepsUpgradeCommand() {
10514
- return new Command36("upgrade").description("Upgrade npm packages l\xEAn version m\u1EDBi nh\u1EA5t").option("--check", "Ch\u1EC9 ki\u1EC3m tra, kh\xF4ng upgrade").option("--all", "Upgrade t\u1EA5t c\u1EA3 kh\xF4ng c\u1EA7n ch\u1ECDn").option("--deps-only", "Ch\u1EC9 upgrade dependencies").option("--dev-only", "Ch\u1EC9 upgrade devDependencies").action(async (options) => {
10515
- await handleDepsUpgrade(options);
10516
- });
10517
- }
10518
- async function handleDepsUpgrade(options) {
10519
- const depsService = new DepsService();
10520
- const cwd = process.cwd();
10521
- try {
10522
- console.log(`
10523
- ${colors3.cyan}\u{1F50D} \u0110ang \u0111\u1ECDc package.json...${colors3.reset}`);
10524
- const pkg = await depsService.readPackageJson(cwd);
10525
- const packagesToCheck = [];
10526
- if (!options.devOnly && pkg.dependencies) {
10527
- for (const [name, version] of Object.entries(pkg.dependencies)) {
10528
- if (isNpmVersion(version)) {
10529
- packagesToCheck.push({ name, current: version, type: "dep" });
10530
- }
10531
- }
10532
- }
10533
- if (!options.depsOnly && pkg.devDependencies) {
10534
- for (const [name, version] of Object.entries(pkg.devDependencies)) {
10535
- if (isNpmVersion(version)) {
10536
- packagesToCheck.push({ name, current: version, type: "dev" });
10537
- }
10538
- }
10539
- }
10540
- if (packagesToCheck.length === 0) {
10541
- console.log(`${colors3.yellow}\u26A0\uFE0F Kh\xF4ng t\xECm th\u1EA5y packages \u0111\u1EC3 ki\u1EC3m tra${colors3.reset}
10542
- `);
10543
- return;
10652
+ // src/services/deps-php.service.ts
10653
+ import { execSync as execSync2 } from "child_process";
10654
+ import { promises as fs15 } from "fs";
10655
+ import path10 from "path";
10656
+ var LARAVEL_PROTECTED_PACKAGES = /^laravel\//;
10657
+ var DepsPhpService = class {
10658
+ ecosystem = "php";
10659
+ isLaravel = false;
10660
+ async check(cwd, onProgress) {
10661
+ this.isLaravel = await this.detectLaravel(cwd);
10662
+ const outdated = await this.fetchOutdatedPackages(cwd);
10663
+ const composerJson = await this.readComposerJson(cwd);
10664
+ const packages = [];
10665
+ let processed = 0;
10666
+ for (const pkg of outdated) {
10667
+ const isDev = pkg.name in (composerJson["require-dev"] || {});
10668
+ const upgradeType = this.getUpgradeType(pkg.version, pkg.latest);
10669
+ const canUpgrade = this.canUpgradePackage(pkg.name, upgradeType);
10670
+ const blockedReason = !canUpgrade ? "Laravel major version blocked" : void 0;
10671
+ packages.push({
10672
+ name: pkg.name,
10673
+ current: pkg.version,
10674
+ latest: pkg.latest,
10675
+ latestWithPrefix: pkg.latest,
10676
+ type: isDev ? "dev" : "dep",
10677
+ upgradeType,
10678
+ canUpgrade,
10679
+ blockedReason
10680
+ });
10681
+ processed++;
10682
+ onProgress?.(processed, outdated.length);
10544
10683
  }
10545
- const depsCount = packagesToCheck.filter((p) => p.type === "dep").length;
10546
- const devCount = packagesToCheck.filter((p) => p.type === "dev").length;
10547
- console.log(
10548
- `${colors3.bold}\u{1F4E6} T\xECm th\u1EA5y ${depsCount} dependencies v\xE0 ${devCount} devDependencies${colors3.reset}
10549
- `
10550
- );
10551
- console.log(`${colors3.cyan}\u23F3 Ki\u1EC3m tra phi\xEAn b\u1EA3n m\u1EDBi nh\u1EA5t...${colors3.reset}`);
10552
- const upgradablePackages = await depsService.fetchBulkVersions(
10553
- packagesToCheck,
10554
- (completed, total) => {
10555
- const progress = Math.round(completed / total * 100);
10556
- const bar = "\u2588".repeat(Math.floor(progress / 5)) + "\u2591".repeat(20 - Math.floor(progress / 5));
10557
- process.stdout.write(`\r[${bar}] ${completed}/${total} packages`);
10558
- }
10559
- );
10560
- console.log("\n");
10561
- if (upgradablePackages.length === 0) {
10562
- console.log(`${colors3.green}\u2705 T\u1EA5t c\u1EA3 packages \u0111\xE3 l\xE0 phi\xEAn b\u1EA3n m\u1EDBi nh\u1EA5t!${colors3.reset}
10563
- `);
10564
- return;
10684
+ return {
10685
+ ecosystem: this.ecosystem,
10686
+ manager: "composer",
10687
+ packages: packages.filter((p) => p.canUpgrade),
10688
+ isLaravel: this.isLaravel
10689
+ };
10690
+ }
10691
+ async upgrade(cwd, options) {
10692
+ const commands = this.getUpgradeCommands(options.packages);
10693
+ if (commands.deps) this.executeUpgrade(commands.deps, cwd);
10694
+ if (commands.devDeps) this.executeUpgrade(commands.devDeps, cwd);
10695
+ }
10696
+ getUpgradeCommands(packages) {
10697
+ const deps = packages.filter((p) => p.type === "dep");
10698
+ const devDeps = packages.filter((p) => p.type === "dev");
10699
+ let depsCmd = null;
10700
+ let devDepsCmd = null;
10701
+ if (deps.length > 0) {
10702
+ const pkgList = deps.map((p) => `${p.name}:${p.latest}`).join(" ");
10703
+ depsCmd = `composer require -W ${pkgList}`;
10565
10704
  }
10566
- displayUpgradeTable(upgradablePackages);
10567
- if (options.check) {
10568
- console.log(
10569
- `${colors3.cyan}\u{1F4A1} Ch\u1EA1y "jai1 deps upgrade" \u0111\u1EC3 ti\u1EBFn h\xE0nh upgrade.${colors3.reset}
10570
- `
10571
- );
10572
- return;
10705
+ if (devDeps.length > 0) {
10706
+ const pkgList = devDeps.map((p) => `${p.name}:${p.latest}`).join(" ");
10707
+ devDepsCmd = `composer require --dev -W ${pkgList}`;
10573
10708
  }
10574
- let selectedPackages;
10575
- if (options.all) {
10576
- selectedPackages = upgradablePackages;
10577
- console.log(`${colors3.cyan}\u{1F4CB} \u0110\xE3 ch\u1ECDn t\u1EA5t c\u1EA3 ${selectedPackages.length} packages${colors3.reset}
10578
- `);
10579
- } else {
10580
- try {
10581
- const selected = await checkbox3({
10582
- message: "Ch\u1ECDn packages \u0111\u1EC3 upgrade (ESC \u0111\u1EC3 h\u1EE7y):",
10583
- choices: upgradablePackages.map((pkg2) => ({
10584
- name: `${pkg2.name} (${pkg2.current} \u2192 ${pkg2.latestWithPrefix}) [${pkg2.type}]`,
10585
- value: pkg2.name,
10586
- checked: true
10587
- // Mặc định chọn tất cả
10588
- })),
10589
- instructions: "\u2191\u2193 navigate \u2022 space select \u2022 a all \u2022 i invert \u2022 \u23CE submit \u2022 esc cancel"
10590
- });
10591
- if (selected.length === 0) {
10592
- console.log(`${colors3.yellow}\u23F8\uFE0F Kh\xF4ng c\xF3 packages n\xE0o \u0111\u01B0\u1EE3c ch\u1ECDn${colors3.reset}
10593
- `);
10594
- return;
10595
- }
10596
- selectedPackages = upgradablePackages.filter((pkg2) => selected.includes(pkg2.name));
10597
- } catch {
10598
- console.log(`
10599
- ${colors3.yellow}\u23F8\uFE0F \u0110\xE3 h\u1EE7y${colors3.reset}
10600
- `);
10601
- return;
10709
+ return { deps: depsCmd, devDeps: devDepsCmd };
10710
+ }
10711
+ async readComposerJson(cwd) {
10712
+ const composerPath = path10.join(cwd, "composer.json");
10713
+ try {
10714
+ const content = await fs15.readFile(composerPath, "utf-8");
10715
+ return JSON.parse(content);
10716
+ } catch (error) {
10717
+ if (error.code === "ENOENT") {
10718
+ throw new Error("Kh\xF4ng t\xECm th\u1EA5y composer.json trong th\u01B0 m\u1EE5c hi\u1EC7n t\u1EA1i");
10602
10719
  }
10720
+ throw new Error(`L\u1ED7i \u0111\u1ECDc composer.json: ${error.message}`);
10603
10721
  }
10604
- let shouldProceed;
10722
+ }
10723
+ async detectLaravel(cwd) {
10605
10724
  try {
10606
- shouldProceed = await confirm6({
10607
- message: `Ti\u1EBFn h\xE0nh upgrade ${selectedPackages.length} packages?`,
10608
- default: true
10609
- });
10725
+ const composerJson = await this.readComposerJson(cwd);
10726
+ const deps = { ...composerJson.require, ...composerJson["require-dev"] };
10727
+ return "laravel/framework" in deps;
10610
10728
  } catch {
10611
- console.log(`
10612
- ${colors3.yellow}\u23F8\uFE0F \u0110\xE3 h\u1EE7y${colors3.reset}
10613
- `);
10614
- return;
10615
- }
10616
- if (!shouldProceed) {
10617
- console.log(`${colors3.yellow}\u23F8\uFE0F Upgrade \u0111\xE3 h\u1EE7y${colors3.reset}
10618
- `);
10619
- return;
10729
+ return false;
10620
10730
  }
10621
- const pm = await depsService.detectPackageManager(cwd);
10622
- console.log(`
10623
- ${colors3.cyan}\u{1F527} Package manager: ${pm}${colors3.reset}`);
10624
- console.log(`${colors3.cyan}\u{1F4E5} \u0110ang upgrade...${colors3.reset}
10625
- `);
10626
- const commands = depsService.getUpgradeCommands(selectedPackages, pm);
10731
+ }
10732
+ async fetchOutdatedPackages(cwd) {
10627
10733
  try {
10628
- if (commands.deps) {
10629
- console.log(`${colors3.dim}$ ${commands.deps}${colors3.reset}
10630
- `);
10631
- depsService.executeUpgrade(commands.deps);
10632
- }
10633
- if (commands.devDeps) {
10634
- console.log(`
10635
- ${colors3.dim}$ ${commands.devDeps}${colors3.reset}
10636
- `);
10637
- depsService.executeUpgrade(commands.devDeps);
10638
- }
10639
- console.log(
10640
- `
10641
- ${colors3.green}\u2705 \u0110\xE3 upgrade ${selectedPackages.length} packages th\xE0nh c\xF4ng!${colors3.reset}
10642
- `
10643
- );
10734
+ const output = execSync2("composer outdated --format=json --direct", {
10735
+ cwd,
10736
+ encoding: "utf-8",
10737
+ stdio: ["pipe", "pipe", "pipe"]
10738
+ });
10739
+ const result = JSON.parse(output);
10740
+ return result.installed || [];
10644
10741
  } catch (error) {
10645
- console.error(`
10646
- ${colors3.red}\u274C L\u1ED7i khi upgrade:${colors3.reset}`);
10647
- console.error(`${colors3.red}${error.message}${colors3.reset}
10648
- `);
10649
- console.log(`${colors3.yellow}\u{1F4A1} B\u1EA1n c\xF3 th\u1EC3 th\u1EED upgrade th\u1EE7 c\xF4ng:${colors3.reset}`);
10650
- if (commands.deps) console.log(` ${colors3.cyan}${commands.deps}${colors3.reset}`);
10651
- if (commands.devDeps) console.log(` ${colors3.cyan}${commands.devDeps}${colors3.reset}`);
10652
- console.log("");
10742
+ if (error.stdout) {
10743
+ try {
10744
+ const result = JSON.parse(error.stdout);
10745
+ return result.installed || [];
10746
+ } catch {
10747
+ }
10748
+ }
10749
+ throw new Error(`L\u1ED7i ki\u1EC3m tra composer packages: ${error.message}`);
10750
+ }
10751
+ }
10752
+ getUpgradeType(current, latest) {
10753
+ const currentParts = current.split(".").map(Number);
10754
+ const latestParts = latest.split(".").map(Number);
10755
+ if ((latestParts[0] || 0) > (currentParts[0] || 0)) return "major";
10756
+ if ((latestParts[1] || 0) > (currentParts[1] || 0)) return "minor";
10757
+ return "patch";
10758
+ }
10759
+ canUpgradePackage(packageName, upgradeType) {
10760
+ if (this.isLaravel && LARAVEL_PROTECTED_PACKAGES.test(packageName) && upgradeType === "major") {
10761
+ return false;
10762
+ }
10763
+ return true;
10764
+ }
10765
+ executeUpgrade(command, cwd) {
10766
+ execSync2(command, {
10767
+ cwd,
10768
+ stdio: "inherit",
10769
+ env: { ...process.env, FORCE_COLOR: "1" }
10770
+ });
10771
+ }
10772
+ };
10773
+
10774
+ // src/services/deps-python.service.ts
10775
+ import { execSync as execSync3 } from "child_process";
10776
+ import { promises as fs16 } from "fs";
10777
+ import path11 from "path";
10778
+ import pLimit3 from "p-limit";
10779
+ var DepsPythonService = class {
10780
+ ecosystem = "python";
10781
+ versionCache = /* @__PURE__ */ new Map();
10782
+ async check(cwd, onProgress) {
10783
+ const manager = await this.detectPackageManager(cwd);
10784
+ let packages;
10785
+ if (manager === "pipenv") {
10786
+ packages = await this.checkPipenv(cwd, onProgress);
10787
+ } else {
10788
+ packages = await this.checkPip(cwd, onProgress);
10789
+ }
10790
+ return { ecosystem: this.ecosystem, manager, packages };
10791
+ }
10792
+ async upgrade(cwd, options) {
10793
+ const manager = await this.detectPackageManager(cwd);
10794
+ const commands = this.getUpgradeCommands(options.packages);
10795
+ if (manager === "pipenv") {
10796
+ if (commands.deps || commands.devDeps) {
10797
+ const allPackages = options.packages.map((p) => p.name).join(" ");
10798
+ this.executeUpgrade(`pipenv update ${allPackages}`, cwd);
10799
+ }
10800
+ } else {
10801
+ if (commands.deps) this.executeUpgrade(commands.deps, cwd);
10802
+ if (commands.devDeps) this.executeUpgrade(commands.devDeps, cwd);
10803
+ }
10804
+ }
10805
+ getUpgradeCommands(packages) {
10806
+ const deps = packages.filter((p) => p.type === "dep");
10807
+ const devDeps = packages.filter((p) => p.type === "dev");
10808
+ let depsCmd = null;
10809
+ let devDepsCmd = null;
10810
+ if (deps.length > 0) {
10811
+ const pkgList = deps.map((p) => `${p.name}==${p.latest}`).join(" ");
10812
+ depsCmd = `pip install --upgrade ${pkgList}`;
10813
+ }
10814
+ if (devDeps.length > 0) {
10815
+ const pkgList = devDeps.map((p) => `${p.name}==${p.latest}`).join(" ");
10816
+ devDepsCmd = `pip install --upgrade ${pkgList}`;
10817
+ }
10818
+ return { deps: depsCmd, devDeps: devDepsCmd };
10819
+ }
10820
+ async detectPackageManager(cwd) {
10821
+ if (await this.fileExists(cwd, "Pipfile")) return "pipenv";
10822
+ return "pip";
10823
+ }
10824
+ async checkPip(cwd, onProgress) {
10825
+ const requirementsPath = path11.join(cwd, "requirements.txt");
10826
+ try {
10827
+ const content = await fs16.readFile(requirementsPath, "utf-8");
10828
+ const packages = this.parseRequirementsTxt(content);
10829
+ return await this.fetchBulkVersions(packages, onProgress);
10830
+ } catch (error) {
10831
+ if (error.code === "ENOENT") {
10832
+ throw new Error("Kh\xF4ng t\xECm th\u1EA5y requirements.txt trong th\u01B0 m\u1EE5c hi\u1EC7n t\u1EA1i");
10833
+ }
10834
+ throw new Error(`L\u1ED7i \u0111\u1ECDc requirements.txt: ${error.message}`);
10835
+ }
10836
+ }
10837
+ async checkPipenv(cwd, onProgress) {
10838
+ try {
10839
+ const output = execSync3("pipenv run pip list --outdated --format=json", {
10840
+ cwd,
10841
+ encoding: "utf-8",
10842
+ stdio: ["pipe", "pipe", "pipe"]
10843
+ });
10844
+ const outdated = JSON.parse(output);
10845
+ const total = outdated.length;
10846
+ let completed = 0;
10847
+ const packages = outdated.map((pkg) => {
10848
+ completed++;
10849
+ onProgress?.(completed, total);
10850
+ return {
10851
+ name: pkg.name,
10852
+ current: pkg.version,
10853
+ latest: pkg.latest_version,
10854
+ latestWithPrefix: pkg.latest_version,
10855
+ type: "dep",
10856
+ upgradeType: this.getUpgradeType(pkg.version, pkg.latest_version),
10857
+ canUpgrade: true
10858
+ };
10859
+ });
10860
+ return packages;
10861
+ } catch (error) {
10862
+ throw new Error(`L\u1ED7i ki\u1EC3m tra pipenv packages: ${error.message}`);
10863
+ }
10864
+ }
10865
+ parseRequirementsTxt(content) {
10866
+ const packages = [];
10867
+ const lines = content.split("\n");
10868
+ for (const line of lines) {
10869
+ const trimmed = line.trim();
10870
+ if (!trimmed || trimmed.startsWith("#")) continue;
10871
+ const match = trimmed.match(/^([a-zA-Z0-9_-]+)\s*([=><]=?)\s*([0-9.]+)/);
10872
+ if (match) {
10873
+ const [, name, , version] = match;
10874
+ packages.push({ name: name.toLowerCase(), current: version, type: "dep" });
10875
+ }
10876
+ }
10877
+ return packages;
10878
+ }
10879
+ async fetchBulkVersions(packages, onProgress) {
10880
+ const limit = pLimit3(10);
10881
+ const total = packages.length;
10882
+ let completed = 0;
10883
+ const results = await Promise.all(
10884
+ packages.map((pkg) => limit(async () => {
10885
+ const latest = await this.fetchLatestVersion(pkg.name);
10886
+ completed++;
10887
+ onProgress?.(completed, total);
10888
+ if (!latest) return null;
10889
+ const canUpgrade = this.isNewerVersion(latest, pkg.current);
10890
+ if (!canUpgrade) return null;
10891
+ return {
10892
+ name: pkg.name,
10893
+ current: pkg.current,
10894
+ latest,
10895
+ latestWithPrefix: latest,
10896
+ type: pkg.type,
10897
+ upgradeType: this.getUpgradeType(pkg.current, latest),
10898
+ canUpgrade: true
10899
+ };
10900
+ }))
10901
+ );
10902
+ return results.filter((r) => r !== null);
10903
+ }
10904
+ async fetchLatestVersion(packageName) {
10905
+ if (this.versionCache.has(packageName)) return this.versionCache.get(packageName);
10906
+ try {
10907
+ const response = await fetch(
10908
+ `https://pypi.org/pypi/${encodeURIComponent(packageName)}/json`,
10909
+ { signal: AbortSignal.timeout(1e4) }
10910
+ );
10911
+ if (!response.ok) return null;
10912
+ const data = await response.json();
10913
+ this.versionCache.set(packageName, data.info.version);
10914
+ return data.info.version;
10915
+ } catch {
10916
+ return null;
10917
+ }
10918
+ }
10919
+ getUpgradeType(current, latest) {
10920
+ const currentParts = current.split(".").map(Number);
10921
+ const latestParts = latest.split(".").map(Number);
10922
+ if ((latestParts[0] || 0) > (currentParts[0] || 0)) return "major";
10923
+ if ((latestParts[1] || 0) > (currentParts[1] || 0)) return "minor";
10924
+ return "patch";
10925
+ }
10926
+ isNewerVersion(remote, local) {
10927
+ const remoteParts = remote.split(".").map(Number);
10928
+ const localParts = local.split(".").map(Number);
10929
+ for (let i = 0; i < 3; i++) {
10930
+ const r = remoteParts[i] || 0;
10931
+ const l = localParts[i] || 0;
10932
+ if (r > l) return true;
10933
+ if (r < l) return false;
10934
+ }
10935
+ return false;
10936
+ }
10937
+ async fileExists(cwd, filename) {
10938
+ try {
10939
+ await fs16.access(path11.join(cwd, filename));
10940
+ return true;
10941
+ } catch {
10942
+ return false;
10943
+ }
10944
+ }
10945
+ executeUpgrade(command, cwd) {
10946
+ execSync3(command, { cwd, stdio: "inherit", env: { ...process.env, FORCE_COLOR: "1" } });
10947
+ }
10948
+ };
10949
+
10950
+ // src/commands/deps/check.ts
10951
+ function createDepsCheckCommand() {
10952
+ const checkCommand = new Command36("check").description("Ki\u1EC3m tra c\xE1c packages c\u1EA7n upgrade").action(async () => {
10953
+ const cwd = process.cwd();
10954
+ const detector = new DepsDetectorService();
10955
+ const spinner = ora("\u0110ang ph\xE1t hi\u1EC7n lo\u1EA1i project...").start();
10956
+ const projects = await detector.detectProjects(cwd);
10957
+ if (projects.length === 0) {
10958
+ spinner.fail("Kh\xF4ng t\xECm th\u1EA5y project n\xE0o \u0111\u01B0\u1EE3c h\u1ED7 tr\u1EE3");
10959
+ console.log();
10960
+ console.log(chalk11.dim("H\u1ED7 tr\u1EE3: Node.js (package.json), PHP (composer.json), Python (requirements.txt/Pipfile)"));
10653
10961
  process.exit(1);
10654
10962
  }
10963
+ spinner.succeed(`Ph\xE1t hi\u1EC7n ${projects.length} project:`);
10964
+ for (const project of projects) {
10965
+ const icon = project.ecosystem === "node" ? "\u{1F4E6}" : project.ecosystem === "php" ? "\u{1F418}" : "\u{1F40D}";
10966
+ const label = project.ecosystem === "node" ? "Node.js" : project.ecosystem === "php" ? "PHP/Composer" : "Python";
10967
+ console.log(` ${icon} ${label} (${project.manager})`);
10968
+ }
10969
+ console.log();
10970
+ for (const project of projects) {
10971
+ await checkEcosystem(project.ecosystem, project.manager, cwd);
10972
+ }
10973
+ console.log();
10974
+ console.log(chalk11.dim('\u{1F4A1} Ch\u1EA1y "jai1 deps upgrade" \u0111\u1EC3 n\xE2ng c\u1EA5p packages.'));
10975
+ });
10976
+ return checkCommand;
10977
+ }
10978
+ async function checkEcosystem(ecosystem, manager, cwd) {
10979
+ let service;
10980
+ let icon;
10981
+ let label;
10982
+ switch (ecosystem) {
10983
+ case "node":
10984
+ service = new DepsService();
10985
+ icon = "\u{1F4E6}";
10986
+ label = `Node.js (${manager})`;
10987
+ break;
10988
+ case "php":
10989
+ service = new DepsPhpService();
10990
+ icon = "\u{1F418}";
10991
+ label = "PHP/Composer";
10992
+ break;
10993
+ case "python":
10994
+ service = new DepsPythonService();
10995
+ icon = "\u{1F40D}";
10996
+ label = `Python (${manager})`;
10997
+ break;
10998
+ }
10999
+ const spinner = ora(`\u0110ang ki\u1EC3m tra ${label}...`).start();
11000
+ let result;
11001
+ try {
11002
+ result = await service.check(cwd, (completed, total) => {
11003
+ spinner.text = `\u0110ang ki\u1EC3m tra ${label}... (${completed}/${total})`;
11004
+ });
10655
11005
  } catch (error) {
10656
- console.error(`
10657
- ${colors3.red}\u274C ${error.message}${colors3.reset}
10658
- `);
11006
+ spinner.fail(`L\u1ED7i ki\u1EC3m tra ${label}`);
11007
+ console.log(chalk11.red(error.message));
11008
+ console.log();
11009
+ return;
11010
+ }
11011
+ if (result.packages.length === 0) {
11012
+ spinner.succeed(`${label} - T\u1EA5t c\u1EA3 packages \u0111\xE3 c\u1EADp nh\u1EADt`);
11013
+ console.log();
11014
+ return;
11015
+ }
11016
+ spinner.succeed(`${label} - ${result.packages.length} packages c\xF3 th\u1EC3 n\xE2ng c\u1EA5p`);
11017
+ console.log();
11018
+ const table = new Table3({
11019
+ head: [
11020
+ chalk11.cyan("Package"),
11021
+ chalk11.cyan("Hi\u1EC7n t\u1EA1i"),
11022
+ chalk11.cyan("M\u1EDBi nh\u1EA5t"),
11023
+ chalk11.cyan("Lo\u1EA1i")
11024
+ ],
11025
+ style: {
11026
+ head: [],
11027
+ border: ["dim"]
11028
+ }
11029
+ });
11030
+ for (const pkg of result.packages) {
11031
+ const upgradeIcon = pkg.upgradeType === "major" ? "\u{1F534}" : pkg.upgradeType === "minor" ? "\u{1F7E1}" : "\u{1F7E2}";
11032
+ table.push([
11033
+ `${upgradeIcon} ${pkg.name}`,
11034
+ chalk11.dim(pkg.current),
11035
+ chalk11.green(pkg.latest),
11036
+ pkg.type === "dev" ? chalk11.dim("dev") : "dep"
11037
+ ]);
11038
+ }
11039
+ console.log(table.toString());
11040
+ console.log();
11041
+ if (result.isLaravel) {
11042
+ const blockedPackages = result.packages.filter((p) => p.blockedReason);
11043
+ if (blockedPackages.length > 0) {
11044
+ console.log(chalk11.yellow("\u26A0\uFE0F Laravel major version upgrades blocked (nguy hi\u1EC3m):"));
11045
+ for (const pkg of blockedPackages) {
11046
+ console.log(chalk11.dim(` - ${pkg.name}: ${pkg.current} \u2192 ${pkg.latest}`));
11047
+ }
11048
+ console.log();
11049
+ console.log(chalk11.dim("\u{1F4A1} \u0110\u1EC3 n\xE2ng c\u1EA5p Laravel major version, ch\u1EA1y th\u1EE7 c\xF4ng:"));
11050
+ console.log(chalk11.dim(` composer require ${blockedPackages[0].name}:^${blockedPackages[0].latest}`));
11051
+ console.log();
11052
+ }
11053
+ }
11054
+ }
11055
+
11056
+ // src/commands/deps/upgrade.ts
11057
+ import { Command as Command37 } from "commander";
11058
+ import { checkbox as checkbox3, confirm as confirm6 } from "@inquirer/prompts";
11059
+ import chalk12 from "chalk";
11060
+ import ora2 from "ora";
11061
+ import Table4 from "cli-table3";
11062
+ function createDepsUpgradeCommand() {
11063
+ return new Command37("upgrade").description("Upgrade packages l\xEAn version m\u1EDBi nh\u1EA5t").option("--all", "Upgrade t\u1EA5t c\u1EA3 kh\xF4ng c\u1EA7n ch\u1ECDn").action(async (options) => {
11064
+ await handleDepsUpgrade(options);
11065
+ });
11066
+ }
11067
+ async function handleDepsUpgrade(options) {
11068
+ const cwd = process.cwd();
11069
+ const detector = new DepsDetectorService();
11070
+ try {
11071
+ const spinner = ora2("\u0110ang ph\xE1t hi\u1EC7n lo\u1EA1i project...").start();
11072
+ const projects = await detector.detectProjects(cwd);
11073
+ if (projects.length === 0) {
11074
+ spinner.fail("Kh\xF4ng t\xECm th\u1EA5y project n\xE0o \u0111\u01B0\u1EE3c h\u1ED7 tr\u1EE3");
11075
+ console.log();
11076
+ console.log(chalk12.dim("H\u1ED7 tr\u1EE3: Node.js (package.json), PHP (composer.json), Python (requirements.txt/Pipfile)"));
11077
+ process.exit(1);
11078
+ }
11079
+ spinner.succeed(`Ph\xE1t hi\u1EC7n ${projects.length} project:`);
11080
+ for (const project of projects) {
11081
+ const icon = getEcosystemIcon(project.ecosystem);
11082
+ const label = getEcosystemLabel(project.ecosystem);
11083
+ console.log(` ${icon} ${label} (${project.manager})`);
11084
+ }
11085
+ console.log();
11086
+ for (const project of projects) {
11087
+ await upgradeEcosystem(project, cwd, options);
11088
+ }
11089
+ console.log();
11090
+ console.log(chalk12.green("\u2705 Ho\xE0n th\xE0nh!"));
11091
+ } catch (error) {
11092
+ console.error(chalk12.red(`
11093
+ \u274C ${error.message}
11094
+ `));
10659
11095
  process.exit(1);
10660
11096
  }
10661
11097
  }
10662
- function isNpmVersion(version) {
10663
- if (version.startsWith("git") || version.startsWith("github") || version.startsWith("file:") || version.startsWith("link:") || version.startsWith("workspace:") || version.includes("://")) {
10664
- return false;
11098
+ async function upgradeEcosystem(project, cwd, options) {
11099
+ const service = getService(project.ecosystem);
11100
+ const label = `${getEcosystemIcon(project.ecosystem)} ${getEcosystemLabel(project.ecosystem)}`;
11101
+ console.log(chalk12.bold.cyan(`
11102
+ ${"\u2501".repeat(80)}`));
11103
+ console.log(chalk12.bold.cyan(`${label}`));
11104
+ console.log(chalk12.bold.cyan("\u2501".repeat(80)));
11105
+ console.log();
11106
+ const spinner = ora2("\u0110ang ki\u1EC3m tra packages...").start();
11107
+ let packages;
11108
+ try {
11109
+ const result = await service.check(cwd, (completed, total) => {
11110
+ spinner.text = `\u0110ang ki\u1EC3m tra packages... (${completed}/${total})`;
11111
+ });
11112
+ packages = result.packages;
11113
+ } catch (error) {
11114
+ spinner.fail("L\u1ED7i ki\u1EC3m tra packages");
11115
+ console.log(chalk12.red(error.message));
11116
+ return;
11117
+ }
11118
+ if (packages.length === 0) {
11119
+ spinner.succeed("T\u1EA5t c\u1EA3 packages \u0111\xE3 c\u1EADp nh\u1EADt");
11120
+ return;
11121
+ }
11122
+ spinner.succeed(`T\xECm th\u1EA5y ${packages.length} packages c\xF3 th\u1EC3 n\xE2ng c\u1EA5p`);
11123
+ console.log();
11124
+ displayUpgradeTable(packages);
11125
+ let selectedPackages;
11126
+ if (options.all) {
11127
+ selectedPackages = packages;
11128
+ console.log(chalk12.cyan(`\u{1F4CB} \u0110\xE3 ch\u1ECDn t\u1EA5t c\u1EA3 ${selectedPackages.length} packages
11129
+ `));
11130
+ } else {
11131
+ try {
11132
+ const selected = await checkbox3({
11133
+ message: "Ch\u1ECDn packages \u0111\u1EC3 upgrade (\u2191\u2193 di chuy\u1EC3n \u2022 space ch\u1ECDn \u2022 a t\u1EA5t c\u1EA3 \u2022 i \u0111\u1EA3o \u2022 \u23CE x\xE1c nh\u1EADn):",
11134
+ choices: packages.map((pkg) => {
11135
+ const icon = pkg.upgradeType === "major" ? "\u{1F534}" : pkg.upgradeType === "minor" ? "\u{1F7E1}" : "\u{1F7E2}";
11136
+ return {
11137
+ name: `${icon} ${pkg.name} (${pkg.current} \u2192 ${pkg.latest}) [${pkg.type}]`,
11138
+ value: pkg.name,
11139
+ checked: true
11140
+ };
11141
+ }),
11142
+ pageSize: 15,
11143
+ theme: checkboxTheme
11144
+ });
11145
+ if (selected.length === 0) {
11146
+ console.log(chalk12.yellow("\u23F8\uFE0F Kh\xF4ng c\xF3 packages n\xE0o \u0111\u01B0\u1EE3c ch\u1ECDn\n"));
11147
+ return;
11148
+ }
11149
+ selectedPackages = packages.filter((p) => selected.includes(p.name));
11150
+ } catch {
11151
+ console.log(chalk12.yellow("\n\u23F8\uFE0F \u0110\xE3 h\u1EE7y\n"));
11152
+ return;
11153
+ }
11154
+ }
11155
+ let shouldProceed;
11156
+ try {
11157
+ shouldProceed = await confirm6({
11158
+ message: `Ti\u1EBFn h\xE0nh upgrade ${selectedPackages.length} packages? (ESC \u0111\u1EC3 h\u1EE7y)`,
11159
+ default: true
11160
+ });
11161
+ } catch {
11162
+ console.log(chalk12.yellow("\n\u23F8\uFE0F \u0110\xE3 h\u1EE7y\n"));
11163
+ return;
11164
+ }
11165
+ if (!shouldProceed) {
11166
+ console.log(chalk12.yellow("\u23F8\uFE0F Upgrade \u0111\xE3 h\u1EE7y\n"));
11167
+ return;
11168
+ }
11169
+ console.log();
11170
+ console.log(chalk12.cyan(`\u{1F527} Package manager: ${project.manager}`));
11171
+ console.log(chalk12.cyan("\u{1F4E5} \u0110ang upgrade...\n"));
11172
+ const commands = service.getUpgradeCommands(selectedPackages);
11173
+ try {
11174
+ if (commands.deps) {
11175
+ console.log(chalk12.dim(`$ ${commands.deps}
11176
+ `));
11177
+ }
11178
+ if (commands.devDeps) {
11179
+ console.log(chalk12.dim(`$ ${commands.devDeps}
11180
+ `));
11181
+ }
11182
+ await service.upgrade(cwd, { packages: selectedPackages });
11183
+ console.log(chalk12.green(`
11184
+ \u2705 \u0110\xE3 upgrade ${selectedPackages.length} packages th\xE0nh c\xF4ng!`));
11185
+ } catch (error) {
11186
+ console.error(chalk12.red("\n\u274C L\u1ED7i khi upgrade:"));
11187
+ console.error(chalk12.red(error.message));
11188
+ console.log(chalk12.yellow("\n\u{1F4A1} B\u1EA1n c\xF3 th\u1EC3 th\u1EED upgrade th\u1EE7 c\xF4ng:"));
11189
+ if (commands.deps) console.log(chalk12.cyan(` ${commands.deps}`));
11190
+ if (commands.devDeps) console.log(chalk12.cyan(` ${commands.devDeps}`));
11191
+ console.log();
11192
+ throw error;
10665
11193
  }
10666
- return true;
10667
11194
  }
10668
11195
  function displayUpgradeTable(packages) {
10669
- console.log(`${colors3.bold}\u{1F4CA} C\xF3 th\u1EC3 upgrade:${colors3.reset}
10670
- `);
10671
- const nameWidth = Math.max(7, ...packages.map((p) => p.name.length));
10672
- const currentWidth = Math.max(8, ...packages.map((p) => p.current.length));
10673
- const latestWidth = Math.max(8, ...packages.map((p) => p.latestWithPrefix.length));
10674
- const header = `\u2502 ${"Package".padEnd(nameWidth)} \u2502 ${"Hi\u1EC7n t\u1EA1i".padEnd(currentWidth)} \u2502 ${"M\u1EDBi nh\u1EA5t".padEnd(latestWidth)} \u2502 Lo\u1EA1i \u2502`;
10675
- const separator = `\u251C${"\u2500".repeat(nameWidth + 2)}\u253C${"\u2500".repeat(currentWidth + 2)}\u253C${"\u2500".repeat(latestWidth + 2)}\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2524`;
10676
- const topBorder = `\u250C${"\u2500".repeat(nameWidth + 2)}\u252C${"\u2500".repeat(currentWidth + 2)}\u252C${"\u2500".repeat(latestWidth + 2)}\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2510`;
10677
- const bottomBorder = `\u2514${"\u2500".repeat(nameWidth + 2)}\u2534${"\u2500".repeat(currentWidth + 2)}\u2534${"\u2500".repeat(latestWidth + 2)}\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2518`;
10678
- console.log(topBorder);
10679
- console.log(header);
10680
- console.log(separator);
11196
+ const table = new Table4({
11197
+ head: [
11198
+ chalk12.cyan("Package"),
11199
+ chalk12.cyan("Hi\u1EC7n t\u1EA1i"),
11200
+ chalk12.cyan("M\u1EDBi nh\u1EA5t"),
11201
+ chalk12.cyan("Lo\u1EA1i")
11202
+ ],
11203
+ style: {
11204
+ head: [],
11205
+ border: ["dim"]
11206
+ }
11207
+ });
10681
11208
  for (const pkg of packages) {
10682
- const typeLabel = pkg.type === "dep" ? "dep " : "dev ";
10683
- const typeColor = pkg.type === "dep" ? colors3.cyan : colors3.yellow;
10684
- console.log(
10685
- `\u2502 ${pkg.name.padEnd(nameWidth)} \u2502 ${colors3.gray}${pkg.current.padEnd(currentWidth)}${colors3.reset} \u2502 ${colors3.green}${pkg.latestWithPrefix.padEnd(latestWidth)}${colors3.reset} \u2502 ${typeColor}${typeLabel}${colors3.reset}\u2502`
10686
- );
11209
+ const upgradeIcon = pkg.upgradeType === "major" ? "\u{1F534}" : pkg.upgradeType === "minor" ? "\u{1F7E1}" : "\u{1F7E2}";
11210
+ table.push([
11211
+ `${upgradeIcon} ${pkg.name}`,
11212
+ chalk12.dim(pkg.current),
11213
+ chalk12.green(pkg.latest),
11214
+ pkg.type === "dev" ? chalk12.dim("dev") : "dep"
11215
+ ]);
11216
+ }
11217
+ console.log(table.toString());
11218
+ console.log();
11219
+ }
11220
+ function getService(ecosystem) {
11221
+ switch (ecosystem) {
11222
+ case "node":
11223
+ return new DepsService();
11224
+ case "php":
11225
+ return new DepsPhpService();
11226
+ case "python":
11227
+ return new DepsPythonService();
11228
+ }
11229
+ }
11230
+ function getEcosystemIcon(ecosystem) {
11231
+ switch (ecosystem) {
11232
+ case "node":
11233
+ return "\u{1F4E6}";
11234
+ case "php":
11235
+ return "\u{1F418}";
11236
+ case "python":
11237
+ return "\u{1F40D}";
11238
+ default:
11239
+ return "\u{1F4E6}";
11240
+ }
11241
+ }
11242
+ function getEcosystemLabel(ecosystem) {
11243
+ switch (ecosystem) {
11244
+ case "node":
11245
+ return "Node.js";
11246
+ case "php":
11247
+ return "PHP/Composer";
11248
+ case "python":
11249
+ return "Python";
11250
+ default:
11251
+ return ecosystem;
10687
11252
  }
10688
- console.log(bottomBorder);
10689
- console.log("");
10690
11253
  }
10691
11254
 
10692
11255
  // src/commands/deps/index.ts
10693
11256
  function showDepsHelp() {
10694
- console.log(chalk11.bold.cyan("\u{1F4E6} jai1 deps") + chalk11.dim(" - Qu\u1EA3n l\xFD dependencies trong project"));
11257
+ console.log(chalk13.bold.cyan("\u{1F4E6} jai1 deps") + chalk13.dim(" - Qu\u1EA3n l\xFD dependencies trong project"));
11258
+ console.log();
11259
+ console.log(chalk13.bold("C\xE1c l\u1EC7nh:"));
11260
+ console.log(` ${chalk13.cyan("check")} Ki\u1EC3m tra c\xE1c packages c\u1EA7n upgrade`);
11261
+ console.log(` ${chalk13.cyan("upgrade")} N\xE2ng c\u1EA5p dependencies l\xEAn phi\xEAn b\u1EA3n m\u1EDBi nh\u1EA5t`);
10695
11262
  console.log();
10696
- console.log(chalk11.bold("C\xE1c l\u1EC7nh:"));
10697
- console.log(` ${chalk11.cyan("upgrade")} N\xE2ng c\u1EA5p dependencies l\xEAn phi\xEAn b\u1EA3n m\u1EDBi nh\u1EA5t`);
11263
+ console.log(chalk13.bold("H\u1ED7 tr\u1EE3:"));
11264
+ console.log(chalk13.dim(" \u2022 Node.js (npm, pnpm, yarn, bun)"));
11265
+ console.log(chalk13.dim(" \u2022 PHP/Composer (v\u1EDBi b\u1EA3o v\u1EC7 Laravel major version)"));
11266
+ console.log(chalk13.dim(" \u2022 Python (pip, pipenv)"));
10698
11267
  console.log();
10699
- console.log(chalk11.bold("V\xED d\u1EE5:"));
10700
- console.log(chalk11.dim(" $ jai1 deps upgrade"));
10701
- console.log(chalk11.dim(" $ jai1 deps upgrade --dry-run"));
11268
+ console.log(chalk13.bold("V\xED d\u1EE5:"));
11269
+ console.log(chalk13.dim(" $ jai1 deps check"));
11270
+ console.log(chalk13.dim(" $ jai1 deps upgrade"));
11271
+ console.log(chalk13.dim(" $ jai1 deps upgrade --all"));
10702
11272
  console.log();
10703
- console.log(chalk11.dim('Ch\u1EA1y "jai1 deps <l\u1EC7nh> --help" \u0111\u1EC3 xem chi ti\u1EBFt'));
11273
+ console.log(chalk13.dim('Ch\u1EA1y "jai1 deps <l\u1EC7nh> --help" \u0111\u1EC3 xem chi ti\u1EBFt'));
10704
11274
  }
10705
11275
  function createDepsCommand() {
10706
- const depsCommand = new Command37("deps").description("Qu\u1EA3n l\xFD dependencies trong project").action(() => {
11276
+ const depsCommand = new Command38("deps").description("Qu\u1EA3n l\xFD dependencies trong project").action(() => {
10707
11277
  showDepsHelp();
10708
11278
  });
11279
+ depsCommand.addCommand(createDepsCheckCommand());
10709
11280
  depsCommand.addCommand(createDepsUpgradeCommand());
10710
11281
  return depsCommand;
10711
11282
  }
10712
11283
 
10713
11284
  // src/commands/kit/index.ts
10714
- import { Command as Command41 } from "commander";
10715
- import chalk13 from "chalk";
11285
+ import { Command as Command42 } from "commander";
11286
+ import chalk15 from "chalk";
10716
11287
 
10717
11288
  // src/commands/kit/list.ts
10718
- import { Command as Command38 } from "commander";
10719
- import chalk12 from "chalk";
10720
- import Table3 from "cli-table3";
11289
+ import { Command as Command39 } from "commander";
11290
+ import chalk14 from "chalk";
11291
+ import Table5 from "cli-table3";
10721
11292
 
10722
11293
  // src/services/starter-kit.service.ts
10723
- import { promises as fs14 } from "fs";
10724
- import { join as join6 } from "path";
11294
+ import { promises as fs17 } from "fs";
11295
+ import { join as join7 } from "path";
10725
11296
  import AdmZip from "adm-zip";
10726
11297
  var StarterKitService = class {
10727
11298
  /**
@@ -10768,29 +11339,29 @@ var StarterKitService = class {
10768
11339
  throw new NetworkError(`Failed to download kit: HTTP ${response.status}`);
10769
11340
  }
10770
11341
  if (onProgress) onProgress(30);
10771
- const tmpDir = join6(process.env.TMPDIR || "/tmp", "jai1-kits");
10772
- await fs14.mkdir(tmpDir, { recursive: true });
10773
- const tmpFile = join6(tmpDir, `${slug}.zip`);
11342
+ const tmpDir = join7(process.env.TMPDIR || "/tmp", "jai1-kits");
11343
+ await fs17.mkdir(tmpDir, { recursive: true });
11344
+ const tmpFile = join7(tmpDir, `${slug}.zip`);
10774
11345
  const buffer = await response.arrayBuffer();
10775
- await fs14.writeFile(tmpFile, Buffer.from(buffer));
11346
+ await fs17.writeFile(tmpFile, Buffer.from(buffer));
10776
11347
  if (onProgress) onProgress(60);
10777
11348
  const zip = new AdmZip(tmpFile);
10778
- await fs14.mkdir(targetDir, { recursive: true });
11349
+ await fs17.mkdir(targetDir, { recursive: true });
10779
11350
  zip.extractAllTo(targetDir, true);
10780
11351
  if (onProgress) onProgress(100);
10781
- await fs14.unlink(tmpFile);
11352
+ await fs17.unlink(tmpFile);
10782
11353
  }
10783
11354
  };
10784
11355
 
10785
11356
  // src/commands/kit/list.ts
10786
11357
  function createKitListCommand() {
10787
- return new Command38("list").description("List available starter kits").option("-c, --category <category>", "Filter by category (backend, frontend, fullstack)").option("-s, --search <term>", "Search kits by name or description").action(async (options) => {
11358
+ return new Command39("list").description("List available starter kits").option("-c, --category <category>", "Filter by category (backend, frontend, fullstack)").option("-s, --search <term>", "Search kits by name or description").action(async (options) => {
10788
11359
  const configService = new ConfigService();
10789
11360
  const config = await configService.load();
10790
11361
  if (!config) {
10791
11362
  throw new ValidationError('Not initialized. Run "jai1 auth" first.');
10792
11363
  }
10793
- console.log(chalk12.cyan("\u{1F4E6} \u0110ang t\u1EA3i danh s\xE1ch starter kits..."));
11364
+ console.log(chalk14.cyan("\u{1F4E6} \u0110ang t\u1EA3i danh s\xE1ch starter kits..."));
10794
11365
  console.log();
10795
11366
  const kitService = new StarterKitService();
10796
11367
  const kits = await kitService.list(config, {
@@ -10798,9 +11369,9 @@ function createKitListCommand() {
10798
11369
  search: options.search
10799
11370
  });
10800
11371
  if (kits.length === 0) {
10801
- console.log(chalk12.yellow("Kh\xF4ng t\xECm th\u1EA5y starter kits n\xE0o."));
11372
+ console.log(chalk14.yellow("Kh\xF4ng t\xECm th\u1EA5y starter kits n\xE0o."));
10802
11373
  if (options.category || options.search) {
10803
- console.log(chalk12.dim("Th\u1EED b\u1ECF filter \u0111\u1EC3 xem t\u1EA5t c\u1EA3."));
11374
+ console.log(chalk14.dim("Th\u1EED b\u1ECF filter \u0111\u1EC3 xem t\u1EA5t c\u1EA3."));
10804
11375
  }
10805
11376
  return;
10806
11377
  }
@@ -10824,35 +11395,35 @@ function createKitListCommand() {
10824
11395
  const categoryKits = byCategory[category];
10825
11396
  const categoryIcon = category === "frontend" ? "\u{1F3A8}" : category === "backend" ? "\u2699\uFE0F" : category === "fullstack" ? "\u{1F680}" : "\u{1F4E6}";
10826
11397
  console.log(
10827
- chalk12.bold(`${categoryIcon} ${category.charAt(0).toUpperCase() + category.slice(1)}`)
11398
+ chalk14.bold(`${categoryIcon} ${category.charAt(0).toUpperCase() + category.slice(1)}`)
10828
11399
  );
10829
- const table = new Table3({
11400
+ const table = new Table5({
10830
11401
  head: [
10831
- chalk12.cyan("Slug"),
10832
- chalk12.cyan("M\xF4 t\u1EA3"),
10833
- chalk12.cyan("Version")
11402
+ chalk14.cyan("Slug"),
11403
+ chalk14.cyan("M\xF4 t\u1EA3"),
11404
+ chalk14.cyan("Version")
10834
11405
  ],
10835
11406
  style: { head: [], border: ["gray"] }
10836
11407
  });
10837
11408
  for (const kit of categoryKits) {
10838
11409
  table.push([
10839
- chalk12.white(kit.slug),
10840
- chalk12.dim(kit.description.slice(0, 50)),
10841
- chalk12.green(`v${kit.version}`)
11410
+ chalk14.white(kit.slug),
11411
+ chalk14.dim(kit.description.slice(0, 50)),
11412
+ chalk14.green(`v${kit.version}`)
10842
11413
  ]);
10843
11414
  }
10844
11415
  console.log(table.toString());
10845
11416
  console.log();
10846
11417
  }
10847
- console.log(chalk12.dim(`T\u1ED5ng c\u1ED9ng: ${kits.length} starter kit(s)`));
10848
- console.log(chalk12.dim('\n\u{1F4A1} Ch\u1EA1y "jai1 kit create <slug>" \u0111\u1EC3 t\u1EA1o project m\u1EDBi'));
11418
+ console.log(chalk14.dim(`T\u1ED5ng c\u1ED9ng: ${kits.length} starter kit(s)`));
11419
+ console.log(chalk14.dim('\n\u{1F4A1} Ch\u1EA1y "jai1 kit create <slug>" \u0111\u1EC3 t\u1EA1o project m\u1EDBi'));
10849
11420
  });
10850
11421
  }
10851
11422
 
10852
11423
  // src/commands/kit/info.ts
10853
- import { Command as Command39 } from "commander";
11424
+ import { Command as Command40 } from "commander";
10854
11425
  function createKitInfoCommand() {
10855
- return new Command39("info").description("Show detailed information about a starter kit").argument("<slug>", "Starter kit slug").action(async (slug) => {
11426
+ return new Command40("info").description("Show detailed information about a starter kit").argument("<slug>", "Starter kit slug").action(async (slug) => {
10856
11427
  const configService = new ConfigService();
10857
11428
  const config = await configService.load();
10858
11429
  if (!config) {
@@ -10901,9 +11472,9 @@ Post-Init Commands:`);
10901
11472
  }
10902
11473
 
10903
11474
  // src/commands/kit/create.ts
10904
- import { Command as Command40 } from "commander";
10905
- import { promises as fs15 } from "fs";
10906
- import { join as join7 } from "path";
11475
+ import { Command as Command41 } from "commander";
11476
+ import { promises as fs18 } from "fs";
11477
+ import { join as join8 } from "path";
10907
11478
  import { select as select3, input as input2, checkbox as checkbox4 } from "@inquirer/prompts";
10908
11479
  import { execa as execa2 } from "execa";
10909
11480
 
@@ -10946,7 +11517,7 @@ var HookExecutor = class {
10946
11517
 
10947
11518
  // src/commands/kit/create.ts
10948
11519
  function createKitCreateCommand() {
10949
- return new Command40("create").description("Create a new project from a starter kit").argument("<slug>", "Starter kit slug").argument("[directory]", "Project directory (default: ./<slug>)").option("-y, --yes", "Auto mode - use defaults, no prompts").option("--name <name>", "Project name").option("--skip-install", "Skip dependency installation").option("--skip-git", "Skip git initialization").option("--skip-framework", "Skip framework apply").option("--skip-ide", "Skip IDE sync").action(async (slug, directory, options) => {
11520
+ return new Command41("create").description("Create a new project from a starter kit").argument("<slug>", "Starter kit slug").argument("[directory]", "Project directory (default: ./<slug>)").option("-y, --yes", "Auto mode - use defaults, no prompts").option("--name <name>", "Project name").option("--skip-install", "Skip dependency installation").option("--skip-git", "Skip git initialization").option("--skip-framework", "Skip framework apply").option("--skip-ide", "Skip IDE sync").action(async (slug, directory, options) => {
10950
11521
  const configService = new ConfigService();
10951
11522
  const config = await configService.load();
10952
11523
  if (!config) {
@@ -10966,9 +11537,9 @@ function createKitCreateCommand() {
10966
11537
  }
10967
11538
  }
10968
11539
  }
10969
- const targetDir = directory || join7(process.cwd(), options.name || slug);
11540
+ const targetDir = directory || join8(process.cwd(), options.name || slug);
10970
11541
  try {
10971
- await fs15.access(targetDir);
11542
+ await fs18.access(targetDir);
10972
11543
  throw new Error(`Directory already exists: ${targetDir}`);
10973
11544
  } catch (error) {
10974
11545
  if (error.code !== "ENOENT") {
@@ -11005,7 +11576,8 @@ function createKitCreateCommand() {
11005
11576
  const answer = await select3({
11006
11577
  message: v.prompt,
11007
11578
  choices: v.options.map((opt) => ({ name: opt, value: opt })),
11008
- default: v.default
11579
+ default: v.default,
11580
+ theme: selectTheme
11009
11581
  });
11010
11582
  variables[v.name] = answer;
11011
11583
  } else {
@@ -11024,9 +11596,13 @@ function createKitCreateCommand() {
11024
11596
  if (!options.skipFramework && kit.config.framework?.apply) {
11025
11597
  console.log("\u{1F916} Applying jai1 framework...");
11026
11598
  const componentsService = new ComponentsService(targetDir);
11027
- for (const filepath of kit.config.framework.components) {
11599
+ const expandedPaths = await componentsService.expandPaths(
11600
+ config,
11601
+ kit.config.framework.components
11602
+ );
11603
+ for (const filepath of expandedPaths) {
11028
11604
  console.log(` \u{1F4E5} Installing ${filepath}...`);
11029
- await componentsService.install(config, filepath);
11605
+ await componentsService.install(config, filepath, join8(targetDir, ".jai1"));
11030
11606
  }
11031
11607
  console.log(" \u2713 Framework components applied");
11032
11608
  }
@@ -11064,7 +11640,8 @@ function createKitCreateCommand() {
11064
11640
  choices: ideChoices.map((c) => ({
11065
11641
  ...c,
11066
11642
  checked: defaultTargets.includes(c.value)
11067
- }))
11643
+ })),
11644
+ theme: checkboxTheme
11068
11645
  });
11069
11646
  selectedIdes = answer;
11070
11647
  }
@@ -11086,7 +11663,7 @@ function createKitCreateCommand() {
11086
11663
  async function applyVariableSubstitution(dir, variables) {
11087
11664
  const files = await getAllFiles(dir);
11088
11665
  for (const file of files) {
11089
- let content = await fs15.readFile(file, "utf-8");
11666
+ let content = await fs18.readFile(file, "utf-8");
11090
11667
  let modified = false;
11091
11668
  for (const [key, value] of Object.entries(variables)) {
11092
11669
  const regex = new RegExp(`\\{\\{${key}\\}\\}`, "g");
@@ -11096,15 +11673,15 @@ async function applyVariableSubstitution(dir, variables) {
11096
11673
  }
11097
11674
  }
11098
11675
  if (modified) {
11099
- await fs15.writeFile(file, content, "utf-8");
11676
+ await fs18.writeFile(file, content, "utf-8");
11100
11677
  }
11101
11678
  }
11102
11679
  }
11103
11680
  async function getAllFiles(dir) {
11104
11681
  const files = [];
11105
- const entries = await fs15.readdir(dir, { withFileTypes: true });
11682
+ const entries = await fs18.readdir(dir, { withFileTypes: true });
11106
11683
  for (const entry of entries) {
11107
- const fullPath = join7(dir, entry.name);
11684
+ const fullPath = join8(dir, entry.name);
11108
11685
  if (entry.isDirectory()) {
11109
11686
  if (!entry.name.startsWith(".") && entry.name !== "node_modules") {
11110
11687
  files.push(...await getAllFiles(fullPath));
@@ -11118,23 +11695,23 @@ async function getAllFiles(dir) {
11118
11695
 
11119
11696
  // src/commands/kit/index.ts
11120
11697
  function showKitHelp() {
11121
- console.log(chalk13.bold.cyan("\u{1F4E6} jai1 kit") + chalk13.dim(" - Qu\u1EA3n l\xFD starter kits"));
11698
+ console.log(chalk15.bold.cyan("\u{1F4E6} jai1 kit") + chalk15.dim(" - Qu\u1EA3n l\xFD starter kits"));
11122
11699
  console.log();
11123
- console.log(chalk13.bold("C\xE1c l\u1EC7nh:"));
11124
- console.log(` ${chalk13.cyan("list")} Li\u1EC7t k\xEA c\xE1c starter kits c\xF3 s\u1EB5n`);
11125
- console.log(` ${chalk13.cyan("info")} Xem chi ti\u1EBFt m\u1ED9t starter kit`);
11126
- console.log(` ${chalk13.cyan("create")} T\u1EA1o project m\u1EDBi t\u1EEB starter kit`);
11700
+ console.log(chalk15.bold("C\xE1c l\u1EC7nh:"));
11701
+ console.log(` ${chalk15.cyan("list")} Li\u1EC7t k\xEA c\xE1c starter kits c\xF3 s\u1EB5n`);
11702
+ console.log(` ${chalk15.cyan("info")} Xem chi ti\u1EBFt m\u1ED9t starter kit`);
11703
+ console.log(` ${chalk15.cyan("create")} T\u1EA1o project m\u1EDBi t\u1EEB starter kit`);
11127
11704
  console.log();
11128
- console.log(chalk13.bold("V\xED d\u1EE5:"));
11129
- console.log(chalk13.dim(" $ jai1 kit list"));
11130
- console.log(chalk13.dim(" $ jai1 kit list --category frontend"));
11131
- console.log(chalk13.dim(" $ jai1 kit info next-tw4-shadcn"));
11132
- console.log(chalk13.dim(" $ jai1 kit create next-tw4-shadcn my-project"));
11705
+ console.log(chalk15.bold("V\xED d\u1EE5:"));
11706
+ console.log(chalk15.dim(" $ jai1 kit list"));
11707
+ console.log(chalk15.dim(" $ jai1 kit list --category frontend"));
11708
+ console.log(chalk15.dim(" $ jai1 kit info next-tw4-shadcn"));
11709
+ console.log(chalk15.dim(" $ jai1 kit create next-tw4-shadcn my-project"));
11133
11710
  console.log();
11134
- console.log(chalk13.dim('Ch\u1EA1y "jai1 kit <l\u1EC7nh> --help" \u0111\u1EC3 xem chi ti\u1EBFt'));
11711
+ console.log(chalk15.dim('Ch\u1EA1y "jai1 kit <l\u1EC7nh> --help" \u0111\u1EC3 xem chi ti\u1EBFt'));
11135
11712
  }
11136
11713
  function createKitCommand() {
11137
- const cmd = new Command41("kit").description("Manage starter kits for new projects").action(() => {
11714
+ const cmd = new Command42("kit").description("Manage starter kits for new projects").action(() => {
11138
11715
  showKitHelp();
11139
11716
  });
11140
11717
  cmd.addCommand(createKitListCommand());
@@ -11144,21 +11721,21 @@ function createKitCommand() {
11144
11721
  }
11145
11722
 
11146
11723
  // src/commands/rules/index.ts
11147
- import { Command as Command48 } from "commander";
11148
- import chalk15 from "chalk";
11724
+ import { Command as Command49 } from "commander";
11725
+ import chalk17 from "chalk";
11149
11726
 
11150
11727
  // src/commands/rules/list.ts
11151
- import { Command as Command42 } from "commander";
11152
- import chalk14 from "chalk";
11153
- import Table4 from "cli-table3";
11728
+ import { Command as Command43 } from "commander";
11729
+ import chalk16 from "chalk";
11730
+ import Table6 from "cli-table3";
11154
11731
  function createRulesListCommand() {
11155
- return new Command42("list").description("List available rule presets").option("--json", "Output as JSON").action(async (options) => {
11732
+ return new Command43("list").description("List available rule presets").option("--json", "Output as JSON").action(async (options) => {
11156
11733
  const configService = new ConfigService();
11157
11734
  const config = await configService.load();
11158
11735
  if (!config) {
11159
11736
  throw new ValidationError('Not initialized. Run "jai1 auth" first.');
11160
11737
  }
11161
- console.log(chalk14.cyan("\u{1F4CB} \u0110ang t\u1EA3i danh s\xE1ch rule presets..."));
11738
+ console.log(chalk16.cyan("\u{1F4CB} \u0110ang t\u1EA3i danh s\xE1ch rule presets..."));
11162
11739
  console.log();
11163
11740
  try {
11164
11741
  const response = await fetch(`${config.apiUrl}/api/rules/presets`, {
@@ -11175,23 +11752,23 @@ function createRulesListCommand() {
11175
11752
  return;
11176
11753
  }
11177
11754
  if (data.total === 0) {
11178
- console.log(chalk14.yellow("Kh\xF4ng c\xF3 presets n\xE0o."));
11755
+ console.log(chalk16.yellow("Kh\xF4ng c\xF3 presets n\xE0o."));
11179
11756
  return;
11180
11757
  }
11181
11758
  console.log(
11182
- chalk14.green(`\u2713 T\xECm th\u1EA5y ${chalk14.bold(data.total)} preset${data.total > 1 ? "s" : ""}`)
11759
+ chalk16.green(`\u2713 T\xECm th\u1EA5y ${chalk16.bold(data.total)} preset${data.total > 1 ? "s" : ""}`)
11183
11760
  );
11184
11761
  console.log();
11185
11762
  for (const preset of data.presets) {
11186
- console.log(chalk14.bold.cyan(`\u{1F4E6} ${preset.slug}`));
11187
- const table = new Table4({
11763
+ console.log(chalk16.bold.cyan(`\u{1F4E6} ${preset.slug}`));
11764
+ const table = new Table6({
11188
11765
  style: { head: [], border: ["gray"], compact: true },
11189
11766
  colWidths: [15, 55]
11190
11767
  });
11191
11768
  table.push(
11192
- [chalk14.dim("T\xEAn"), chalk14.white(preset.name)],
11193
- [chalk14.dim("M\xF4 t\u1EA3"), chalk14.white(preset.description)],
11194
- [chalk14.dim("Version"), chalk14.green(`v${preset.version}`)]
11769
+ [chalk16.dim("T\xEAn"), chalk16.white(preset.name)],
11770
+ [chalk16.dim("M\xF4 t\u1EA3"), chalk16.white(preset.description)],
11771
+ [chalk16.dim("Version"), chalk16.green(`v${preset.version}`)]
11195
11772
  );
11196
11773
  const stackParts = [];
11197
11774
  if (preset.stack.frontend) stackParts.push(preset.stack.frontend);
@@ -11199,16 +11776,16 @@ function createRulesListCommand() {
11199
11776
  if (preset.stack.css) stackParts.push(preset.stack.css);
11200
11777
  if (preset.stack.database) stackParts.push(preset.stack.database);
11201
11778
  if (stackParts.length > 0) {
11202
- table.push([chalk14.dim("Stack"), chalk14.yellow(stackParts.join(" + "))]);
11779
+ table.push([chalk16.dim("Stack"), chalk16.yellow(stackParts.join(" + "))]);
11203
11780
  }
11204
11781
  table.push(
11205
- [chalk14.dim("Tags"), chalk14.dim(preset.tags.join(", ") || "-")],
11206
- [chalk14.dim("Downloads"), chalk14.white(preset.downloads.toString())]
11782
+ [chalk16.dim("Tags"), chalk16.dim(preset.tags.join(", ") || "-")],
11783
+ [chalk16.dim("Downloads"), chalk16.white(preset.downloads.toString())]
11207
11784
  );
11208
11785
  console.log(table.toString());
11209
11786
  console.log();
11210
11787
  }
11211
- console.log(chalk14.dim('\u{1F4A1} Ch\u1EA1y "jai1 rules init --preset=<slug>" \u0111\u1EC3 \xE1p d\u1EE5ng preset'));
11788
+ console.log(chalk16.dim('\u{1F4A1} Ch\u1EA1y "jai1 rules apply <name>" \u0111\u1EC3 \xE1p d\u1EE5ng preset'));
11212
11789
  } catch (error) {
11213
11790
  throw new Error(
11214
11791
  `L\u1ED7i khi t\u1EA3i presets: ${error instanceof Error ? error.message : String(error)}`
@@ -11218,12 +11795,132 @@ function createRulesListCommand() {
11218
11795
  }
11219
11796
 
11220
11797
  // src/commands/rules/init.ts
11221
- import { Command as Command43 } from "commander";
11222
- import { promises as fs16 } from "fs";
11223
- import { join as join8 } from "path";
11798
+ import { Command as Command44 } from "commander";
11799
+ import { promises as fs20 } from "fs";
11800
+ import { join as join10 } from "path";
11224
11801
  import { select as select4, confirm as confirm7 } from "@inquirer/prompts";
11802
+
11803
+ // src/services/project-config.service.ts
11804
+ import { promises as fs19 } from "fs";
11805
+ import { join as join9 } from "path";
11806
+ var ProjectConfigService = class {
11807
+ projectRoot;
11808
+ configDir;
11809
+ configPath;
11810
+ constructor(projectRoot = process.cwd()) {
11811
+ this.projectRoot = projectRoot;
11812
+ this.configDir = join9(this.projectRoot, ".jai1");
11813
+ this.configPath = join9(this.configDir, "project.json");
11814
+ }
11815
+ /**
11816
+ * Check if config file exists
11817
+ */
11818
+ async exists() {
11819
+ try {
11820
+ await fs19.access(this.configPath);
11821
+ return true;
11822
+ } catch {
11823
+ return false;
11824
+ }
11825
+ }
11826
+ /**
11827
+ * Load full project configuration
11828
+ * @returns Config object or null if not found
11829
+ */
11830
+ async load() {
11831
+ if (!await this.exists()) {
11832
+ return null;
11833
+ }
11834
+ try {
11835
+ const content = await fs19.readFile(this.configPath, "utf-8");
11836
+ return JSON.parse(content);
11837
+ } catch (error) {
11838
+ throw new Error(
11839
+ `Failed to load project config: ${error instanceof Error ? error.message : String(error)}`
11840
+ );
11841
+ }
11842
+ }
11843
+ /**
11844
+ * Save full project configuration
11845
+ * Creates .jai1 directory if it doesn't exist
11846
+ */
11847
+ async save(config) {
11848
+ try {
11849
+ await fs19.mkdir(this.configDir, { recursive: true });
11850
+ await fs19.writeFile(this.configPath, JSON.stringify(config, null, 2), "utf-8");
11851
+ } catch (error) {
11852
+ throw new Error(
11853
+ `Failed to save project config: ${error instanceof Error ? error.message : String(error)}`
11854
+ );
11855
+ }
11856
+ }
11857
+ /**
11858
+ * Load rules configuration only
11859
+ * @returns RulesConfig or null if not found
11860
+ */
11861
+ async loadRules() {
11862
+ const config = await this.load();
11863
+ return config?.rules ?? null;
11864
+ }
11865
+ /**
11866
+ * Save rules configuration
11867
+ * Merges with existing config, preserving other sections
11868
+ */
11869
+ async saveRules(rulesConfig) {
11870
+ const existingConfig = await this.load() ?? {};
11871
+ existingConfig.rules = rulesConfig;
11872
+ await this.save(existingConfig);
11873
+ }
11874
+ /**
11875
+ * Update rules configuration partially
11876
+ * Merges with existing rules config
11877
+ */
11878
+ async updateRules(partialRulesConfig) {
11879
+ const existingRules = await this.loadRules();
11880
+ if (!existingRules) {
11881
+ throw new Error('No rules configuration found. Run "jai1 rules apply" first.');
11882
+ }
11883
+ const updatedRules = {
11884
+ ...existingRules,
11885
+ ...partialRulesConfig
11886
+ };
11887
+ await this.saveRules(updatedRules);
11888
+ }
11889
+ /**
11890
+ * Add a backup entry to rules config
11891
+ * Keeps only the last 5 backups
11892
+ */
11893
+ async addBackup(backup) {
11894
+ const rules = await this.loadRules();
11895
+ if (!rules) {
11896
+ throw new Error("No rules configuration found.");
11897
+ }
11898
+ const backups = [backup, ...rules.backups ?? []].slice(0, 5);
11899
+ await this.updateRules({ backups });
11900
+ }
11901
+ /**
11902
+ * Get config file path
11903
+ */
11904
+ getConfigPath() {
11905
+ return this.configPath;
11906
+ }
11907
+ /**
11908
+ * Get config directory path (.jai1/)
11909
+ */
11910
+ getConfigDir() {
11911
+ return this.configDir;
11912
+ }
11913
+ /**
11914
+ * Get project root path
11915
+ */
11916
+ getProjectRoot() {
11917
+ return this.projectRoot;
11918
+ }
11919
+ };
11920
+
11921
+ // src/commands/rules/init.ts
11225
11922
  function createRulesInitCommand() {
11226
- return new Command43("init").description("Apply rule preset to project").option("--preset <slug>", "Preset slug to apply").option("--output <format>", "Output format: cursor, agents-md, both (default: cursor)", "cursor").option("-y, --yes", "Skip confirmations").action(async (options) => {
11923
+ return new Command44("init").description("Apply rule preset to project").option("--preset <slug>", "Preset slug to apply").option("--output <format>", "Output format: cursor, agents-md, both (default: cursor)", "cursor").option("-y, --yes", "Skip confirmations").action(async (options) => {
11227
11924
  const configService = new ConfigService();
11228
11925
  const config = await configService.load();
11229
11926
  if (!config) {
@@ -11251,7 +11948,8 @@ function createRulesInitCommand() {
11251
11948
  name: `${p.name} - ${p.description}`,
11252
11949
  value: p.slug,
11253
11950
  description: `v${p.version} | ${p.tags.join(", ")}`
11254
- }))
11951
+ })),
11952
+ theme: selectTheme
11255
11953
  });
11256
11954
  }
11257
11955
  console.log(`
@@ -11279,7 +11977,8 @@ function createRulesInitCommand() {
11279
11977
  { name: "Cursor (.cursor/rules/)", value: "cursor" },
11280
11978
  { name: "AGENTS.md (single file)", value: "agents-md" },
11281
11979
  { name: "Both", value: "both" }
11282
- ]
11980
+ ],
11981
+ theme: selectTheme
11283
11982
  });
11284
11983
  }
11285
11984
  if (!options.yes) {
@@ -11299,14 +11998,16 @@ function createRulesInitCommand() {
11299
11998
  if (outputFormat === "agents-md" || outputFormat === "both") {
11300
11999
  await applyAgentsMdFormat(bundle);
11301
12000
  }
11302
- const projectConfig = {
12001
+ const projectConfigService = new ProjectConfigService();
12002
+ const rulesConfig = {
11303
12003
  preset: bundle.preset.slug,
11304
12004
  version: bundle.preset.version,
11305
12005
  appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
12006
+ ides: [],
11306
12007
  customContext: "09-custom.mdc"
11307
12008
  };
11308
- await fs16.writeFile("jai1-rules.json", JSON.stringify(projectConfig, null, 2));
11309
- console.log("\u2713 Created jai1-rules.json");
12009
+ await projectConfigService.saveRules(rulesConfig);
12010
+ console.log("\u2713 Created .jai1/project.json");
11310
12011
  console.log("\n\u2705 Preset applied successfully!\n");
11311
12012
  console.log("Next steps:");
11312
12013
  console.log(" 1. Edit 09-custom.mdc to add project-specific rules");
@@ -11315,11 +12016,11 @@ function createRulesInitCommand() {
11315
12016
  });
11316
12017
  }
11317
12018
  async function applyCursorFormat(bundle) {
11318
- const rulesDir = join8(process.cwd(), ".cursor", "rules");
11319
- await fs16.mkdir(rulesDir, { recursive: true });
12019
+ const rulesDir = join10(process.cwd(), ".cursor", "rules");
12020
+ await fs20.mkdir(rulesDir, { recursive: true });
11320
12021
  for (const [filename, content] of Object.entries(bundle.files)) {
11321
- const filePath = join8(rulesDir, filename);
11322
- await fs16.writeFile(filePath, content, "utf-8");
12022
+ const filePath = join10(rulesDir, filename);
12023
+ await fs20.writeFile(filePath, content, "utf-8");
11323
12024
  console.log(`\u2713 Created .cursor/rules/${filename}`);
11324
12025
  }
11325
12026
  }
@@ -11346,14 +12047,14 @@ async function applyAgentsMdFormat(bundle) {
11346
12047
  }
11347
12048
  }
11348
12049
  const agentsMd = sections.join("\n");
11349
- await fs16.writeFile("AGENTS.md", agentsMd, "utf-8");
12050
+ await fs20.writeFile("AGENTS.md", agentsMd, "utf-8");
11350
12051
  console.log("\u2713 Created AGENTS.md");
11351
12052
  }
11352
12053
 
11353
12054
  // src/commands/rules/apply.ts
11354
- import { Command as Command44 } from "commander";
11355
- import { promises as fs18 } from "fs";
11356
- import { join as join10 } from "path";
12055
+ import { Command as Command45 } from "commander";
12056
+ import { promises as fs22 } from "fs";
12057
+ import { join as join12 } from "path";
11357
12058
  import { select as select5, confirm as confirm8, checkbox as checkbox5 } from "@inquirer/prompts";
11358
12059
 
11359
12060
  // src/services/rules-generator.service.ts
@@ -11388,11 +12089,6 @@ var RulesGeneratorService = class {
11388
12089
  */
11389
12090
  generateCursorFiles(bundle, format) {
11390
12091
  const files = [];
11391
- files.push({
11392
- path: `${format.rulesPath}/00-jai1${format.fileExtension}`,
11393
- content: this.generateCursorJai1Rule(),
11394
- description: "Jai1 Framework base rule"
11395
- });
11396
12092
  for (const [filename, content] of Object.entries(bundle.files)) {
11397
12093
  files.push({
11398
12094
  path: `${format.rulesPath}/${filename}`,
@@ -11403,28 +12099,11 @@ var RulesGeneratorService = class {
11403
12099
  }
11404
12100
  return files;
11405
12101
  }
11406
- /**
11407
- * Generate Cursor jai1 base rule
11408
- */
11409
- generateCursorJai1Rule() {
11410
- return `---
11411
- description: Jai1 Framework integration and skill loading
11412
- globs: ["**/*"]
11413
- alwaysApply: true
11414
- ---
11415
-
11416
- ${JAI1_BASE_RULE}`;
11417
- }
11418
12102
  /**
11419
12103
  * Generate Windsurf format (.md files with trigger metadata)
11420
12104
  */
11421
12105
  generateWindsurfFiles(bundle, format) {
11422
12106
  const files = [];
11423
- files.push({
11424
- path: `${format.rulesPath}/00-jai1${format.fileExtension}`,
11425
- content: this.generateWindsurfJai1Rule(),
11426
- description: "Jai1 Framework base rule"
11427
- });
11428
12107
  for (const [filename, content] of Object.entries(bundle.files)) {
11429
12108
  const converted = this.convertToWindsurf(content, filename);
11430
12109
  const newFilename = filename.replace(/\.mdc$/, ".md");
@@ -11459,27 +12138,12 @@ trigger: ${trigger}
11459
12138
 
11460
12139
  ${body.trim()}
11461
12140
  `;
11462
- }
11463
- /**
11464
- * Generate Windsurf jai1 base rule
11465
- */
11466
- generateWindsurfJai1Rule() {
11467
- return `---
11468
- trigger: always
11469
- ---
11470
-
11471
- ${JAI1_BASE_RULE}`;
11472
12141
  }
11473
12142
  /**
11474
12143
  * Generate Antigravity format (.md files with trigger metadata + @AGENTS.md reference)
11475
12144
  */
11476
12145
  generateAntigravityFiles(bundle, format) {
11477
12146
  const files = [];
11478
- files.push({
11479
- path: `${format.rulesPath}/00-jai1${format.fileExtension}`,
11480
- content: this.generateAntigravityJai1Rule(),
11481
- description: "Jai1 Framework base rule"
11482
- });
11483
12147
  for (const [filename, content] of Object.entries(bundle.files)) {
11484
12148
  const converted = this.convertToAntigravity(content, filename);
11485
12149
  const newFilename = filename.replace(/\.mdc$/, ".md");
@@ -11514,29 +12178,12 @@ trigger: ${trigger}
11514
12178
 
11515
12179
  ${body.trim()}
11516
12180
  `;
11517
- }
11518
- /**
11519
- * Generate Antigravity jai1 base rule
11520
- */
11521
- generateAntigravityJai1Rule() {
11522
- return `---
11523
- trigger: always_on
11524
- ---
11525
-
11526
- @AGENTS.md
11527
-
11528
- ${JAI1_BASE_RULE}`;
11529
12181
  }
11530
12182
  /**
11531
12183
  * Generate Claude Code format (.md files with paths metadata)
11532
12184
  */
11533
12185
  generateClaudeFiles(bundle, format) {
11534
12186
  const files = [];
11535
- files.push({
11536
- path: `${format.rulesPath}/00-jai1${format.fileExtension}`,
11537
- content: this.generateClaudeJai1Rule(),
11538
- description: "Jai1 Framework base rule"
11539
- });
11540
12187
  for (const [filename, content] of Object.entries(bundle.files)) {
11541
12188
  const converted = this.convertToClaude(content, filename);
11542
12189
  const newFilename = filename.replace(/\.mdc$/, ".md");
@@ -11570,17 +12217,6 @@ paths: ${paths}`;
11570
12217
 
11571
12218
  ${body.trim()}
11572
12219
  `;
11573
- }
11574
- /**
11575
- * Generate Claude jai1 base rule
11576
- */
11577
- generateClaudeJai1Rule() {
11578
- return `---
11579
- description: Jai1 Framework integration and skill loading
11580
- paths: **/*
11581
- ---
11582
-
11583
- ${JAI1_BASE_RULE}`;
11584
12220
  }
11585
12221
  /**
11586
12222
  * Generate AGENTS.md file (single merged file)
@@ -11590,10 +12226,8 @@ ${JAI1_BASE_RULE}`;
11590
12226
  sections.push("# AGENTS.md\n");
11591
12227
  sections.push(`<!-- Generated by jai1 rules - Preset: ${bundle.preset.slug} v${bundle.preset.version} -->
11592
12228
  `);
11593
- sections.push("## Jai1 Framework Integration\n");
11594
- sections.push(JAI1_BASE_RULE.trim());
11595
- sections.push("\n---\n");
11596
12229
  const fileOrder = [
12230
+ "00-jai1.mdc",
11597
12231
  "01-project.mdc",
11598
12232
  "02-standards.mdc",
11599
12233
  "03-frontend.mdc",
@@ -11713,8 +12347,8 @@ Follow all instructions and patterns defined in AGENTS.md above.
11713
12347
  };
11714
12348
 
11715
12349
  // src/services/backup.service.ts
11716
- import { promises as fs17 } from "fs";
11717
- import { join as join9, dirname } from "path";
12350
+ import { promises as fs21 } from "fs";
12351
+ import { join as join11, dirname } from "path";
11718
12352
  var BackupService = class {
11719
12353
  backupDir = ".jai1/backups";
11720
12354
  /**
@@ -11722,7 +12356,7 @@ var BackupService = class {
11722
12356
  */
11723
12357
  async createBackup(ides, presetSlug) {
11724
12358
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
11725
- const backupPath = join9(this.backupDir, timestamp);
12359
+ const backupPath = join11(this.backupDir, timestamp);
11726
12360
  const backedUpFiles = [];
11727
12361
  let hasContent = false;
11728
12362
  for (const ideId of ides) {
@@ -11731,7 +12365,7 @@ var BackupService = class {
11731
12365
  console.warn(`Unknown IDE format: ${ideId}, skipping backup`);
11732
12366
  continue;
11733
12367
  }
11734
- const rulesPath = format.rulesPath === "." ? process.cwd() : join9(process.cwd(), format.rulesPath);
12368
+ const rulesPath = format.rulesPath === "." ? process.cwd() : join11(process.cwd(), format.rulesPath);
11735
12369
  try {
11736
12370
  const exists = await this.pathExists(rulesPath);
11737
12371
  if (!exists) {
@@ -11744,19 +12378,19 @@ var BackupService = class {
11744
12378
  await this.backupSingleFile("GEMINI.md", backupPath, ideId, backedUpFiles);
11745
12379
  hasContent = true;
11746
12380
  } else {
11747
- const stats = await fs17.stat(rulesPath);
12381
+ const stats = await fs21.stat(rulesPath);
11748
12382
  if (stats.isDirectory()) {
11749
- const files = await fs17.readdir(rulesPath);
12383
+ const files = await fs21.readdir(rulesPath);
11750
12384
  for (const file of files) {
11751
12385
  if (file.endsWith(format.fileExtension)) {
11752
- const originalPath = join9(rulesPath, file);
11753
- const relativePath = join9(format.rulesPath, file);
11754
- const destPath = join9(backupPath, ideId, file);
11755
- await fs17.mkdir(dirname(destPath), { recursive: true });
11756
- await fs17.copyFile(originalPath, destPath);
12386
+ const originalPath = join11(rulesPath, file);
12387
+ const relativePath = join11(format.rulesPath, file);
12388
+ const destPath = join11(backupPath, ideId, file);
12389
+ await fs21.mkdir(dirname(destPath), { recursive: true });
12390
+ await fs21.copyFile(originalPath, destPath);
11757
12391
  backedUpFiles.push({
11758
12392
  originalPath: relativePath,
11759
- backupPath: join9(ideId, file),
12393
+ backupPath: join11(ideId, file),
11760
12394
  ide: ideId
11761
12395
  });
11762
12396
  hasContent = true;
@@ -11777,9 +12411,9 @@ var BackupService = class {
11777
12411
  ides,
11778
12412
  files: backedUpFiles
11779
12413
  };
11780
- await fs17.mkdir(backupPath, { recursive: true });
11781
- await fs17.writeFile(
11782
- join9(backupPath, "metadata.json"),
12414
+ await fs21.mkdir(backupPath, { recursive: true });
12415
+ await fs21.writeFile(
12416
+ join11(backupPath, "metadata.json"),
11783
12417
  JSON.stringify(metadata, null, 2),
11784
12418
  "utf-8"
11785
12419
  );
@@ -11789,18 +12423,18 @@ var BackupService = class {
11789
12423
  * Backup a single file (for AGENTS.md, GEMINI.md)
11790
12424
  */
11791
12425
  async backupSingleFile(filename, backupPath, ideId, backedUpFiles) {
11792
- const originalPath = join9(process.cwd(), filename);
12426
+ const originalPath = join11(process.cwd(), filename);
11793
12427
  try {
11794
12428
  const exists = await this.pathExists(originalPath);
11795
12429
  if (!exists) {
11796
12430
  return;
11797
12431
  }
11798
- const destPath = join9(backupPath, ideId, filename);
11799
- await fs17.mkdir(dirname(destPath), { recursive: true });
11800
- await fs17.copyFile(originalPath, destPath);
12432
+ const destPath = join11(backupPath, ideId, filename);
12433
+ await fs21.mkdir(dirname(destPath), { recursive: true });
12434
+ await fs21.copyFile(originalPath, destPath);
11801
12435
  backedUpFiles.push({
11802
12436
  originalPath: filename,
11803
- backupPath: join9(ideId, filename),
12437
+ backupPath: join11(ideId, filename),
11804
12438
  ide: ideId
11805
12439
  });
11806
12440
  } catch (error) {
@@ -11810,16 +12444,16 @@ var BackupService = class {
11810
12444
  * Restore files from a backup
11811
12445
  */
11812
12446
  async restoreBackup(backupPath) {
11813
- const metadataPath = join9(backupPath, "metadata.json");
11814
- const metadataContent = await fs17.readFile(metadataPath, "utf-8");
12447
+ const metadataPath = join11(backupPath, "metadata.json");
12448
+ const metadataContent = await fs21.readFile(metadataPath, "utf-8");
11815
12449
  const metadata = JSON.parse(metadataContent);
11816
12450
  console.log(`
11817
12451
  Restoring backup from ${metadata.timestamp}...`);
11818
12452
  for (const file of metadata.files) {
11819
- const sourcePath = join9(backupPath, file.backupPath);
11820
- const destPath = join9(process.cwd(), file.originalPath);
11821
- await fs17.mkdir(dirname(destPath), { recursive: true });
11822
- await fs17.copyFile(sourcePath, destPath);
12453
+ const sourcePath = join11(backupPath, file.backupPath);
12454
+ const destPath = join11(process.cwd(), file.originalPath);
12455
+ await fs21.mkdir(dirname(destPath), { recursive: true });
12456
+ await fs21.copyFile(sourcePath, destPath);
11823
12457
  console.log(`\u2713 Restored ${file.originalPath}`);
11824
12458
  }
11825
12459
  console.log("\n\u2705 Backup restored successfully!");
@@ -11829,18 +12463,18 @@ Restoring backup from ${metadata.timestamp}...`);
11829
12463
  */
11830
12464
  async listBackups() {
11831
12465
  try {
11832
- const backupDirPath = join9(process.cwd(), this.backupDir);
12466
+ const backupDirPath = join11(process.cwd(), this.backupDir);
11833
12467
  const exists = await this.pathExists(backupDirPath);
11834
12468
  if (!exists) {
11835
12469
  return [];
11836
12470
  }
11837
- const entries = await fs17.readdir(backupDirPath, { withFileTypes: true });
12471
+ const entries = await fs21.readdir(backupDirPath, { withFileTypes: true });
11838
12472
  const backups = [];
11839
12473
  for (const entry of entries) {
11840
12474
  if (entry.isDirectory()) {
11841
- const metadataPath = join9(backupDirPath, entry.name, "metadata.json");
12475
+ const metadataPath = join11(backupDirPath, entry.name, "metadata.json");
11842
12476
  try {
11843
- const metadataContent = await fs17.readFile(metadataPath, "utf-8");
12477
+ const metadataContent = await fs21.readFile(metadataPath, "utf-8");
11844
12478
  const metadata = JSON.parse(metadataContent);
11845
12479
  backups.push(metadata);
11846
12480
  } catch {
@@ -11857,7 +12491,7 @@ Restoring backup from ${metadata.timestamp}...`);
11857
12491
  * Delete a specific backup
11858
12492
  */
11859
12493
  async deleteBackup(timestamp) {
11860
- const backupPath = join9(process.cwd(), this.backupDir, timestamp);
12494
+ const backupPath = join11(process.cwd(), this.backupDir, timestamp);
11861
12495
  await this.deleteDirectory(backupPath);
11862
12496
  }
11863
12497
  /**
@@ -11887,9 +12521,9 @@ Restoring backup from ${metadata.timestamp}...`);
11887
12521
  /**
11888
12522
  * Check if a path exists
11889
12523
  */
11890
- async pathExists(path10) {
12524
+ async pathExists(path13) {
11891
12525
  try {
11892
- await fs17.access(path10);
12526
+ await fs21.access(path13);
11893
12527
  return true;
11894
12528
  } catch {
11895
12529
  return false;
@@ -11898,22 +12532,22 @@ Restoring backup from ${metadata.timestamp}...`);
11898
12532
  /**
11899
12533
  * Recursively delete a directory
11900
12534
  */
11901
- async deleteDirectory(path10) {
12535
+ async deleteDirectory(path13) {
11902
12536
  try {
11903
- const exists = await this.pathExists(path10);
12537
+ const exists = await this.pathExists(path13);
11904
12538
  if (!exists) {
11905
12539
  return;
11906
12540
  }
11907
- const entries = await fs17.readdir(path10, { withFileTypes: true });
12541
+ const entries = await fs21.readdir(path13, { withFileTypes: true });
11908
12542
  for (const entry of entries) {
11909
- const fullPath = join9(path10, entry.name);
12543
+ const fullPath = join11(path13, entry.name);
11910
12544
  if (entry.isDirectory()) {
11911
12545
  await this.deleteDirectory(fullPath);
11912
12546
  } else {
11913
- await fs17.unlink(fullPath);
12547
+ await fs21.unlink(fullPath);
11914
12548
  }
11915
12549
  }
11916
- await fs17.rmdir(path10);
12550
+ await fs21.rmdir(path13);
11917
12551
  } catch (error) {
11918
12552
  }
11919
12553
  }
@@ -11921,20 +12555,20 @@ Restoring backup from ${metadata.timestamp}...`);
11921
12555
  * Get backup directory path
11922
12556
  */
11923
12557
  getBackupDir() {
11924
- return join9(process.cwd(), this.backupDir);
12558
+ return join11(process.cwd(), this.backupDir);
11925
12559
  }
11926
12560
  /**
11927
12561
  * Ensure backup directory exists
11928
12562
  */
11929
12563
  async ensureBackupDir() {
11930
- const backupDirPath = join9(process.cwd(), this.backupDir);
11931
- await fs17.mkdir(backupDirPath, { recursive: true });
12564
+ const backupDirPath = join11(process.cwd(), this.backupDir);
12565
+ await fs21.mkdir(backupDirPath, { recursive: true });
11932
12566
  }
11933
12567
  };
11934
12568
 
11935
12569
  // src/commands/rules/apply.ts
11936
12570
  function createRulesApplyCommand() {
11937
- return new Command44("apply").description("Apply rule preset to project with multi-IDE support").argument("[preset]", "Preset slug to apply (optional)").option("--ides <ides>", "Comma-separated list of IDE formats (cursor,windsurf,antigravity,claude,agentsmd,gemini)").option("--skip-backup", "Skip backup creation").option("-y, --yes", "Skip all confirmations (auto mode)").action(async (presetSlug, options) => {
12571
+ return new Command45("apply").description("Apply rule preset to project with multi-IDE support").argument("[preset]", "Preset slug to apply (optional)").option("--ides <ides>", "Comma-separated list of IDE formats (cursor,windsurf,antigravity,claude,agentsmd,gemini)").option("--skip-backup", "Skip backup creation").option("-y, --yes", "Skip all confirmations (auto mode)").action(async (presetSlug, options) => {
11938
12572
  const configService = new ConfigService();
11939
12573
  const config = await configService.load();
11940
12574
  if (!config) {
@@ -11972,7 +12606,8 @@ function createRulesApplyCommand() {
11972
12606
  name: `${p.name} - ${p.description}`,
11973
12607
  value: p.slug,
11974
12608
  description: `v${p.version} | ${p.tags.join(", ")}`
11975
- }))
12609
+ })),
12610
+ theme: selectTheme
11976
12611
  });
11977
12612
  }
11978
12613
  console.log(`
@@ -12042,7 +12677,8 @@ function createRulesApplyCommand() {
12042
12677
  selectedIdes = await checkbox5({
12043
12678
  message: "Select IDE formats to generate (pre-selected are recommended):",
12044
12679
  choices,
12045
- required: true
12680
+ required: true,
12681
+ theme: checkboxTheme
12046
12682
  });
12047
12683
  }
12048
12684
  }
@@ -12084,21 +12720,21 @@ function createRulesApplyCommand() {
12084
12720
  }
12085
12721
  }
12086
12722
  console.log("\n\u{1F4DD} Applying preset...\n");
12087
- const rulePresetDir = join10(process.cwd(), ".jai1", "rule-preset");
12723
+ const rulePresetDir = join12(process.cwd(), ".jai1", "rule-preset");
12088
12724
  try {
12089
- await fs18.rm(rulePresetDir, { recursive: true, force: true });
12725
+ await fs22.rm(rulePresetDir, { recursive: true, force: true });
12090
12726
  } catch {
12091
12727
  }
12092
- await fs18.mkdir(rulePresetDir, { recursive: true });
12093
- await fs18.writeFile(
12094
- join10(rulePresetDir, "preset.json"),
12728
+ await fs22.mkdir(rulePresetDir, { recursive: true });
12729
+ await fs22.writeFile(
12730
+ join12(rulePresetDir, "preset.json"),
12095
12731
  JSON.stringify(bundle.preset, null, 2),
12096
12732
  "utf-8"
12097
12733
  );
12098
12734
  for (const [filename, content] of Object.entries(bundle.files)) {
12099
- const filePath = join10(rulePresetDir, filename);
12100
- await fs18.mkdir(join10(filePath, ".."), { recursive: true });
12101
- await fs18.writeFile(filePath, content, "utf-8");
12735
+ const filePath = join12(rulePresetDir, filename);
12736
+ await fs22.mkdir(join12(filePath, ".."), { recursive: true });
12737
+ await fs22.writeFile(filePath, content, "utf-8");
12102
12738
  }
12103
12739
  console.log(`\u2713 Saved preset to .jai1/rule-preset/`);
12104
12740
  const allGeneratedFiles = [];
@@ -12106,9 +12742,9 @@ function createRulesApplyCommand() {
12106
12742
  try {
12107
12743
  const files = generatorService.generateForIde(bundle, ideId);
12108
12744
  for (const file of files) {
12109
- const fullPath = join10(process.cwd(), file.path);
12110
- await fs18.mkdir(join10(fullPath, ".."), { recursive: true });
12111
- await fs18.writeFile(fullPath, file.content, "utf-8");
12745
+ const fullPath = join12(process.cwd(), file.path);
12746
+ await fs22.mkdir(join12(fullPath, ".."), { recursive: true });
12747
+ await fs22.writeFile(fullPath, file.content, "utf-8");
12112
12748
  console.log(`\u2713 [${ideId}] ${file.path}`);
12113
12749
  allGeneratedFiles.push({
12114
12750
  ide: ideId,
@@ -12120,7 +12756,8 @@ function createRulesApplyCommand() {
12120
12756
  console.error(`\u2717 Failed to generate ${ideId} files:`, error);
12121
12757
  }
12122
12758
  }
12123
- const projectConfig = {
12759
+ const projectConfigService = new ProjectConfigService();
12760
+ const rulesConfig = {
12124
12761
  preset: bundle.preset.slug,
12125
12762
  version: bundle.preset.version,
12126
12763
  appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -12135,24 +12772,18 @@ function createRulesApplyCommand() {
12135
12772
  ] : []
12136
12773
  };
12137
12774
  try {
12138
- const existingConfigPath = join10(process.cwd(), "jai1-rules.json");
12139
- const existingConfigContent = await fs18.readFile(existingConfigPath, "utf-8");
12140
- const existingConfig = JSON.parse(existingConfigContent);
12141
- if (existingConfig.backups && existingConfig.backups.length > 0) {
12142
- projectConfig.backups = [
12143
- ...projectConfig.backups,
12144
- ...existingConfig.backups.slice(0, 5)
12775
+ const existingRules = await projectConfigService.loadRules();
12776
+ if (existingRules?.backups && existingRules.backups.length > 0) {
12777
+ rulesConfig.backups = [
12778
+ ...rulesConfig.backups,
12779
+ ...existingRules.backups.slice(0, 5)
12145
12780
  // Keep last 5 backups
12146
12781
  ];
12147
12782
  }
12148
12783
  } catch {
12149
12784
  }
12150
- await fs18.writeFile(
12151
- join10(process.cwd(), "jai1-rules.json"),
12152
- JSON.stringify(projectConfig, null, 2),
12153
- "utf-8"
12154
- );
12155
- console.log("\n\u2713 Updated jai1-rules.json");
12785
+ await projectConfigService.saveRules(rulesConfig);
12786
+ console.log("\n\u2713 Updated .jai1/project.json");
12156
12787
  console.log("\n\u2705 Preset applied successfully!\n");
12157
12788
  console.log("\u{1F4CA} Summary:");
12158
12789
  console.log(` Generated ${allGeneratedFiles.length} files across ${resolvedIdes.length} IDE(s)`);
@@ -12179,11 +12810,11 @@ function createRulesApplyCommand() {
12179
12810
  }
12180
12811
 
12181
12812
  // src/commands/rules/restore.ts
12182
- import { Command as Command45 } from "commander";
12183
- import { join as join11 } from "path";
12813
+ import { Command as Command46 } from "commander";
12814
+ import { join as join13 } from "path";
12184
12815
  import { select as select6, confirm as confirm9 } from "@inquirer/prompts";
12185
12816
  function createRulesRestoreCommand() {
12186
- return new Command45("restore").description("Restore rules from a backup").option("--latest", "Restore the most recent backup").option("-y, --yes", "Skip confirmation").action(async (options) => {
12817
+ return new Command46("restore").description("Restore rules from a backup").option("--latest", "Restore the most recent backup").option("-y, --yes", "Skip confirmation").action(async (options) => {
12187
12818
  const backupService = new BackupService();
12188
12819
  const backups = await backupService.listBackups();
12189
12820
  if (backups.length === 0) {
@@ -12202,7 +12833,8 @@ function createRulesRestoreCommand() {
12202
12833
  name: formatBackupInfo(backup),
12203
12834
  value: backup.timestamp,
12204
12835
  description: `${backup.files.length} files | IDEs: ${backup.ides.join(", ")}`
12205
- }))
12836
+ })),
12837
+ theme: selectTheme
12206
12838
  });
12207
12839
  selectedBackup = backups.find((b) => b.timestamp === backupTimestamp);
12208
12840
  if (!selectedBackup) {
@@ -12226,7 +12858,7 @@ function createRulesRestoreCommand() {
12226
12858
  }
12227
12859
  console.log("\n\u{1F504} Restoring backup...\n");
12228
12860
  try {
12229
- const backupPath = join11(backupService.getBackupDir(), selectedBackup.timestamp);
12861
+ const backupPath = join13(backupService.getBackupDir(), selectedBackup.timestamp);
12230
12862
  await backupService.restoreBackup(backupPath);
12231
12863
  console.log("\n\u2705 Backup restored successfully!\n");
12232
12864
  console.log("\u{1F4A1} Tip: Your IDE may need to be restarted to pick up the changes.");
@@ -12251,24 +12883,42 @@ function formatTimestamp(timestamp) {
12251
12883
  }
12252
12884
 
12253
12885
  // src/commands/rules/sync.ts
12254
- import { Command as Command46 } from "commander";
12255
- import { promises as fs19 } from "fs";
12256
- import { join as join12 } from "path";
12257
- import { confirm as confirm10 } from "@inquirer/prompts";
12886
+ import { Command as Command47 } from "commander";
12887
+ import { promises as fs23 } from "fs";
12888
+ import { join as join14 } from "path";
12889
+ import { checkbox as checkbox6, confirm as confirm10, Separator } from "@inquirer/prompts";
12258
12890
  function createRulesSyncCommand() {
12259
- return new Command46("sync").description("Regenerate rule outputs for all configured IDEs").option("--ides <ides>", "Comma-separated list of IDEs to sync (default: all configured)").option("--detect", "Auto-detect active IDEs instead of using config").option("-y, --yes", "Skip confirmations").action(async (options) => {
12260
- const configPath = join12(process.cwd(), "jai1-rules.json");
12261
- let projectConfig;
12891
+ return new Command47("sync").description("Regenerate rule outputs for all configured IDEs").option("--ides <ides>", "Comma-separated list of IDEs to sync (default: all configured)").option("--detect", "Auto-detect active IDEs instead of using config").option("-y, --yes", "Skip confirmations").action(async (options) => {
12892
+ const rulePresetDir = join14(process.cwd(), ".jai1", "rule-preset");
12893
+ const presetJsonPath = join14(rulePresetDir, "preset.json");
12894
+ let presetExists = false;
12895
+ let presetData = null;
12262
12896
  try {
12263
- const configContent = await fs19.readFile(configPath, "utf-8");
12264
- projectConfig = JSON.parse(configContent);
12897
+ const presetContent = await fs23.readFile(presetJsonPath, "utf-8");
12898
+ presetData = JSON.parse(presetContent);
12899
+ presetExists = true;
12265
12900
  } catch {
12901
+ }
12902
+ if (!presetExists) {
12266
12903
  throw new ValidationError(
12267
- 'No jai1-rules.json found. Run "jai1 rules apply" first.'
12904
+ 'No rule preset found in .jai1/rule-preset/\nRun "jai1 rules apply <preset>" or create a project with "jai1 kit create <kit>" first.'
12268
12905
  );
12269
12906
  }
12907
+ const projectConfigService = new ProjectConfigService();
12908
+ let rulesConfig = await projectConfigService.loadRules();
12909
+ if (!rulesConfig) {
12910
+ console.log("\u26A0\uFE0F No .jai1/project.json found, creating from preset...\n");
12911
+ rulesConfig = {
12912
+ preset: presetData.slug,
12913
+ version: presetData.version,
12914
+ appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
12915
+ ides: [],
12916
+ customContext: "09-custom.md",
12917
+ backups: []
12918
+ };
12919
+ }
12270
12920
  console.log("\u{1F504} Syncing rules...\n");
12271
- console.log(`Preset: ${projectConfig.preset} v${projectConfig.version}`);
12921
+ console.log(`Preset: ${rulesConfig.preset} v${rulesConfig.version}`);
12272
12922
  let idesToSync = [];
12273
12923
  if (options.detect) {
12274
12924
  console.log("\n\u{1F50D} Auto-detecting active IDEs...");
@@ -12299,18 +12949,52 @@ Detected ${detected.length} active IDE(s):
12299
12949
  idesToSync = detected.map((d) => d.id);
12300
12950
  } else if (options.ides) {
12301
12951
  const requested = options.ides.split(",").map((ide) => ide.trim());
12302
- const configured = projectConfig.ides || [];
12952
+ const configured = rulesConfig.ides || [];
12303
12953
  idesToSync = requested;
12304
12954
  const notConfigured = requested.filter((ide) => !configured.includes(ide));
12305
12955
  if (notConfigured.length > 0) {
12306
12956
  console.log(`
12307
12957
  \u26A0\uFE0F IDEs not in config: ${notConfigured.join(", ")}`);
12308
- console.log(" They will still be synced, but may not be in jai1-rules.json");
12958
+ console.log(" They will still be synced, but may not be in .jai1/project.json");
12959
+ }
12960
+ } else if (!options.yes) {
12961
+ const currentIdes = rulesConfig.ides || [];
12962
+ console.log(`
12963
+ Current IDE(s): ${currentIdes.join(", ") || "none"}`);
12964
+ const detectionService = new IdeDetectionService();
12965
+ const detected = await detectionService.detectActiveIdes();
12966
+ const suggestions = await detectionService.suggestIdes();
12967
+ if (detected.length > 0) {
12968
+ const detectedCount = detected.reduce((sum, d) => sum + d.ruleCount + d.workflowCount, 0);
12969
+ console.log(` (${detectedCount} files detected)`);
12970
+ }
12971
+ if (suggestions.length > 0) {
12972
+ console.log("\n\u{1F4A1} Smart suggestions based on your project:\n");
12973
+ suggestions.slice(0, 3).forEach((s) => {
12974
+ const priority = s.priority === "high" ? "\u2B50" : s.priority === "medium" ? "\u{1F538}" : "\u25AB\uFE0F";
12975
+ console.log(`${priority} ${s.name} - ${s.reason}`);
12976
+ });
12977
+ console.log("");
12978
+ }
12979
+ const choices = buildIdeChoices(currentIdes, detected, suggestions);
12980
+ idesToSync = await checkbox6({
12981
+ message: "Select IDE formats to sync:",
12982
+ choices,
12983
+ required: false,
12984
+ theme: checkboxTheme
12985
+ });
12986
+ if (idesToSync.includes("__quit__")) {
12987
+ console.log("Cancelled.");
12988
+ return;
12989
+ }
12990
+ if (idesToSync.length === 0) {
12991
+ console.log("Cancelled.");
12992
+ return;
12309
12993
  }
12310
12994
  } else {
12311
- idesToSync = projectConfig.ides || [];
12995
+ idesToSync = rulesConfig.ides || [];
12312
12996
  if (idesToSync.length === 0) {
12313
- console.log("\n\u26A0\uFE0F No IDEs configured in jai1-rules.json");
12997
+ console.log("\n\u26A0\uFE0F No IDEs configured in .jai1/project.json");
12314
12998
  console.log(" Detecting from existing files...\n");
12315
12999
  const detectionService = new IdeDetectionService();
12316
13000
  idesToSync = await detectionService.detectExistingIdes();
@@ -12331,7 +13015,7 @@ Detected ${detected.length} active IDE(s):
12331
13015
  if (!config) {
12332
13016
  throw new ValidationError('Not initialized. Run "jai1 auth" first.');
12333
13017
  }
12334
- const presetResponse = await fetch(`${config.apiUrl}/api/rules/presets/${projectConfig.preset}`, {
13018
+ const presetResponse = await fetch(`${config.apiUrl}/api/rules/presets/${rulesConfig.preset}`, {
12335
13019
  headers: {
12336
13020
  "JAI1-Access-Key": config.accessKey
12337
13021
  }
@@ -12340,21 +13024,13 @@ Detected ${detected.length} active IDE(s):
12340
13024
  throw new Error(`Failed to fetch preset: ${presetResponse.statusText}`);
12341
13025
  }
12342
13026
  const bundle = await presetResponse.json();
12343
- const rulePresetDir = join12(process.cwd(), ".jai1", "rule-preset");
12344
- const presetExists = await checkPathExists(rulePresetDir);
12345
- if (presetExists) {
12346
- const files = await fs19.readdir(rulePresetDir);
12347
- for (const file of files) {
12348
- if (file.endsWith(".mdc")) {
12349
- const filePath = join12(rulePresetDir, file);
12350
- const content = await fs19.readFile(filePath, "utf-8");
12351
- bundle.files[file] = content;
12352
- }
13027
+ const files = await fs23.readdir(rulePresetDir);
13028
+ for (const file of files) {
13029
+ if (file.endsWith(".mdc") || file.endsWith(".md")) {
13030
+ const filePath = join14(rulePresetDir, file);
13031
+ const content = await fs23.readFile(filePath, "utf-8");
13032
+ bundle.files[file] = content;
12353
13033
  }
12354
- } else {
12355
- console.log("\n\u26A0\uFE0F No rule preset found in .jai1/rule-preset/");
12356
- console.log(' Run "jai1 rules apply" first to apply a preset.\n');
12357
- return;
12358
13034
  }
12359
13035
  const generatorService = new RulesGeneratorService();
12360
13036
  for (const ideId of idesToSync) {
@@ -12364,25 +13040,21 @@ Detected ${detected.length} active IDE(s):
12364
13040
  console.log(`\u26A0\uFE0F Unknown IDE format: ${ideId}, skipping`);
12365
13041
  continue;
12366
13042
  }
12367
- const files = generatorService.generateForIde(bundle, ideId);
12368
- for (const file of files) {
12369
- const fullPath = join12(process.cwd(), file.path);
12370
- await fs19.mkdir(join12(fullPath, ".."), { recursive: true });
12371
- await fs19.writeFile(fullPath, file.content, "utf-8");
13043
+ const files2 = generatorService.generateForIde(bundle, ideId);
13044
+ for (const file of files2) {
13045
+ const fullPath = join14(process.cwd(), file.path);
13046
+ await fs23.mkdir(join14(fullPath, ".."), { recursive: true });
13047
+ await fs23.writeFile(fullPath, file.content, "utf-8");
12372
13048
  }
12373
- console.log(`\u2713 ${format.name} - ${files.length} files regenerated`);
13049
+ console.log(`\u2713 ${format.name} - ${files2.length} files regenerated`);
12374
13050
  } catch (error) {
12375
13051
  console.error(`\u2717 Failed to sync ${ideId}:`, error);
12376
13052
  }
12377
13053
  }
12378
- if (JSON.stringify(projectConfig.ides) !== JSON.stringify(idesToSync)) {
12379
- projectConfig.ides = idesToSync;
12380
- await fs19.writeFile(
12381
- join12(process.cwd(), "jai1-rules.json"),
12382
- JSON.stringify(projectConfig, null, 2),
12383
- "utf-8"
12384
- );
12385
- console.log("\n\u2713 Updated jai1-rules.json with synced IDEs");
13054
+ if (JSON.stringify(rulesConfig.ides) !== JSON.stringify(idesToSync)) {
13055
+ rulesConfig.ides = idesToSync;
13056
+ await projectConfigService.saveRules(rulesConfig);
13057
+ console.log("\n\u2713 Updated .jai1/project.json with synced IDEs");
12386
13058
  }
12387
13059
  console.log("\n\u2705 Rules synced successfully!\n");
12388
13060
  console.log("\u{1F4A1} Next steps:");
@@ -12391,44 +13063,74 @@ Detected ${detected.length} active IDE(s):
12391
13063
  console.log(" \u2022 Edit source files in .jai1/rule-preset/ and sync again\n");
12392
13064
  });
12393
13065
  }
12394
- async function checkPathExists(absolutePath) {
12395
- try {
12396
- await fs19.access(absolutePath);
12397
- return true;
12398
- } catch {
12399
- return false;
12400
- }
13066
+ function buildIdeChoices(currentIdes, detected, suggestions) {
13067
+ const choices = [
13068
+ {
13069
+ name: "Cursor (.cursor/rules/)",
13070
+ value: "cursor",
13071
+ checked: currentIdes.includes("cursor") || detected.some((d) => d.id === "cursor")
13072
+ },
13073
+ {
13074
+ name: "Windsurf (.windsurf/rules/)",
13075
+ value: "windsurf",
13076
+ checked: currentIdes.includes("windsurf") || detected.some((d) => d.id === "windsurf")
13077
+ },
13078
+ {
13079
+ name: "Antigravity (.agent/rules/)",
13080
+ value: "antigravity",
13081
+ checked: currentIdes.includes("antigravity") || detected.some((d) => d.id === "antigravity")
13082
+ },
13083
+ {
13084
+ name: "Claude Code (.claude/rules/)",
13085
+ value: "claude",
13086
+ checked: currentIdes.includes("claude") || detected.some((d) => d.id === "claude")
13087
+ },
13088
+ {
13089
+ name: "AGENTS.md (single file)",
13090
+ value: "agentsmd",
13091
+ checked: currentIdes.includes("agentsmd") || detected.some((d) => d.id === "agentsmd")
13092
+ },
13093
+ {
13094
+ name: "Gemini CLI (GEMINI.md)",
13095
+ value: "gemini",
13096
+ checked: currentIdes.includes("gemini") || detected.some((d) => d.id === "gemini")
13097
+ },
13098
+ new Separator(),
13099
+ {
13100
+ name: "\u2716 Quit",
13101
+ value: "__quit__",
13102
+ checked: false
13103
+ }
13104
+ ];
13105
+ return choices;
12401
13106
  }
12402
13107
 
12403
13108
  // src/commands/rules/info.ts
12404
- import { Command as Command47 } from "commander";
12405
- import { promises as fs20 } from "fs";
12406
- import { join as join13 } from "path";
13109
+ import { Command as Command48 } from "commander";
13110
+ import { promises as fs24 } from "fs";
13111
+ import { join as join15 } from "path";
12407
13112
  function createRulesInfoCommand() {
12408
- return new Command47("info").description("Show current preset information").option("--json", "Output as JSON").action(async (options) => {
12409
- const configPath = join13(process.cwd(), "jai1-rules.json");
12410
- let projectConfig;
12411
- try {
12412
- const configContent = await fs20.readFile(configPath, "utf-8");
12413
- projectConfig = JSON.parse(configContent);
12414
- } catch {
13113
+ return new Command48("info").description("Show current preset information").option("--json", "Output as JSON").action(async (options) => {
13114
+ const projectConfigService = new ProjectConfigService();
13115
+ const rulesConfig = await projectConfigService.loadRules();
13116
+ if (!rulesConfig) {
12415
13117
  throw new ValidationError(
12416
- 'No jai1-rules.json found. Run "jai1 rules init" first.'
13118
+ 'No .jai1/project.json found. Run "jai1 rules apply" first.'
12417
13119
  );
12418
13120
  }
12419
13121
  if (options.json) {
12420
- console.log(JSON.stringify(projectConfig, null, 2));
13122
+ console.log(JSON.stringify(rulesConfig, null, 2));
12421
13123
  return;
12422
13124
  }
12423
13125
  console.log("\u{1F4CB} Current Preset Information\n");
12424
- const rulePresetDir = join13(process.cwd(), ".jai1", "rule-preset");
12425
- const presetJsonPath = join13(rulePresetDir, "preset.json");
13126
+ const rulePresetDir = join15(process.cwd(), ".jai1", "rule-preset");
13127
+ const presetJsonPath = join15(rulePresetDir, "preset.json");
12426
13128
  let presetMetadata = null;
12427
13129
  let presetFiles = [];
12428
13130
  try {
12429
- const presetContent = await fs20.readFile(presetJsonPath, "utf-8");
13131
+ const presetContent = await fs24.readFile(presetJsonPath, "utf-8");
12430
13132
  presetMetadata = JSON.parse(presetContent);
12431
- const files = await fs20.readdir(rulePresetDir);
13133
+ const files = await fs24.readdir(rulePresetDir);
12432
13134
  presetFiles = files.filter((f) => f.endsWith(".mdc"));
12433
13135
  } catch {
12434
13136
  }
@@ -12440,10 +13142,10 @@ function createRulesInfoCommand() {
12440
13142
  console.log(`Tags: ${presetMetadata.tags.join(", ")}`);
12441
13143
  }
12442
13144
  } else {
12443
- console.log(`Preset: ${projectConfig.preset}`);
12444
- console.log(`Version: ${projectConfig.version}`);
13145
+ console.log(`Preset: ${rulesConfig.preset}`);
13146
+ console.log(`Version: ${rulesConfig.version}`);
12445
13147
  }
12446
- console.log(`Applied at: ${new Date(projectConfig.appliedAt).toLocaleString()}`);
13148
+ console.log(`Applied at: ${new Date(rulesConfig.appliedAt).toLocaleString()}`);
12447
13149
  console.log(`
12448
13150
  Source of Truth:`);
12449
13151
  if (presetFiles.length > 0) {
@@ -12458,10 +13160,10 @@ Source of Truth:`);
12458
13160
  console.log(` \u26A0\uFE0F .jai1/rule-preset/ not found`);
12459
13161
  console.log(` Run "jai1 rules apply" to create it`);
12460
13162
  }
12461
- if (projectConfig.ides && projectConfig.ides.length > 0) {
13163
+ if (rulesConfig.ides && rulesConfig.ides.length > 0) {
12462
13164
  console.log(`
12463
- Configured IDEs (${projectConfig.ides.length}):`);
12464
- for (const ideId of projectConfig.ides) {
13165
+ Configured IDEs (${rulesConfig.ides.length}):`);
13166
+ for (const ideId of rulesConfig.ides) {
12465
13167
  const format = IDE_FORMATS[ideId];
12466
13168
  if (format) {
12467
13169
  const exists = await checkIdeFilesExist(ideId, format);
@@ -12470,15 +13172,15 @@ Configured IDEs (${projectConfig.ides.length}):`);
12470
13172
  }
12471
13173
  }
12472
13174
  }
12473
- if (projectConfig.backups && projectConfig.backups.length > 0) {
13175
+ if (rulesConfig.backups && rulesConfig.backups.length > 0) {
12474
13176
  console.log(`
12475
- Available Backups (${projectConfig.backups.length}):`);
12476
- for (const backup of projectConfig.backups.slice(0, 3)) {
13177
+ Available Backups (${rulesConfig.backups.length}):`);
13178
+ for (const backup of rulesConfig.backups.slice(0, 3)) {
12477
13179
  const timestamp = new Date(backup.timestamp).toLocaleString();
12478
13180
  console.log(` \u2022 ${timestamp} - IDEs: ${backup.ides.join(", ")}`);
12479
13181
  }
12480
- if (projectConfig.backups.length > 3) {
12481
- console.log(` ... and ${projectConfig.backups.length - 3} more`);
13182
+ if (rulesConfig.backups.length > 3) {
13183
+ console.log(` ... and ${rulesConfig.backups.length - 3} more`);
12482
13184
  }
12483
13185
  }
12484
13186
  console.log("\n\u2139\uFE0F Commands:");
@@ -12487,9 +13189,9 @@ Available Backups (${projectConfig.backups.length}):`);
12487
13189
  console.log(' \u2022 "jai1 rules apply" - Apply a different preset (replaces current)');
12488
13190
  });
12489
13191
  }
12490
- async function checkPathExists2(path10) {
13192
+ async function checkPathExists(path13) {
12491
13193
  try {
12492
- await fs20.access(join13(process.cwd(), path10));
13194
+ await fs24.access(join15(process.cwd(), path13));
12493
13195
  return true;
12494
13196
  } catch {
12495
13197
  return false;
@@ -12498,11 +13200,11 @@ async function checkPathExists2(path10) {
12498
13200
  async function checkIdeFilesExist(ideId, format) {
12499
13201
  try {
12500
13202
  if (ideId === "agentsmd") {
12501
- return await checkPathExists2("AGENTS.md");
13203
+ return await checkPathExists("AGENTS.md");
12502
13204
  } else if (ideId === "gemini") {
12503
- return await checkPathExists2("GEMINI.md");
13205
+ return await checkPathExists("GEMINI.md");
12504
13206
  } else {
12505
- return await checkPathExists2(format.rulesPath);
13207
+ return await checkPathExists(format.rulesPath);
12506
13208
  }
12507
13209
  } catch {
12508
13210
  return false;
@@ -12511,26 +13213,26 @@ async function checkIdeFilesExist(ideId, format) {
12511
13213
 
12512
13214
  // src/commands/rules/index.ts
12513
13215
  function showRulesHelp() {
12514
- console.log(chalk15.bold.cyan("\u{1F4CB} jai1 rules") + chalk15.dim(" - Qu\u1EA3n l\xFD rule presets cho AI agents"));
13216
+ console.log(chalk17.bold.cyan("\u{1F4CB} jai1 rules") + chalk17.dim(" - Qu\u1EA3n l\xFD rule presets cho AI agents"));
12515
13217
  console.log();
12516
- console.log(chalk15.bold("C\xE1c l\u1EC7nh:"));
12517
- console.log(` ${chalk15.cyan("list")} Li\u1EC7t k\xEA c\xE1c presets c\xF3 s\u1EB5n`);
12518
- console.log(` ${chalk15.cyan("info")} Xem chi ti\u1EBFt m\u1ED9t preset`);
12519
- console.log(` ${chalk15.cyan("init")} Kh\u1EDFi t\u1EA1o rules t\u1EEB preset`);
12520
- console.log(` ${chalk15.cyan("apply")} \xC1p d\u1EE5ng preset v\xE0o project`);
12521
- console.log(` ${chalk15.cyan("sync")} \u0110\u1ED3ng b\u1ED9 rules v\u1EDBi server`);
12522
- console.log(` ${chalk15.cyan("restore")} Kh\xF4i ph\u1EE5c rules t\u1EEB backup`);
13218
+ console.log(chalk17.bold("C\xE1c l\u1EC7nh:"));
13219
+ console.log(` ${chalk17.cyan("list")} Li\u1EC7t k\xEA c\xE1c presets c\xF3 s\u1EB5n`);
13220
+ console.log(` ${chalk17.cyan("info")} Xem chi ti\u1EBFt m\u1ED9t preset`);
13221
+ console.log(` ${chalk17.cyan("init")} Kh\u1EDFi t\u1EA1o rules t\u1EEB preset`);
13222
+ console.log(` ${chalk17.cyan("apply")} \xC1p d\u1EE5ng preset v\xE0o project`);
13223
+ console.log(` ${chalk17.cyan("sync")} \u0110\u1ED3ng b\u1ED9 rules sang c\xE1c \u0111\u1ECBnh d\u1EA1ng IDE`);
13224
+ console.log(` ${chalk17.cyan("restore")} Kh\xF4i ph\u1EE5c rules t\u1EEB backup`);
12523
13225
  console.log();
12524
- console.log(chalk15.bold("V\xED d\u1EE5:"));
12525
- console.log(chalk15.dim(" $ jai1 rules list"));
12526
- console.log(chalk15.dim(" $ jai1 rules info react-typescript"));
12527
- console.log(chalk15.dim(" $ jai1 rules init --preset=react-typescript"));
12528
- console.log(chalk15.dim(" $ jai1 rules apply react-typescript"));
13226
+ console.log(chalk17.bold("V\xED d\u1EE5:"));
13227
+ console.log(chalk17.dim(" $ jai1 rules list"));
13228
+ console.log(chalk17.dim(" $ jai1 rules info react-typescript"));
13229
+ console.log(chalk17.dim(" $ jai1 rules init --preset=react-typescript"));
13230
+ console.log(chalk17.dim(" $ jai1 rules apply react-typescript"));
12529
13231
  console.log();
12530
- console.log(chalk15.dim('Ch\u1EA1y "jai1 rules <l\u1EC7nh> --help" \u0111\u1EC3 xem chi ti\u1EBFt'));
13232
+ console.log(chalk17.dim('Ch\u1EA1y "jai1 rules <l\u1EC7nh> --help" \u0111\u1EC3 xem chi ti\u1EBFt'));
12531
13233
  }
12532
13234
  function createRulesCommand() {
12533
- const rulesCommand = new Command48("rules").description("Manage rule presets for AI agents").action(() => {
13235
+ const rulesCommand = new Command49("rules").description("Manage rule presets for AI agents").action(() => {
12534
13236
  showRulesHelp();
12535
13237
  });
12536
13238
  rulesCommand.addCommand(createRulesListCommand());
@@ -12543,10 +13245,10 @@ function createRulesCommand() {
12543
13245
  }
12544
13246
 
12545
13247
  // src/commands/upgrade.ts
12546
- import { Command as Command49 } from "commander";
13248
+ import { Command as Command50 } from "commander";
12547
13249
  import { confirm as confirm11 } from "@inquirer/prompts";
12548
- import { execSync as execSync2 } from "child_process";
12549
- var colors4 = {
13250
+ import { execSync as execSync4 } from "child_process";
13251
+ var colors3 = {
12550
13252
  yellow: "\x1B[33m",
12551
13253
  green: "\x1B[32m",
12552
13254
  cyan: "\x1B[36m",
@@ -12555,7 +13257,7 @@ var colors4 = {
12555
13257
  bold: "\x1B[1m"
12556
13258
  };
12557
13259
  function createUpgradeCommand() {
12558
- return new Command49("upgrade").description("Upgrade jai1-client to the latest version").option("--check", "Only check for updates without installing").option("--force", "Force upgrade without confirmation").action(async (options) => {
13260
+ return new Command50("upgrade").description("Upgrade jai1-client to the latest version").option("--check", "Only check for updates without installing").option("--force", "Force upgrade without confirmation").action(async (options) => {
12559
13261
  await handleUpgrade(options);
12560
13262
  });
12561
13263
  }
@@ -12566,7 +13268,7 @@ async function handleUpgrade(options) {
12566
13268
  throw new ValidationError('Not initialized. Run "jai1 auth" first.');
12567
13269
  }
12568
13270
  try {
12569
- console.log(`${colors4.cyan}\u{1F50D} Checking for updates...${colors4.reset}`);
13271
+ console.log(`${colors3.cyan}\u{1F50D} Checking for updates...${colors3.reset}`);
12570
13272
  const response = await fetch(`${config.apiUrl}/api/versions/client`, {
12571
13273
  headers: {
12572
13274
  "JAI1-Access-Key": config.accessKey
@@ -12581,20 +13283,20 @@ async function handleUpgrade(options) {
12581
13283
  const latestVersion = data.version;
12582
13284
  const currentVersion = package_default.version;
12583
13285
  console.log(`
12584
- ${colors4.bold}Current version:${colors4.reset} ${currentVersion}`);
12585
- console.log(`${colors4.bold}Latest version:${colors4.reset} ${latestVersion}
13286
+ ${colors3.bold}Current version:${colors3.reset} ${currentVersion}`);
13287
+ console.log(`${colors3.bold}Latest version:${colors3.reset} ${latestVersion}
12586
13288
  `);
12587
13289
  if (!isNewerVersion2(latestVersion, currentVersion)) {
12588
- console.log(`${colors4.green}\u2705 You're already on the latest version!${colors4.reset}
13290
+ console.log(`${colors3.green}\u2705 You're already on the latest version!${colors3.reset}
12589
13291
  `);
12590
13292
  return;
12591
13293
  }
12592
- console.log(`${colors4.yellow}\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E${colors4.reset}`);
12593
- console.log(`${colors4.yellow}\u2502${colors4.reset} ${colors4.bold}\u2B06\uFE0F Update available!${colors4.reset} ${currentVersion} \u2192 ${colors4.cyan}${latestVersion}${colors4.reset} ${colors4.yellow}\u2502${colors4.reset}`);
12594
- console.log(`${colors4.yellow}\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F${colors4.reset}
13294
+ console.log(`${colors3.yellow}\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E${colors3.reset}`);
13295
+ console.log(`${colors3.yellow}\u2502${colors3.reset} ${colors3.bold}\u2B06\uFE0F Update available!${colors3.reset} ${currentVersion} \u2192 ${colors3.cyan}${latestVersion}${colors3.reset} ${colors3.yellow}\u2502${colors3.reset}`);
13296
+ console.log(`${colors3.yellow}\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F${colors3.reset}
12595
13297
  `);
12596
13298
  if (options.check) {
12597
- console.log(`${colors4.cyan}Run "jai1 upgrade" to install the latest version.${colors4.reset}
13299
+ console.log(`${colors3.cyan}Run "jai1 upgrade" to install the latest version.${colors3.reset}
12598
13300
  `);
12599
13301
  return;
12600
13302
  }
@@ -12604,24 +13306,24 @@ ${colors4.bold}Current version:${colors4.reset} ${currentVersion}`);
12604
13306
  default: true
12605
13307
  });
12606
13308
  if (!shouldUpdate) {
12607
- console.log(`${colors4.yellow}\u23F8\uFE0F Upgrade cancelled.${colors4.reset}
13309
+ console.log(`${colors3.yellow}\u23F8\uFE0F Upgrade cancelled.${colors3.reset}
12608
13310
  `);
12609
13311
  return;
12610
13312
  }
12611
13313
  }
12612
13314
  console.log(`
12613
- ${colors4.cyan}\u{1F4E5} Installing latest version...${colors4.reset}
13315
+ ${colors3.cyan}\u{1F4E5} Installing latest version...${colors3.reset}
12614
13316
  `);
12615
13317
  try {
12616
13318
  const packageManager2 = detectPackageManager();
12617
13319
  const installCommand = getInstallCommand(packageManager2);
12618
- console.log(`${colors4.cyan}Using ${packageManager2}...${colors4.reset}`);
12619
- execSync2(installCommand, {
13320
+ console.log(`${colors3.cyan}Using ${packageManager2}...${colors3.reset}`);
13321
+ execSync4(installCommand, {
12620
13322
  stdio: "inherit",
12621
13323
  env: { ...process.env, FORCE_COLOR: "1" }
12622
13324
  });
12623
13325
  console.log(`
12624
- ${colors4.green}\u2705 Successfully upgraded to version ${latestVersion}!${colors4.reset}
13326
+ ${colors3.green}\u2705 Successfully upgraded to version ${latestVersion}!${colors3.reset}
12625
13327
  `);
12626
13328
  trackAction("upgrade", {
12627
13329
  from_version: currentVersion,
@@ -12631,17 +13333,17 @@ ${colors4.green}\u2705 Successfully upgraded to version ${latestVersion}!${color
12631
13333
  disableUpdateCheck();
12632
13334
  } catch (error) {
12633
13335
  console.error(`
12634
- ${colors4.red}\u274C Upgrade failed!${colors4.reset}`);
12635
- console.error(`${colors4.red}Error: ${error instanceof Error ? error.message : "Unknown error"}${colors4.reset}
13336
+ ${colors3.red}\u274C Upgrade failed!${colors3.reset}`);
13337
+ console.error(`${colors3.red}Error: ${error instanceof Error ? error.message : "Unknown error"}${colors3.reset}
12636
13338
  `);
12637
- console.error(`${colors4.yellow}\u{1F4A1} You can try manually upgrading with:${colors4.reset}`);
13339
+ console.error(`${colors3.yellow}\u{1F4A1} You can try manually upgrading with:${colors3.reset}`);
12638
13340
  const manualCommands = {
12639
13341
  npm: `npm install -g @jvittechs/jai1-cli@latest`,
12640
13342
  pnpm: `pnpm add -g @jvittechs/jai1-cli@latest`,
12641
13343
  yarn: `yarn global add @jvittechs/jai1-cli@latest`,
12642
13344
  bun: `bun add -g @jvittechs/jai1-cli@latest`
12643
13345
  };
12644
- console.error(` ${colors4.cyan}${manualCommands[packageManager]}${colors4.reset}
13346
+ console.error(` ${colors3.cyan}${manualCommands[packageManager]}${colors3.reset}
12645
13347
  `);
12646
13348
  throw error;
12647
13349
  }
@@ -12665,7 +13367,7 @@ function isNewerVersion2(remote, local) {
12665
13367
  }
12666
13368
  function detectPackageManager() {
12667
13369
  try {
12668
- const jai1Path = execSync2("which jai1 || where jai1", {
13370
+ const jai1Path = execSync4("which jai1 || where jai1", {
12669
13371
  encoding: "utf-8",
12670
13372
  stdio: ["pipe", "pipe", "ignore"]
12671
13373
  }).trim();
@@ -12703,11 +13405,11 @@ function getInstallCommand(packageManager2) {
12703
13405
  }
12704
13406
 
12705
13407
  // src/commands/clean.ts
12706
- import { Command as Command50 } from "commander";
13408
+ import { Command as Command51 } from "commander";
12707
13409
  import { confirm as confirm12, select as select7 } from "@inquirer/prompts";
12708
- import { join as join14 } from "path";
13410
+ import { join as join16 } from "path";
12709
13411
  function createCleanCommand() {
12710
- return new Command50("clean").description("Clean up backups, cache, and temporary files").option("-y, --yes", "Skip confirmation").option("--backups", "Clean only backup files").option("--all", "Clean all (backups + cache)").action(async (options) => {
13412
+ return new Command51("clean").description("Clean up backups, cache, and temporary files").option("-y, --yes", "Skip confirmation").option("--backups", "Clean only backup files").option("--all", "Clean all (backups + cache)").action(async (options) => {
12711
13413
  await handleClean(options);
12712
13414
  });
12713
13415
  }
@@ -12718,7 +13420,7 @@ async function handleClean(options) {
12718
13420
  {
12719
13421
  name: "Backups",
12720
13422
  description: "Component backup files (.jai1_backup/)",
12721
- path: join14(cwd, ".jai1_backup"),
13423
+ path: join16(cwd, ".jai1_backup"),
12722
13424
  check: async () => {
12723
13425
  const backups = await service.listBackups(cwd);
12724
13426
  return { exists: backups.length > 0, count: backups.length };
@@ -12777,7 +13479,8 @@ async function handleClean(options) {
12777
13479
  })),
12778
13480
  { name: "\u{1F9F9} Clean all", value: "all" },
12779
13481
  { name: "\u274C Cancel", value: "cancel" }
12780
- ]
13482
+ ],
13483
+ theme: selectTheme
12781
13484
  });
12782
13485
  if (action === "cancel") {
12783
13486
  console.log("\n\u274C Operation cancelled.");
@@ -12820,10 +13523,10 @@ async function cleanTarget(target, skipConfirm) {
12820
13523
  }
12821
13524
 
12822
13525
  // src/commands/redmine/check.ts
12823
- import { Command as Command51 } from "commander";
13526
+ import { Command as Command52 } from "commander";
12824
13527
 
12825
13528
  // src/services/redmine-config.service.ts
12826
- import { readFile as readFile6 } from "fs/promises";
13529
+ import { readFile as readFile7 } from "fs/promises";
12827
13530
  import { resolve as resolve2 } from "path";
12828
13531
 
12829
13532
  // src/types/redmine.types.ts
@@ -12882,7 +13585,7 @@ var RedmineConfigService = class {
12882
13585
  */
12883
13586
  async load() {
12884
13587
  try {
12885
- const content = await readFile6(this.configPath, "utf-8");
13588
+ const content = await readFile7(this.configPath, "utf-8");
12886
13589
  const rawConfig = parse(content);
12887
13590
  return RedmineConfigSchema.parse(rawConfig);
12888
13591
  } catch (error) {
@@ -12921,7 +13624,7 @@ var RedmineConfigService = class {
12921
13624
  // src/api.ts
12922
13625
  import { fetch as fetch2 } from "undici";
12923
13626
  import pRetry2 from "p-retry";
12924
- import pLimit3 from "p-limit";
13627
+ import pLimit4 from "p-limit";
12925
13628
  var RedmineApiError = class extends Error {
12926
13629
  constructor(message, status, response) {
12927
13630
  super(message);
@@ -12939,10 +13642,10 @@ var RedmineApiClient = class {
12939
13642
  this.baseUrl = config.baseUrl.replace(/\/$/, "");
12940
13643
  this.apiAccessToken = config.apiAccessToken;
12941
13644
  this.retryConfig = config.defaults.retry;
12942
- this.concurrencyLimit = pLimit3(config.defaults.concurrency);
13645
+ this.concurrencyLimit = pLimit4(config.defaults.concurrency);
12943
13646
  }
12944
- async request(path10, options = {}) {
12945
- const url = `${this.baseUrl}${path10}`;
13647
+ async request(path13, options = {}) {
13648
+ const url = `${this.baseUrl}${path13}`;
12946
13649
  const headers = {
12947
13650
  "X-Redmine-API-Key": this.apiAccessToken,
12948
13651
  "Content-Type": "application/json",
@@ -13003,8 +13706,8 @@ var RedmineApiClient = class {
13003
13706
  if (include && include.length > 0) {
13004
13707
  params.append("include", include.join(","));
13005
13708
  }
13006
- const path10 = `/issues/${issueId}.json${params.toString() ? `?${params.toString()}` : ""}`;
13007
- return this.request(path10);
13709
+ const path13 = `/issues/${issueId}.json${params.toString() ? `?${params.toString()}` : ""}`;
13710
+ return this.request(path13);
13008
13711
  }
13009
13712
  async getIssues(projectId, options = {}) {
13010
13713
  const params = new URLSearchParams();
@@ -13024,8 +13727,8 @@ var RedmineApiClient = class {
13024
13727
  if (options.updatedSince) {
13025
13728
  params.append("updated_on", `>=${options.updatedSince}`);
13026
13729
  }
13027
- const path10 = `/issues.json?${params.toString()}`;
13028
- return this.request(path10);
13730
+ const path13 = `/issues.json?${params.toString()}`;
13731
+ return this.request(path13);
13029
13732
  }
13030
13733
  async getAllIssues(projectId, options = {}) {
13031
13734
  const pageSize = options.pageSize || 100;
@@ -13127,7 +13830,7 @@ async function checkConnectivity(config) {
13127
13830
 
13128
13831
  // src/commands/redmine/check.ts
13129
13832
  function createRedmineCheckCommand() {
13130
- const cmd = new Command51("check").description("Check Redmine connectivity").option("-c, --config <path>", "Config file path", "redmine.config.yaml").option("--json", "Output as JSON").action(async (options) => {
13833
+ const cmd = new Command52("check").description("Check Redmine connectivity").option("-c, --config <path>", "Config file path", "redmine.config.yaml").option("--json", "Output as JSON").action(async (options) => {
13131
13834
  await handleRedmineCheck(options);
13132
13835
  });
13133
13836
  return cmd;
@@ -13155,7 +13858,7 @@ async function handleRedmineCheck(options) {
13155
13858
  }
13156
13859
 
13157
13860
  // src/commands/redmine/sync-issue.ts
13158
- import { Command as Command52 } from "commander";
13861
+ import { Command as Command53 } from "commander";
13159
13862
 
13160
13863
  // src/sync-issue.ts
13161
13864
  import { resolve as resolve3, relative } from "path";
@@ -13297,7 +14000,7 @@ function generateFilename(issueId, title, config, existingSlugs = /* @__PURE__ *
13297
14000
  }
13298
14001
 
13299
14002
  // src/file.util.ts
13300
- import { readFile as readFile7, writeFile as writeFile2, mkdir } from "fs/promises";
14003
+ import { readFile as readFile8, writeFile as writeFile2, mkdir } from "fs/promises";
13301
14004
  import { dirname as dirname2 } from "path";
13302
14005
  import matter4 from "gray-matter";
13303
14006
  async function ensureDir(filePath) {
@@ -13306,7 +14009,7 @@ async function ensureDir(filePath) {
13306
14009
  }
13307
14010
  async function readMarkdownFile(filePath) {
13308
14011
  try {
13309
- const content = await readFile7(filePath, "utf-8");
14012
+ const content = await readFile8(filePath, "utf-8");
13310
14013
  return parseMarkdownContent(content);
13311
14014
  } catch (error) {
13312
14015
  if (error.code === "ENOENT") {
@@ -13539,7 +14242,7 @@ function extractIssueIdFromUrl(url) {
13539
14242
 
13540
14243
  // src/commands/redmine/sync-issue.ts
13541
14244
  function createSyncIssueCommand() {
13542
- const cmd = new Command52("issue").description("Sync a single issue").option("-i, --id <number>", "Issue ID").option("-u, --url <url>", "Issue URL").option("--dry-run", "Preview without making changes").option("-c, --config <path>", "Config file path").option("-o, --output-dir <path>", "Output directory").option("--json", "Output as JSON").action(async (options) => {
14245
+ const cmd = new Command53("issue").description("Sync a single issue").option("-i, --id <number>", "Issue ID").option("-u, --url <url>", "Issue URL").option("--dry-run", "Preview without making changes").option("-c, --config <path>", "Config file path").option("-o, --output-dir <path>", "Output directory").option("--json", "Output as JSON").action(async (options) => {
13543
14246
  await handleSyncIssue(options);
13544
14247
  });
13545
14248
  return cmd;
@@ -13583,7 +14286,7 @@ async function handleSyncIssue(options) {
13583
14286
  }
13584
14287
 
13585
14288
  // src/commands/redmine/sync-project.ts
13586
- import { Command as Command53 } from "commander";
14289
+ import { Command as Command54 } from "commander";
13587
14290
 
13588
14291
  // src/sync-project.ts
13589
14292
  async function syncProject(config, options = {}) {
@@ -13653,7 +14356,7 @@ async function syncProject(config, options = {}) {
13653
14356
 
13654
14357
  // src/commands/redmine/sync-project.ts
13655
14358
  function createSyncProjectCommand() {
13656
- const cmd = new Command53("project").description("Sync all issues in a project").option("-s, --status <status>", "Filter by status (default: *)", "*").option("--updated-since <date>", "Only sync issues updated since YYYY-MM-DD").option("--concurrency <number>", "Number of concurrent requests").option("--page-size <number>", "Page size for API requests").option("--dry-run", "Preview without making changes").option("-c, --config <path>", "Config file path").option("-o, --output-dir <path>", "Output directory").option("--json", "Output as JSON").action(async (options) => {
14359
+ const cmd = new Command54("project").description("Sync all issues in a project").option("-s, --status <status>", "Filter by status (default: *)", "*").option("--updated-since <date>", "Only sync issues updated since YYYY-MM-DD").option("--concurrency <number>", "Number of concurrent requests").option("--page-size <number>", "Page size for API requests").option("--dry-run", "Preview without making changes").option("-c, --config <path>", "Config file path").option("-o, --output-dir <path>", "Output directory").option("--json", "Output as JSON").action(async (options) => {
13657
14360
  await handleSyncProject(options);
13658
14361
  });
13659
14362
  return cmd;
@@ -13708,12 +14411,12 @@ async function handleSyncProject(options) {
13708
14411
  }
13709
14412
 
13710
14413
  // src/commands/framework/info.ts
13711
- import { Command as Command54 } from "commander";
13712
- import { promises as fs21 } from "fs";
13713
- import { join as join15 } from "path";
14414
+ import { Command as Command55 } from "commander";
14415
+ import { promises as fs25 } from "fs";
14416
+ import { join as join17 } from "path";
13714
14417
  import { homedir as homedir5 } from "os";
13715
14418
  function createInfoCommand() {
13716
- const cmd = new Command54("info").description("Show jai1-client configuration and status").option("--json", "Output as JSON").option("--verbose", "Show detailed information").action(async (options) => {
14419
+ const cmd = new Command55("info").description("Show jai1-client configuration and status").option("--json", "Output as JSON").option("--verbose", "Show detailed information").action(async (options) => {
13717
14420
  await handleInfo(options);
13718
14421
  });
13719
14422
  return cmd;
@@ -13724,7 +14427,7 @@ async function handleInfo(options) {
13724
14427
  if (!config) {
13725
14428
  throw new ValidationError('Not initialized. Run "jai1 auth" first.');
13726
14429
  }
13727
- const frameworkPath = join15(homedir5(), ".jai1", "framework");
14430
+ const frameworkPath = join17(homedir5(), ".jai1", "framework");
13728
14431
  const projectStatus = await getProjectStatus2();
13729
14432
  const info = {
13730
14433
  configPath: configService.getConfigPath(),
@@ -13759,9 +14462,9 @@ function maskKey3(key) {
13759
14462
  return "****" + key.slice(-4);
13760
14463
  }
13761
14464
  async function getProjectStatus2() {
13762
- const projectJai1 = join15(process.cwd(), ".jai1");
14465
+ const projectJai1 = join17(process.cwd(), ".jai1");
13763
14466
  try {
13764
- await fs21.access(projectJai1);
14467
+ await fs25.access(projectJai1);
13765
14468
  return { exists: true, version: "Synced" };
13766
14469
  } catch {
13767
14470
  return { exists: false };
@@ -13769,10 +14472,10 @@ async function getProjectStatus2() {
13769
14472
  }
13770
14473
 
13771
14474
  // src/commands/self-update.ts
13772
- import { Command as Command55 } from "commander";
14475
+ import { Command as Command56 } from "commander";
13773
14476
  import { confirm as confirm13 } from "@inquirer/prompts";
13774
- import { execSync as execSync3 } from "child_process";
13775
- var colors5 = {
14477
+ import { execSync as execSync5 } from "child_process";
14478
+ var colors4 = {
13776
14479
  yellow: "\x1B[33m",
13777
14480
  green: "\x1B[32m",
13778
14481
  cyan: "\x1B[36m",
@@ -13781,7 +14484,7 @@ var colors5 = {
13781
14484
  bold: "\x1B[1m"
13782
14485
  };
13783
14486
  function createSelfUpdateCommand() {
13784
- return new Command55("self-update").description("Update jai1-client to the latest version").option("--check", "Only check for updates without installing").option("--force", "Force update without confirmation").action(async (options) => {
14487
+ return new Command56("self-update").description("Update jai1-client to the latest version").option("--check", "Only check for updates without installing").option("--force", "Force update without confirmation").action(async (options) => {
13785
14488
  await handleSelfUpdate(options);
13786
14489
  });
13787
14490
  }
@@ -13792,7 +14495,7 @@ async function handleSelfUpdate(options) {
13792
14495
  throw new ValidationError('Not initialized. Run "jai1 auth" first.');
13793
14496
  }
13794
14497
  try {
13795
- console.log(`${colors5.cyan}\u{1F50D} Checking for updates...${colors5.reset}`);
14498
+ console.log(`${colors4.cyan}\u{1F50D} Checking for updates...${colors4.reset}`);
13796
14499
  const response = await fetch(`${config.apiUrl}/api/versions/client`, {
13797
14500
  headers: {
13798
14501
  "JAI1-Access-Key": config.accessKey
@@ -13807,20 +14510,20 @@ async function handleSelfUpdate(options) {
13807
14510
  const latestVersion = data.version;
13808
14511
  const currentVersion = package_default.version;
13809
14512
  console.log(`
13810
- ${colors5.bold}Current version:${colors5.reset} ${currentVersion}`);
13811
- console.log(`${colors5.bold}Latest version:${colors5.reset} ${latestVersion}
14513
+ ${colors4.bold}Current version:${colors4.reset} ${currentVersion}`);
14514
+ console.log(`${colors4.bold}Latest version:${colors4.reset} ${latestVersion}
13812
14515
  `);
13813
14516
  if (!isNewerVersion3(latestVersion, currentVersion)) {
13814
- console.log(`${colors5.green}\u2705 You're already on the latest version!${colors5.reset}
14517
+ console.log(`${colors4.green}\u2705 You're already on the latest version!${colors4.reset}
13815
14518
  `);
13816
14519
  return;
13817
14520
  }
13818
- console.log(`${colors5.yellow}\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E${colors5.reset}`);
13819
- console.log(`${colors5.yellow}\u2502${colors5.reset} ${colors5.bold}\u2B06\uFE0F Update available!${colors5.reset} ${currentVersion} \u2192 ${colors5.cyan}${latestVersion}${colors5.reset} ${colors5.yellow}\u2502${colors5.reset}`);
13820
- console.log(`${colors5.yellow}\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F${colors5.reset}
14521
+ console.log(`${colors4.yellow}\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E${colors4.reset}`);
14522
+ console.log(`${colors4.yellow}\u2502${colors4.reset} ${colors4.bold}\u2B06\uFE0F Update available!${colors4.reset} ${currentVersion} \u2192 ${colors4.cyan}${latestVersion}${colors4.reset} ${colors4.yellow}\u2502${colors4.reset}`);
14523
+ console.log(`${colors4.yellow}\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F${colors4.reset}
13821
14524
  `);
13822
14525
  if (options.check) {
13823
- console.log(`${colors5.cyan}Run "jai1 self-update" to install the latest version.${colors5.reset}
14526
+ console.log(`${colors4.cyan}Run "jai1 self-update" to install the latest version.${colors4.reset}
13824
14527
  `);
13825
14528
  return;
13826
14529
  }
@@ -13830,24 +14533,24 @@ ${colors5.bold}Current version:${colors5.reset} ${currentVersion}`);
13830
14533
  default: true
13831
14534
  });
13832
14535
  if (!shouldUpdate) {
13833
- console.log(`${colors5.yellow}\u23F8\uFE0F Update cancelled.${colors5.reset}
14536
+ console.log(`${colors4.yellow}\u23F8\uFE0F Update cancelled.${colors4.reset}
13834
14537
  `);
13835
14538
  return;
13836
14539
  }
13837
14540
  }
13838
14541
  console.log(`
13839
- ${colors5.cyan}\u{1F4E5} Installing latest version...${colors5.reset}
14542
+ ${colors4.cyan}\u{1F4E5} Installing latest version...${colors4.reset}
13840
14543
  `);
13841
14544
  try {
13842
14545
  const packageManager2 = detectPackageManager2();
13843
14546
  const installCommand = getInstallCommand2(packageManager2);
13844
- console.log(`${colors5.cyan}Using ${packageManager2}...${colors5.reset}`);
13845
- execSync3(installCommand, {
14547
+ console.log(`${colors4.cyan}Using ${packageManager2}...${colors4.reset}`);
14548
+ execSync5(installCommand, {
13846
14549
  stdio: "inherit",
13847
14550
  env: { ...process.env, FORCE_COLOR: "1" }
13848
14551
  });
13849
14552
  console.log(`
13850
- ${colors5.green}\u2705 Successfully updated to version ${latestVersion}!${colors5.reset}
14553
+ ${colors4.green}\u2705 Successfully updated to version ${latestVersion}!${colors4.reset}
13851
14554
  `);
13852
14555
  trackAction("self_update", {
13853
14556
  from_version: currentVersion,
@@ -13857,11 +14560,11 @@ ${colors5.green}\u2705 Successfully updated to version ${latestVersion}!${colors
13857
14560
  disableUpdateCheck();
13858
14561
  } catch (error) {
13859
14562
  console.error(`
13860
- ${colors5.red}\u274C Update failed!${colors5.reset}`);
13861
- console.error(`${colors5.red}Error: ${error instanceof Error ? error.message : "Unknown error"}${colors5.reset}
14563
+ ${colors4.red}\u274C Update failed!${colors4.reset}`);
14564
+ console.error(`${colors4.red}Error: ${error instanceof Error ? error.message : "Unknown error"}${colors4.reset}
13862
14565
  `);
13863
- console.error(`${colors5.yellow}\u{1F4A1} You can try manually updating with:${colors5.reset}`);
13864
- console.error(` ${colors5.cyan}npm install -g @jvittechs/jai1-cli@latest${colors5.reset}
14566
+ console.error(`${colors4.yellow}\u{1F4A1} You can try manually updating with:${colors4.reset}`);
14567
+ console.error(` ${colors4.cyan}npm install -g @jvittechs/jai1-cli@latest${colors4.reset}
13865
14568
  `);
13866
14569
  throw error;
13867
14570
  }
@@ -13889,17 +14592,17 @@ function detectPackageManager2() {
13889
14592
  if (userAgent.includes("yarn")) return "yarn";
13890
14593
  if (userAgent.includes("bun")) return "bun";
13891
14594
  try {
13892
- execSync3("pnpm --version", { stdio: "ignore" });
14595
+ execSync5("pnpm --version", { stdio: "ignore" });
13893
14596
  return "pnpm";
13894
14597
  } catch {
13895
14598
  }
13896
14599
  try {
13897
- execSync3("yarn --version", { stdio: "ignore" });
14600
+ execSync5("yarn --version", { stdio: "ignore" });
13898
14601
  return "yarn";
13899
14602
  } catch {
13900
14603
  }
13901
14604
  try {
13902
- execSync3("bun --version", { stdio: "ignore" });
14605
+ execSync5("bun --version", { stdio: "ignore" });
13903
14606
  return "bun";
13904
14607
  } catch {
13905
14608
  }
@@ -13921,10 +14624,10 @@ function getInstallCommand2(packageManager2) {
13921
14624
  }
13922
14625
 
13923
14626
  // src/commands/clear-backups.ts
13924
- import { Command as Command56 } from "commander";
14627
+ import { Command as Command57 } from "commander";
13925
14628
  import { confirm as confirm14 } from "@inquirer/prompts";
13926
14629
  function createClearBackupsCommand() {
13927
- return new Command56("clear-backups").description("Clear backup files").option("-y, --yes", "Skip confirmation").action(async (options) => {
14630
+ return new Command57("clear-backups").description("Clear backup files").option("-y, --yes", "Skip confirmation").action(async (options) => {
13928
14631
  const service = new ComponentsService();
13929
14632
  const backups = await service.listBackups(process.cwd());
13930
14633
  if (backups.length === 0) {
@@ -13949,10 +14652,10 @@ function createClearBackupsCommand() {
13949
14652
  }
13950
14653
 
13951
14654
  // src/commands/vscode/index.ts
13952
- import { Command as Command57 } from "commander";
14655
+ import { Command as Command58 } from "commander";
13953
14656
  import { checkbox as checkbox7, confirm as confirm15, select as select8 } from "@inquirer/prompts";
13954
- import fs22 from "fs/promises";
13955
- import path9 from "path";
14657
+ import fs26 from "fs/promises";
14658
+ import path12 from "path";
13956
14659
  import { existsSync as existsSync3 } from "fs";
13957
14660
  var PERFORMANCE_GROUPS2 = {
13958
14661
  telemetry: {
@@ -14089,7 +14792,7 @@ var PERFORMANCE_GROUPS2 = {
14089
14792
  }
14090
14793
  };
14091
14794
  function createVSCodeCommand() {
14092
- const vscodeCommand = new Command57("vscode").description("Qu\u1EA3n l\xFD c\xE0i \u0111\u1EB7t VSCode cho d\u1EF1 \xE1n hi\u1EC7n t\u1EA1i");
14795
+ const vscodeCommand = new Command58("vscode").description("Qu\u1EA3n l\xFD c\xE0i \u0111\u1EB7t VSCode cho d\u1EF1 \xE1n hi\u1EC7n t\u1EA1i");
14093
14796
  vscodeCommand.action(async () => {
14094
14797
  await interactiveMode2();
14095
14798
  });
@@ -14140,7 +14843,8 @@ async function interactiveMode2() {
14140
14843
  { name: "\u274C Disable c\xE1c nh\xF3m t\u1ED1i \u01B0u", value: "disable" },
14141
14844
  { name: "\u{1F680} Max Performance (enable t\u1EA5t c\u1EA3)", value: "max" },
14142
14845
  { name: "\u{1F504} Reset v\u1EC1 m\u1EB7c \u0111\u1ECBnh", value: "reset" }
14143
- ]
14846
+ ],
14847
+ theme: selectTheme
14144
14848
  });
14145
14849
  if (action === "max") {
14146
14850
  const allGroups = Object.keys(PERFORMANCE_GROUPS2);
@@ -14159,7 +14863,8 @@ async function selectGroupsToApply2(action) {
14159
14863
  try {
14160
14864
  const selectedGroups = await checkbox7({
14161
14865
  message: `Ch\u1ECDn c\xE1c nh\xF3m \u0111\u1EC3 ${action === "enable" ? "enable" : "disable"} (SPACE \u0111\u1EC3 ch\u1ECDn, ENTER \u0111\u1EC3 x\xE1c nh\u1EADn):`,
14162
- choices
14866
+ choices,
14867
+ theme: checkboxTheme
14163
14868
  });
14164
14869
  if (selectedGroups.length === 0) {
14165
14870
  console.log("\n\u26A0\uFE0F B\u1EA1n ch\u01B0a ch\u1ECDn nh\xF3m n\xE0o!");
@@ -14172,8 +14877,8 @@ async function selectGroupsToApply2(action) {
14172
14877
  }
14173
14878
  }
14174
14879
  async function applyGroups2(groupKeys, action) {
14175
- const vscodeDir = path9.join(process.cwd(), ".vscode");
14176
- const settingsPath = path9.join(vscodeDir, "settings.json");
14880
+ const vscodeDir = path12.join(process.cwd(), ".vscode");
14881
+ const settingsPath = path12.join(vscodeDir, "settings.json");
14177
14882
  const invalidGroups = groupKeys.filter((key) => !PERFORMANCE_GROUPS2[key]);
14178
14883
  if (invalidGroups.length > 0) {
14179
14884
  console.log(`
@@ -14182,13 +14887,13 @@ async function applyGroups2(groupKeys, action) {
14182
14887
  return;
14183
14888
  }
14184
14889
  if (!existsSync3(vscodeDir)) {
14185
- await fs22.mkdir(vscodeDir, { recursive: true });
14890
+ await fs26.mkdir(vscodeDir, { recursive: true });
14186
14891
  console.log("\u{1F4C1} \u0110\xE3 t\u1EA1o th\u01B0 m\u1EE5c .vscode/");
14187
14892
  }
14188
14893
  let currentSettings = {};
14189
14894
  if (existsSync3(settingsPath)) {
14190
14895
  try {
14191
- const content = await fs22.readFile(settingsPath, "utf-8");
14896
+ const content = await fs26.readFile(settingsPath, "utf-8");
14192
14897
  currentSettings = JSON.parse(content);
14193
14898
  console.log("\u{1F4C4} \u0110\xE3 \u0111\u1ECDc c\xE0i \u0111\u1EB7t hi\u1EC7n t\u1EA1i t\u1EEB settings.json");
14194
14899
  } catch {
@@ -14228,14 +14933,14 @@ async function applyGroups2(groupKeys, action) {
14228
14933
  }
14229
14934
  }
14230
14935
  }
14231
- await fs22.writeFile(settingsPath, JSON.stringify(newSettings, null, 2));
14936
+ await fs26.writeFile(settingsPath, JSON.stringify(newSettings, null, 2));
14232
14937
  console.log(`
14233
14938
  \u2705 \u0110\xE3 c\u1EADp nh\u1EADt c\xE0i \u0111\u1EB7t VSCode t\u1EA1i: ${settingsPath}`);
14234
14939
  console.log("\u{1F4A1} M\u1EB9o: Kh\u1EDFi \u0111\u1ED9ng l\u1EA1i VSCode \u0111\u1EC3 \xE1p d\u1EE5ng c\xE1c thay \u0111\u1ED5i.");
14235
14940
  }
14236
14941
  async function resetSettings2(groupKeys) {
14237
- const vscodeDir = path9.join(process.cwd(), ".vscode");
14238
- const settingsPath = path9.join(vscodeDir, "settings.json");
14942
+ const vscodeDir = path12.join(process.cwd(), ".vscode");
14943
+ const settingsPath = path12.join(vscodeDir, "settings.json");
14239
14944
  if (!existsSync3(settingsPath)) {
14240
14945
  console.log("\n\u26A0\uFE0F Kh\xF4ng t\xECm th\u1EA5y file settings.json");
14241
14946
  return;
@@ -14249,7 +14954,7 @@ async function resetSettings2(groupKeys) {
14249
14954
  return;
14250
14955
  }
14251
14956
  if (groupKeys.length === 0) {
14252
- await fs22.unlink(settingsPath);
14957
+ await fs26.unlink(settingsPath);
14253
14958
  console.log("\n\u2705 \u0110\xE3 x\xF3a file settings.json");
14254
14959
  } else {
14255
14960
  await applyGroups2(groupKeys, "disable");
@@ -14260,9 +14965,9 @@ async function resetSettings2(groupKeys) {
14260
14965
  // src/commands/context.ts
14261
14966
  import React43 from "react";
14262
14967
  import { render as render6 } from "ink";
14263
- import { Command as Command58 } from "commander";
14968
+ import { Command as Command59 } from "commander";
14264
14969
  function createContextCommand() {
14265
- const cmd = new Command58("context").description("Kh\xE1m ph\xE1 v\xE0 qu\u1EA3n l\xFD context d\u1EF1 \xE1n cho c\xE1c IDE").option("--ide <ide>", "M\u1EDF tr\u1EF1c ti\u1EBFp IDE c\u1EE5 th\u1EC3 (cursor, windsurf, antigravity, jai1)").option("--type <type>", "Hi\u1EC3n th\u1ECB lo\u1EA1i context c\u1EE5 th\u1EC3 (rules, workflows, skills, agents, prompts)").option("--stats", "Hi\u1EC3n th\u1ECB th\u1ED1ng k\xEA context (non-interactive)").action(async (options) => {
14970
+ const cmd = new Command59("context").description("Kh\xE1m ph\xE1 v\xE0 qu\u1EA3n l\xFD context d\u1EF1 \xE1n cho c\xE1c IDE").option("--ide <ide>", "M\u1EDF tr\u1EF1c ti\u1EBFp IDE c\u1EE5 th\u1EC3 (cursor, windsurf, antigravity, jai1)").option("--type <type>", "Hi\u1EC3n th\u1ECB lo\u1EA1i context c\u1EE5 th\u1EC3 (rules, workflows, skills, agents, prompts)").option("--stats", "Hi\u1EC3n th\u1ECB th\u1ED1ng k\xEA context (non-interactive)").action(async (options) => {
14266
14971
  let initialIDE;
14267
14972
  if (options.ide) {
14268
14973
  const validIDEs = ["cursor", "windsurf", "antigravity", "jai1"];
@@ -14339,10 +15044,10 @@ async function printStats2() {
14339
15044
  }
14340
15045
 
14341
15046
  // src/commands/migrate-ide.ts
14342
- import { Command as Command59 } from "commander";
15047
+ import { Command as Command60 } from "commander";
14343
15048
  import { checkbox as checkbox8, confirm as confirm16 } from "@inquirer/prompts";
14344
15049
  function createMigrateIdeCommand() {
14345
- const cmd = new Command59("migrate-ide").description("Migrate .jai1 rules v\xE0 workflows sang IDEs (Cursor, Windsurf, Claude Code, etc.)").option("--ide <ides...>", "Target IDEs (cursor, windsurf, antigravity, claudecode, opencode)").option("--type <types...>", "Content types (rules, workflows, commands)").option("--dry-run", "Preview changes without writing files").action(async (options) => {
15050
+ const cmd = new Command60("migrate-ide").description("Migrate .jai1 rules v\xE0 workflows sang IDEs (Cursor, Windsurf, Claude Code, etc.)").option("--ide <ides...>", "Target IDEs (cursor, windsurf, antigravity, claudecode, opencode)").option("--type <types...>", "Content types (rules, workflows, commands)").option("--dry-run", "Preview changes without writing files").action(async (options) => {
14346
15051
  await runMigrateIde(options);
14347
15052
  });
14348
15053
  return cmd;
@@ -14372,7 +15077,8 @@ async function runMigrateIde(options) {
14372
15077
  });
14373
15078
  selectedIdes = await checkbox8({
14374
15079
  message: "Ch\u1ECDn IDE(s) \u0111\u1EC3 migrate (SPACE \u0111\u1EC3 ch\u1ECDn, ENTER \u0111\u1EC3 x\xE1c nh\u1EADn):",
14375
- choices: ideChoices
15080
+ choices: ideChoices,
15081
+ theme: checkboxTheme
14376
15082
  });
14377
15083
  if (selectedIdes.length === 0) {
14378
15084
  console.log("\n\u26A0\uFE0F B\u1EA1n ch\u01B0a ch\u1ECDn IDE n\xE0o!");
@@ -14391,7 +15097,8 @@ async function runMigrateIde(options) {
14391
15097
  ];
14392
15098
  selectedTypes = await checkbox8({
14393
15099
  message: "Ch\u1ECDn content types \u0111\u1EC3 migrate:",
14394
- choices: typeChoices
15100
+ choices: typeChoices,
15101
+ theme: checkboxTheme
14395
15102
  });
14396
15103
  if (selectedTypes.length === 0) {
14397
15104
  console.log("\n\u26A0\uFE0F B\u1EA1n ch\u01B0a ch\u1ECDn content type n\xE0o!");
@@ -14449,54 +15156,54 @@ async function runMigrateIde(options) {
14449
15156
 
14450
15157
  // src/utils/help-formatter.ts
14451
15158
  import boxen4 from "boxen";
14452
- import chalk16 from "chalk";
15159
+ import chalk18 from "chalk";
14453
15160
  import gradient from "gradient-string";
14454
15161
  import figlet from "figlet";
14455
15162
  function showCustomHelp(version) {
14456
15163
  const title = figlet.textSync("JAI1", { font: "Small" });
14457
15164
  console.log(gradient.pastel(title));
14458
15165
  console.log(
14459
- boxen4(chalk16.cyan(`Agentic Coding CLI v${version}`), {
15166
+ boxen4(chalk18.cyan(`Agentic Coding CLI v${version}`), {
14460
15167
  padding: { left: 1, right: 1, top: 0, bottom: 0 },
14461
15168
  borderStyle: "round",
14462
15169
  borderColor: "cyan"
14463
15170
  })
14464
15171
  );
14465
- console.log(chalk16.bold("\n\u{1F527} Thi\u1EBFt l\u1EADp & Th\xF4ng tin"));
15172
+ console.log(chalk18.bold("\n\u{1F527} Thi\u1EBFt l\u1EADp & Th\xF4ng tin"));
14466
15173
  console.log(" auth X\xE1c th\u1EF1c v\xE0 c\u1EA5u h\xECnh jai1-client");
14467
15174
  console.log(" status Hi\u1EC3n th\u1ECB tr\u1EA1ng th\xE1i c\u1EA5u h\xECnh");
14468
15175
  console.log(" guide Trung t\xE2m h\u1ECDc Agentic Coding");
14469
- console.log(chalk16.bold("\n\u{1F4E6} Qu\u1EA3n l\xFD Components"));
15176
+ console.log(chalk18.bold("\n\u{1F4E6} Qu\u1EA3n l\xFD Components"));
14470
15177
  console.log(" apply C\xE0i \u0111\u1EB7t components (interactive)");
14471
15178
  console.log(" update C\u1EADp nh\u1EADt components \u0111\xE3 c\xE0i");
14472
15179
  console.log(" check Ki\u1EC3m tra c\u1EADp nh\u1EADt t\u1EEB server");
14473
- console.log(chalk16.bold("\n\u{1F5A5}\uFE0F IDE & T\xEDch h\u1EE3p"));
15180
+ console.log(chalk18.bold("\n\u{1F5A5}\uFE0F IDE & T\xEDch h\u1EE3p"));
14474
15181
  console.log(" ide L\u1EC7nh c\u1EA5u h\xECnh IDE");
14475
15182
  console.log(" chat Chat AI v\u1EDBi Jai1 LLM Proxy");
14476
15183
  console.log(" openai-keys Th\xF4ng tin API credentials");
14477
- console.log(chalk16.bold("\n\u{1F916} AI Tools"));
15184
+ console.log(chalk18.bold("\n\u{1F916} AI Tools"));
14478
15185
  console.log(" translate D\u1ECBch v\u0103n b\u1EA3n/file b\u1EB1ng AI");
14479
15186
  console.log(" image T\u1EA1o \u1EA3nh (Coming Soon)");
14480
15187
  console.log(" stats Th\u1ED1ng k\xEA s\u1EED d\u1EE5ng LLM");
14481
15188
  console.log(" feedback G\u1EEDi b\xE1o c\xE1o/\u0111\u1EC1 xu\u1EA5t");
14482
- console.log(chalk16.bold("\n\u{1F4C1} Project"));
15189
+ console.log(chalk18.bold("\n\u{1F4C1} Project"));
14483
15190
  console.log(" kit Qu\u1EA3n l\xFD starter kits");
14484
15191
  console.log(" rules Qu\u1EA3n l\xFD rule presets");
14485
15192
  console.log(" deps Qu\u1EA3n l\xFD dependencies");
14486
15193
  console.log(" redmine Redmine context sync");
14487
- console.log(chalk16.bold("\n\u2699\uFE0F B\u1EA3o tr\xEC"));
15194
+ console.log(chalk18.bold("\n\u2699\uFE0F B\u1EA3o tr\xEC"));
14488
15195
  console.log(" upgrade C\u1EADp nh\u1EADt jai1-client");
14489
15196
  console.log(" clean D\u1ECDn d\u1EB9p cache/backup");
14490
15197
  console.log(" utils Developer utilities");
14491
- console.log(chalk16.dim("\nS\u1EED d\u1EE5ng: jai1 [l\u1EC7nh] --help \u0111\u1EC3 xem chi ti\u1EBFt"));
15198
+ console.log(chalk18.dim("\nS\u1EED d\u1EE5ng: jai1 [l\u1EC7nh] --help \u0111\u1EC3 xem chi ti\u1EBFt"));
14492
15199
  }
14493
15200
  function showUnknownCommand(commandName) {
14494
- console.error(chalk16.red(`\u274C L\u1EC7nh kh\xF4ng t\u1ED3n t\u1EA1i: ${commandName}`));
14495
- console.error(chalk16.dim("\nG\u1EE3i \xFD: Ch\u1EA1y jai1 --help \u0111\u1EC3 xem danh s\xE1ch l\u1EC7nh"));
15201
+ console.error(chalk18.red(`\u274C L\u1EC7nh kh\xF4ng t\u1ED3n t\u1EA1i: ${commandName}`));
15202
+ console.error(chalk18.dim("\nG\u1EE3i \xFD: Ch\u1EA1y jai1 --help \u0111\u1EC3 xem danh s\xE1ch l\u1EC7nh"));
14496
15203
  }
14497
15204
 
14498
15205
  // src/cli.ts
14499
- var program = new Command60();
15206
+ var program = new Command61();
14500
15207
  if (process.argv.includes("-v") || process.argv.includes("--version")) {
14501
15208
  console.log(package_default.version);
14502
15209
  if (!process.argv.includes("--skip-update-check")) {
@@ -14530,9 +15237,9 @@ program.addCommand(createKitCommand());
14530
15237
  program.addCommand(createRulesCommand());
14531
15238
  program.addCommand(createUpgradeCommand());
14532
15239
  program.addCommand(createCleanCommand());
14533
- var redmineCommand = new Command60("redmine").description("Redmine context sync commands");
15240
+ var redmineCommand = new Command61("redmine").description("Redmine context sync commands");
14534
15241
  redmineCommand.addCommand(createRedmineCheckCommand());
14535
- var syncCommand = new Command60("sync").description("Sync Redmine issues to markdown files");
15242
+ var syncCommand = new Command61("sync").description("Sync Redmine issues to markdown files");
14536
15243
  syncCommand.addCommand(createSyncIssueCommand());
14537
15244
  syncCommand.addCommand(createSyncProjectCommand());
14538
15245
  redmineCommand.addCommand(syncCommand);