@hapico/cli 0.0.29 → 0.0.30

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/bin/index.js CHANGED
@@ -411,7 +411,7 @@ class RoomState {
411
411
  return this.isConnected;
412
412
  }
413
413
  }
414
- commander_1.program.version("0.0.29").description("Hapico CLI for project management");
414
+ commander_1.program.version("0.0.30").description("Hapico CLI for project management");
415
415
  commander_1.program
416
416
  .command("clone <id>")
417
417
  .description("Clone a project by ID")
@@ -17,7 +17,8 @@ const scanRecursively = async (dir, baseDir) => {
17
17
  const list = await promises_1.default.readdir(dir, { withFileTypes: true });
18
18
  for (const entry of list) {
19
19
  const fullPath = node_path_1.default.join(dir, entry.name);
20
- const relativePath = node_path_1.default.relative(baseDir, fullPath);
20
+ // Chuẩn hóa đường dẫn về forward slash để nhất quán giữa các hệ điều hành
21
+ const relativePath = node_path_1.default.relative(baseDir, fullPath).split(node_path_1.default.sep).join('/');
21
22
  if (shouldIgnorePath(relativePath))
22
23
  continue;
23
24
  if (entry.isDirectory()) {
@@ -604,7 +605,8 @@ const startServer = async (rootDir) => {
604
605
 
605
606
  async init() {
606
607
  try {
607
- const res = await fetch('http://localhost:${PORT}/api/files');
608
+ // Sử dụng relative URL để tránh lỗi phân giải localhost/127.0.0.1 trên Windows
609
+ const res = await fetch('/api/files');
608
610
  const data = await res.json();
609
611
  this.rawFiles = data;
610
612
  this.buildTree();
@@ -614,98 +616,10 @@ const startServer = async (rootDir) => {
614
616
  this.loading = false;
615
617
  }
616
618
 
617
- // Search watcher
618
619
  this.$watch('search', () => this.buildTree());
619
-
620
- // Auto-generate prompt
621
620
  this.$watch('instruction', () => this.debouncedFetchPrompt());
622
621
  this.$watch('selectedFiles', () => this.debouncedFetchPrompt());
623
622
  },
624
-
625
- // ===========================
626
- // Tree Logic
627
- // ===========================
628
-
629
- buildTree() {
630
- // 1. Convert paths to object structure
631
- const root = {};
632
- const searchLower = this.search.toLowerCase();
633
-
634
- this.rawFiles.forEach(file => {
635
- if (this.search && !file.path.toLowerCase().includes(searchLower)) return;
636
-
637
- const parts = file.path.split(/[\\\\/]/);
638
- let current = root;
639
-
640
- parts.forEach((part, index) => {
641
- if (!current[part]) {
642
- current[part] = {
643
- name: part,
644
- path: parts.slice(0, index + 1).join('/'),
645
- children: {},
646
- type: index === parts.length - 1 ? 'file' : 'folder',
647
- fullPath: file.path // Only meaningful for file
648
- };
649
- }
650
- current = current[part].children;
651
- });
652
- });
653
-
654
- // 2. Flatten object structure into renderable list with depth
655
- this.treeNodes = [];
656
- const traverse = (nodeMap, depth) => {
657
- // Sort: Folders first, then Files. Alphabetical.
658
- const keys = Object.keys(nodeMap).sort((a, b) => {
659
- const nodeA = nodeMap[a];
660
- const nodeB = nodeMap[b];
661
- if (nodeA.type !== nodeB.type) return nodeA.type === 'folder' ? -1 : 1;
662
- return a.localeCompare(b);
663
- });
664
-
665
- keys.forEach(key => {
666
- const node = nodeMap[key];
667
- const flatNode = {
668
- id: node.path,
669
- name: node.name,
670
- path: node.path, // Relative path for selection
671
- type: node.type,
672
- depth: depth,
673
- children: Object.keys(node.children).length > 0, // boolean for UI
674
- expanded: this.search.length > 0 || depth < 1, // Auto expand on search or root
675
- rawChildren: node.children // Keep ref for recursion
676
- };
677
-
678
- this.treeNodes.push(flatNode);
679
- if (flatNode.children && flatNode.expanded) {
680
- traverse(node.children, depth + 1);
681
- }
682
- });
683
- };
684
-
685
- traverse(root, 0);
686
- },
687
-
688
- // Re-calculate visible nodes (folding logic) without rebuilding everything
689
- // But for simplicity in this SFC, we just rebuild the list when expanded changes.
690
- toggleExpand(node) {
691
- if (node.type !== 'folder') return;
692
-
693
- // Find node in treeNodes and toggle
694
- // Since treeNodes is flat and generated, we need to persist state or smarter logic.
695
- // Simpler: Just modify the expanded state in the current flat list is tricky because children are dynamic.
696
- // Better approach for this lightweight UI:
697
- // We actually need a persistent "expanded" Set to survive re-renders if we want to be fancy.
698
- // For now, let's just cheat: Update the boolean in the flat list works for collapsing,
699
- // but expanding requires knowing the children.
700
-
701
- // Let's reload the tree logic but using a \`expandedPaths\` set.
702
- if (this.expandedPaths.has(node.path)) {
703
- this.expandedPaths.delete(node.path);
704
- } else {
705
- this.expandedPaths.add(node.path);
706
- }
707
- this.refreshTreeVisibility();
708
- },
709
623
 
710
624
  // Alternative: Just use a computed property for \`visibleNodes\`.
711
625
  // Since Alpine isn't React, we do this manually.
@@ -733,7 +647,8 @@ const startServer = async (rootDir) => {
733
647
 
734
648
  this.rawFiles.forEach(file => {
735
649
  if (this.search && !file.path.toLowerCase().includes(searchLower)) return;
736
- const parts = file.path.split(/[\\\\/]/);
650
+ // File path đã được chuẩn hóa '/' từ backend
651
+ const parts = file.path.split('/');
737
652
  let current = this.rootObject;
738
653
  parts.forEach((part, index) => {
739
654
  if (!current[part]) {
@@ -834,9 +749,8 @@ const startServer = async (rootDir) => {
834
749
  },
835
750
 
836
751
  getAllDescendants(folderPath) {
837
- // Simple filter from rawFiles
838
752
  return this.rawFiles
839
- .filter(f => f.path.startsWith(folderPath + '/') || f.path.startsWith(folderPath + '\\\\'))
753
+ .filter(f => f.path === folderPath || f.path.startsWith(folderPath + '/'))
840
754
  .map(f => f.path);
841
755
  },
842
756
 
@@ -869,7 +783,7 @@ const startServer = async (rootDir) => {
869
783
  }
870
784
  this.generating = true;
871
785
  try {
872
- const res = await fetch('http://localhost:${PORT}/api/prompt', {
786
+ const res = await fetch('/api/prompt', {
873
787
  method: 'POST',
874
788
  headers: {'Content-Type': 'application/json'},
875
789
  body: JSON.stringify({ filePaths: Array.from(this.selectedFiles), instruction: this.instruction })
@@ -896,7 +810,7 @@ const startServer = async (rootDir) => {
896
810
  async applyPatch() {
897
811
  this.applying = true;
898
812
  try {
899
- const res = await fetch('http://localhost:${PORT}/api/apply', {
813
+ const res = await fetch('/api/apply', {
900
814
  method: 'POST',
901
815
  headers: {'Content-Type': 'application/json'},
902
816
  body: JSON.stringify({ filePaths: Array.from(this.selectedFiles), llmResponse: this.llmResponse })
package/dist/index.js CHANGED
@@ -411,7 +411,7 @@ class RoomState {
411
411
  return this.isConnected;
412
412
  }
413
413
  }
414
- commander_1.program.version("0.0.29").description("Hapico CLI for project management");
414
+ commander_1.program.version("0.0.30").description("Hapico CLI for project management");
415
415
  commander_1.program
416
416
  .command("clone <id>")
417
417
  .description("Clone a project by ID")
@@ -17,7 +17,8 @@ const scanRecursively = async (dir, baseDir) => {
17
17
  const list = await promises_1.default.readdir(dir, { withFileTypes: true });
18
18
  for (const entry of list) {
19
19
  const fullPath = node_path_1.default.join(dir, entry.name);
20
- const relativePath = node_path_1.default.relative(baseDir, fullPath);
20
+ // Chuẩn hóa đường dẫn về forward slash để nhất quán giữa các hệ điều hành
21
+ const relativePath = node_path_1.default.relative(baseDir, fullPath).split(node_path_1.default.sep).join('/');
21
22
  if (shouldIgnorePath(relativePath))
22
23
  continue;
23
24
  if (entry.isDirectory()) {
@@ -604,7 +605,8 @@ const startServer = async (rootDir) => {
604
605
 
605
606
  async init() {
606
607
  try {
607
- const res = await fetch('http://localhost:${PORT}/api/files');
608
+ // Sử dụng relative URL để tránh lỗi phân giải localhost/127.0.0.1 trên Windows
609
+ const res = await fetch('/api/files');
608
610
  const data = await res.json();
609
611
  this.rawFiles = data;
610
612
  this.buildTree();
@@ -614,98 +616,10 @@ const startServer = async (rootDir) => {
614
616
  this.loading = false;
615
617
  }
616
618
 
617
- // Search watcher
618
619
  this.$watch('search', () => this.buildTree());
619
-
620
- // Auto-generate prompt
621
620
  this.$watch('instruction', () => this.debouncedFetchPrompt());
622
621
  this.$watch('selectedFiles', () => this.debouncedFetchPrompt());
623
622
  },
624
-
625
- // ===========================
626
- // Tree Logic
627
- // ===========================
628
-
629
- buildTree() {
630
- // 1. Convert paths to object structure
631
- const root = {};
632
- const searchLower = this.search.toLowerCase();
633
-
634
- this.rawFiles.forEach(file => {
635
- if (this.search && !file.path.toLowerCase().includes(searchLower)) return;
636
-
637
- const parts = file.path.split(/[\\\\/]/);
638
- let current = root;
639
-
640
- parts.forEach((part, index) => {
641
- if (!current[part]) {
642
- current[part] = {
643
- name: part,
644
- path: parts.slice(0, index + 1).join('/'),
645
- children: {},
646
- type: index === parts.length - 1 ? 'file' : 'folder',
647
- fullPath: file.path // Only meaningful for file
648
- };
649
- }
650
- current = current[part].children;
651
- });
652
- });
653
-
654
- // 2. Flatten object structure into renderable list with depth
655
- this.treeNodes = [];
656
- const traverse = (nodeMap, depth) => {
657
- // Sort: Folders first, then Files. Alphabetical.
658
- const keys = Object.keys(nodeMap).sort((a, b) => {
659
- const nodeA = nodeMap[a];
660
- const nodeB = nodeMap[b];
661
- if (nodeA.type !== nodeB.type) return nodeA.type === 'folder' ? -1 : 1;
662
- return a.localeCompare(b);
663
- });
664
-
665
- keys.forEach(key => {
666
- const node = nodeMap[key];
667
- const flatNode = {
668
- id: node.path,
669
- name: node.name,
670
- path: node.path, // Relative path for selection
671
- type: node.type,
672
- depth: depth,
673
- children: Object.keys(node.children).length > 0, // boolean for UI
674
- expanded: this.search.length > 0 || depth < 1, // Auto expand on search or root
675
- rawChildren: node.children // Keep ref for recursion
676
- };
677
-
678
- this.treeNodes.push(flatNode);
679
- if (flatNode.children && flatNode.expanded) {
680
- traverse(node.children, depth + 1);
681
- }
682
- });
683
- };
684
-
685
- traverse(root, 0);
686
- },
687
-
688
- // Re-calculate visible nodes (folding logic) without rebuilding everything
689
- // But for simplicity in this SFC, we just rebuild the list when expanded changes.
690
- toggleExpand(node) {
691
- if (node.type !== 'folder') return;
692
-
693
- // Find node in treeNodes and toggle
694
- // Since treeNodes is flat and generated, we need to persist state or smarter logic.
695
- // Simpler: Just modify the expanded state in the current flat list is tricky because children are dynamic.
696
- // Better approach for this lightweight UI:
697
- // We actually need a persistent "expanded" Set to survive re-renders if we want to be fancy.
698
- // For now, let's just cheat: Update the boolean in the flat list works for collapsing,
699
- // but expanding requires knowing the children.
700
-
701
- // Let's reload the tree logic but using a \`expandedPaths\` set.
702
- if (this.expandedPaths.has(node.path)) {
703
- this.expandedPaths.delete(node.path);
704
- } else {
705
- this.expandedPaths.add(node.path);
706
- }
707
- this.refreshTreeVisibility();
708
- },
709
623
 
710
624
  // Alternative: Just use a computed property for \`visibleNodes\`.
711
625
  // Since Alpine isn't React, we do this manually.
@@ -733,7 +647,8 @@ const startServer = async (rootDir) => {
733
647
 
734
648
  this.rawFiles.forEach(file => {
735
649
  if (this.search && !file.path.toLowerCase().includes(searchLower)) return;
736
- const parts = file.path.split(/[\\\\/]/);
650
+ // File path đã được chuẩn hóa '/' từ backend
651
+ const parts = file.path.split('/');
737
652
  let current = this.rootObject;
738
653
  parts.forEach((part, index) => {
739
654
  if (!current[part]) {
@@ -834,9 +749,8 @@ const startServer = async (rootDir) => {
834
749
  },
835
750
 
836
751
  getAllDescendants(folderPath) {
837
- // Simple filter from rawFiles
838
752
  return this.rawFiles
839
- .filter(f => f.path.startsWith(folderPath + '/') || f.path.startsWith(folderPath + '\\\\'))
753
+ .filter(f => f.path === folderPath || f.path.startsWith(folderPath + '/'))
840
754
  .map(f => f.path);
841
755
  },
842
756
 
@@ -869,7 +783,7 @@ const startServer = async (rootDir) => {
869
783
  }
870
784
  this.generating = true;
871
785
  try {
872
- const res = await fetch('http://localhost:${PORT}/api/prompt', {
786
+ const res = await fetch('/api/prompt', {
873
787
  method: 'POST',
874
788
  headers: {'Content-Type': 'application/json'},
875
789
  body: JSON.stringify({ filePaths: Array.from(this.selectedFiles), instruction: this.instruction })
@@ -896,7 +810,7 @@ const startServer = async (rootDir) => {
896
810
  async applyPatch() {
897
811
  this.applying = true;
898
812
  try {
899
- const res = await fetch('http://localhost:${PORT}/api/apply', {
813
+ const res = await fetch('/api/apply', {
900
814
  method: 'POST',
901
815
  headers: {'Content-Type': 'application/json'},
902
816
  body: JSON.stringify({ filePaths: Array.from(this.selectedFiles), llmResponse: this.llmResponse })
package/index.ts CHANGED
@@ -483,7 +483,7 @@ class RoomState {
483
483
  }
484
484
  }
485
485
 
486
- program.version("0.0.29").description("Hapico CLI for project management");
486
+ program.version("0.0.30").description("Hapico CLI for project management");
487
487
 
488
488
  program
489
489
  .command("clone <id>")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hapico/cli",
3
- "version": "0.0.29",
3
+ "version": "0.0.30",
4
4
  "description": "A simple CLI tool for project management",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -16,7 +16,8 @@ const scanRecursively = async (
16
16
 
17
17
  for (const entry of list) {
18
18
  const fullPath = path.join(dir, entry.name);
19
- const relativePath = path.relative(baseDir, fullPath);
19
+ // Chuẩn hóa đường dẫn về forward slash để nhất quán giữa các hệ điều hành
20
+ const relativePath = path.relative(baseDir, fullPath).split(path.sep).join('/');
20
21
 
21
22
  if (shouldIgnorePath(relativePath)) continue;
22
23
 
@@ -662,7 +663,8 @@ const startServer = async (rootDir: string) => {
662
663
 
663
664
  async init() {
664
665
  try {
665
- const res = await fetch('http://localhost:${PORT}/api/files');
666
+ // Sử dụng relative URL để tránh lỗi phân giải localhost/127.0.0.1 trên Windows
667
+ const res = await fetch('/api/files');
666
668
  const data = await res.json();
667
669
  this.rawFiles = data;
668
670
  this.buildTree();
@@ -672,98 +674,10 @@ const startServer = async (rootDir: string) => {
672
674
  this.loading = false;
673
675
  }
674
676
 
675
- // Search watcher
676
677
  this.$watch('search', () => this.buildTree());
677
-
678
- // Auto-generate prompt
679
678
  this.$watch('instruction', () => this.debouncedFetchPrompt());
680
679
  this.$watch('selectedFiles', () => this.debouncedFetchPrompt());
681
680
  },
682
-
683
- // ===========================
684
- // Tree Logic
685
- // ===========================
686
-
687
- buildTree() {
688
- // 1. Convert paths to object structure
689
- const root = {};
690
- const searchLower = this.search.toLowerCase();
691
-
692
- this.rawFiles.forEach(file => {
693
- if (this.search && !file.path.toLowerCase().includes(searchLower)) return;
694
-
695
- const parts = file.path.split(/[\\\\/]/);
696
- let current = root;
697
-
698
- parts.forEach((part, index) => {
699
- if (!current[part]) {
700
- current[part] = {
701
- name: part,
702
- path: parts.slice(0, index + 1).join('/'),
703
- children: {},
704
- type: index === parts.length - 1 ? 'file' : 'folder',
705
- fullPath: file.path // Only meaningful for file
706
- };
707
- }
708
- current = current[part].children;
709
- });
710
- });
711
-
712
- // 2. Flatten object structure into renderable list with depth
713
- this.treeNodes = [];
714
- const traverse = (nodeMap, depth) => {
715
- // Sort: Folders first, then Files. Alphabetical.
716
- const keys = Object.keys(nodeMap).sort((a, b) => {
717
- const nodeA = nodeMap[a];
718
- const nodeB = nodeMap[b];
719
- if (nodeA.type !== nodeB.type) return nodeA.type === 'folder' ? -1 : 1;
720
- return a.localeCompare(b);
721
- });
722
-
723
- keys.forEach(key => {
724
- const node = nodeMap[key];
725
- const flatNode = {
726
- id: node.path,
727
- name: node.name,
728
- path: node.path, // Relative path for selection
729
- type: node.type,
730
- depth: depth,
731
- children: Object.keys(node.children).length > 0, // boolean for UI
732
- expanded: this.search.length > 0 || depth < 1, // Auto expand on search or root
733
- rawChildren: node.children // Keep ref for recursion
734
- };
735
-
736
- this.treeNodes.push(flatNode);
737
- if (flatNode.children && flatNode.expanded) {
738
- traverse(node.children, depth + 1);
739
- }
740
- });
741
- };
742
-
743
- traverse(root, 0);
744
- },
745
-
746
- // Re-calculate visible nodes (folding logic) without rebuilding everything
747
- // But for simplicity in this SFC, we just rebuild the list when expanded changes.
748
- toggleExpand(node) {
749
- if (node.type !== 'folder') return;
750
-
751
- // Find node in treeNodes and toggle
752
- // Since treeNodes is flat and generated, we need to persist state or smarter logic.
753
- // Simpler: Just modify the expanded state in the current flat list is tricky because children are dynamic.
754
- // Better approach for this lightweight UI:
755
- // We actually need a persistent "expanded" Set to survive re-renders if we want to be fancy.
756
- // For now, let's just cheat: Update the boolean in the flat list works for collapsing,
757
- // but expanding requires knowing the children.
758
-
759
- // Let's reload the tree logic but using a \`expandedPaths\` set.
760
- if (this.expandedPaths.has(node.path)) {
761
- this.expandedPaths.delete(node.path);
762
- } else {
763
- this.expandedPaths.add(node.path);
764
- }
765
- this.refreshTreeVisibility();
766
- },
767
681
 
768
682
  // Alternative: Just use a computed property for \`visibleNodes\`.
769
683
  // Since Alpine isn't React, we do this manually.
@@ -791,7 +705,8 @@ const startServer = async (rootDir: string) => {
791
705
 
792
706
  this.rawFiles.forEach(file => {
793
707
  if (this.search && !file.path.toLowerCase().includes(searchLower)) return;
794
- const parts = file.path.split(/[\\\\/]/);
708
+ // File path đã được chuẩn hóa '/' từ backend
709
+ const parts = file.path.split('/');
795
710
  let current = this.rootObject;
796
711
  parts.forEach((part, index) => {
797
712
  if (!current[part]) {
@@ -892,9 +807,8 @@ const startServer = async (rootDir: string) => {
892
807
  },
893
808
 
894
809
  getAllDescendants(folderPath) {
895
- // Simple filter from rawFiles
896
810
  return this.rawFiles
897
- .filter(f => f.path.startsWith(folderPath + '/') || f.path.startsWith(folderPath + '\\\\'))
811
+ .filter(f => f.path === folderPath || f.path.startsWith(folderPath + '/'))
898
812
  .map(f => f.path);
899
813
  },
900
814
 
@@ -927,7 +841,7 @@ const startServer = async (rootDir: string) => {
927
841
  }
928
842
  this.generating = true;
929
843
  try {
930
- const res = await fetch('http://localhost:${PORT}/api/prompt', {
844
+ const res = await fetch('/api/prompt', {
931
845
  method: 'POST',
932
846
  headers: {'Content-Type': 'application/json'},
933
847
  body: JSON.stringify({ filePaths: Array.from(this.selectedFiles), instruction: this.instruction })
@@ -954,7 +868,7 @@ const startServer = async (rootDir: string) => {
954
868
  async applyPatch() {
955
869
  this.applying = true;
956
870
  try {
957
- const res = await fetch('http://localhost:${PORT}/api/apply', {
871
+ const res = await fetch('/api/apply', {
958
872
  method: 'POST',
959
873
  headers: {'Content-Type': 'application/json'},
960
874
  body: JSON.stringify({ filePaths: Array.from(this.selectedFiles), llmResponse: this.llmResponse })