fasterdev 0.1.5 → 0.1.8
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/README.md +64 -138
- package/dist/cli.js +778 -84
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -91,6 +91,13 @@ var FasterAPI = class {
|
|
|
91
91
|
if (options?.tool) params.set("tool", options.tool);
|
|
92
92
|
return this.request(`/packages/search?${params}`);
|
|
93
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* Get all packages in a scope (e.g., "audit" returns all @audit/* packages)
|
|
96
|
+
*/
|
|
97
|
+
async getPackagesByScope(scope) {
|
|
98
|
+
const params = new URLSearchParams({ scope });
|
|
99
|
+
return this.request(`/packages/search?${params}`);
|
|
100
|
+
}
|
|
94
101
|
/**
|
|
95
102
|
* Get package info
|
|
96
103
|
*/
|
|
@@ -134,12 +141,15 @@ var FasterAPI = class {
|
|
|
134
141
|
|
|
135
142
|
// src/config.ts
|
|
136
143
|
import Conf from "conf";
|
|
144
|
+
import { homedir } from "os";
|
|
145
|
+
import { join } from "path";
|
|
137
146
|
var DEFAULT_CONFIG = {
|
|
138
147
|
apiUrl: "https://faster.dev/api/v1",
|
|
139
148
|
analytics: true
|
|
140
149
|
};
|
|
141
150
|
var store = new Conf({
|
|
142
|
-
projectName: "faster",
|
|
151
|
+
projectName: "faster-dev",
|
|
152
|
+
cwd: join(homedir(), ".faster-dev"),
|
|
143
153
|
defaults: DEFAULT_CONFIG
|
|
144
154
|
});
|
|
145
155
|
function getConfig() {
|
|
@@ -177,6 +187,7 @@ function getConfigPath() {
|
|
|
177
187
|
|
|
178
188
|
// src/utils.ts
|
|
179
189
|
import { spawn } from "child_process";
|
|
190
|
+
import * as readline from "readline";
|
|
180
191
|
function parsePackageSpec(input) {
|
|
181
192
|
if (input.startsWith("@")) {
|
|
182
193
|
const match2 = input.match(/^(@[^/]+\/[^@]+)(?:@(.+))?$/);
|
|
@@ -191,6 +202,10 @@ function parsePackageSpec(input) {
|
|
|
191
202
|
}
|
|
192
203
|
return { name: input };
|
|
193
204
|
}
|
|
205
|
+
function getScopeFromInput(input) {
|
|
206
|
+
const match = input.match(/^@([^/@]+)$/);
|
|
207
|
+
return match ? match[1] : null;
|
|
208
|
+
}
|
|
194
209
|
function resolveInstallType(asSkill) {
|
|
195
210
|
return asSkill ? "skill" : "rule";
|
|
196
211
|
}
|
|
@@ -221,6 +236,18 @@ function openBrowser(url) {
|
|
|
221
236
|
return false;
|
|
222
237
|
}
|
|
223
238
|
}
|
|
239
|
+
async function confirm(message) {
|
|
240
|
+
const rl = readline.createInterface({
|
|
241
|
+
input: process.stdin,
|
|
242
|
+
output: process.stdout
|
|
243
|
+
});
|
|
244
|
+
return new Promise((resolve) => {
|
|
245
|
+
rl.question(`${message} (y/N) `, (answer) => {
|
|
246
|
+
rl.close();
|
|
247
|
+
resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
}
|
|
224
251
|
|
|
225
252
|
// src/lib/command-utils.ts
|
|
226
253
|
import chalk from "chalk";
|
|
@@ -371,7 +398,7 @@ function registerWhoamiCommand(program2) {
|
|
|
371
398
|
outputJson({ authenticated: false });
|
|
372
399
|
} else {
|
|
373
400
|
console.log(chalk4.yellow("Not logged in"));
|
|
374
|
-
console.log(chalk4.dim("Run `
|
|
401
|
+
console.log(chalk4.dim("Run `fasterdev login` to authenticate"));
|
|
375
402
|
}
|
|
376
403
|
setExitCode(EXIT_CODES.AUTH_REQUIRED);
|
|
377
404
|
return;
|
|
@@ -634,8 +661,8 @@ var TOOL_CONFIGS = {
|
|
|
634
661
|
id: "amp",
|
|
635
662
|
name: "Amp (Sourcegraph)",
|
|
636
663
|
detect: {
|
|
637
|
-
projectDirs: [".amp", ".claude"],
|
|
638
|
-
globalDirs: [path.join(home, ".config", "amp"), path.join(home, ".
|
|
664
|
+
projectDirs: [".agents", ".amp", ".claude"],
|
|
665
|
+
globalDirs: [path.join(home, ".config", "amp"), path.join(home, ".config", "agents")],
|
|
639
666
|
configFiles: ["AGENTS.md", "AGENT.md"]
|
|
640
667
|
},
|
|
641
668
|
rules: {
|
|
@@ -645,8 +672,8 @@ var TOOL_CONFIGS = {
|
|
|
645
672
|
fileExtension: ".md"
|
|
646
673
|
},
|
|
647
674
|
skills: {
|
|
648
|
-
projectPath: ".
|
|
649
|
-
globalPath: path.join(home, ".
|
|
675
|
+
projectPath: ".agents/skills",
|
|
676
|
+
globalPath: path.join(home, ".config", "agents", "skills")
|
|
650
677
|
}
|
|
651
678
|
},
|
|
652
679
|
opencode: {
|
|
@@ -667,6 +694,387 @@ var TOOL_CONFIGS = {
|
|
|
667
694
|
projectPath: ".opencode/skill",
|
|
668
695
|
globalPath: path.join(home, ".config", "opencode", "skill")
|
|
669
696
|
}
|
|
697
|
+
},
|
|
698
|
+
antigravity: {
|
|
699
|
+
id: "antigravity",
|
|
700
|
+
name: "Antigravity",
|
|
701
|
+
detect: {
|
|
702
|
+
projectDirs: [".agent"],
|
|
703
|
+
globalDirs: [path.join(home, ".gemini", "antigravity")],
|
|
704
|
+
configFiles: []
|
|
705
|
+
},
|
|
706
|
+
rules: {
|
|
707
|
+
projectPath: ".agent/rules",
|
|
708
|
+
globalPath: path.join(home, ".gemini", "antigravity", "rules"),
|
|
709
|
+
format: "markdown",
|
|
710
|
+
fileExtension: ".md"
|
|
711
|
+
},
|
|
712
|
+
skills: {
|
|
713
|
+
projectPath: ".agent/skills",
|
|
714
|
+
globalPath: path.join(home, ".gemini", "antigravity", "skills")
|
|
715
|
+
}
|
|
716
|
+
},
|
|
717
|
+
// New agents from Vercel Skills
|
|
718
|
+
windsurf: {
|
|
719
|
+
id: "windsurf",
|
|
720
|
+
name: "Windsurf",
|
|
721
|
+
detect: {
|
|
722
|
+
projectDirs: [".windsurf"],
|
|
723
|
+
globalDirs: [path.join(home, ".codeium", "windsurf")],
|
|
724
|
+
configFiles: []
|
|
725
|
+
},
|
|
726
|
+
rules: {
|
|
727
|
+
projectPath: ".windsurf/rules",
|
|
728
|
+
globalPath: path.join(home, ".codeium", "windsurf", "rules"),
|
|
729
|
+
format: "markdown",
|
|
730
|
+
fileExtension: ".md"
|
|
731
|
+
},
|
|
732
|
+
skills: {
|
|
733
|
+
projectPath: ".windsurf/skills",
|
|
734
|
+
globalPath: path.join(home, ".codeium", "windsurf", "skills")
|
|
735
|
+
}
|
|
736
|
+
},
|
|
737
|
+
"github-copilot": {
|
|
738
|
+
id: "github-copilot",
|
|
739
|
+
name: "GitHub Copilot",
|
|
740
|
+
detect: {
|
|
741
|
+
projectDirs: [".github"],
|
|
742
|
+
globalDirs: [path.join(home, ".copilot")],
|
|
743
|
+
configFiles: [".github/copilot-instructions.md"]
|
|
744
|
+
},
|
|
745
|
+
rules: {
|
|
746
|
+
projectPath: ".github/rules",
|
|
747
|
+
globalPath: path.join(home, ".copilot", "rules"),
|
|
748
|
+
format: "markdown",
|
|
749
|
+
fileExtension: ".md"
|
|
750
|
+
},
|
|
751
|
+
skills: {
|
|
752
|
+
projectPath: ".github/skills",
|
|
753
|
+
globalPath: path.join(home, ".copilot", "skills")
|
|
754
|
+
}
|
|
755
|
+
},
|
|
756
|
+
goose: {
|
|
757
|
+
id: "goose",
|
|
758
|
+
name: "Goose",
|
|
759
|
+
detect: {
|
|
760
|
+
projectDirs: [".goose"],
|
|
761
|
+
globalDirs: [path.join(home, ".config", "goose")],
|
|
762
|
+
configFiles: []
|
|
763
|
+
},
|
|
764
|
+
rules: {
|
|
765
|
+
projectPath: ".goose/rules",
|
|
766
|
+
globalPath: path.join(home, ".config", "goose", "rules"),
|
|
767
|
+
format: "markdown",
|
|
768
|
+
fileExtension: ".md"
|
|
769
|
+
},
|
|
770
|
+
skills: {
|
|
771
|
+
projectPath: ".goose/skills",
|
|
772
|
+
globalPath: path.join(home, ".config", "goose", "skills")
|
|
773
|
+
}
|
|
774
|
+
},
|
|
775
|
+
kilo: {
|
|
776
|
+
id: "kilo",
|
|
777
|
+
name: "Kilo Code",
|
|
778
|
+
detect: {
|
|
779
|
+
projectDirs: [".kilocode"],
|
|
780
|
+
globalDirs: [path.join(home, ".kilocode")],
|
|
781
|
+
configFiles: []
|
|
782
|
+
},
|
|
783
|
+
rules: {
|
|
784
|
+
projectPath: ".kilocode/rules",
|
|
785
|
+
globalPath: path.join(home, ".kilocode", "rules"),
|
|
786
|
+
format: "markdown",
|
|
787
|
+
fileExtension: ".md"
|
|
788
|
+
},
|
|
789
|
+
skills: {
|
|
790
|
+
projectPath: ".kilocode/skills",
|
|
791
|
+
globalPath: path.join(home, ".kilocode", "skills")
|
|
792
|
+
}
|
|
793
|
+
},
|
|
794
|
+
kiro: {
|
|
795
|
+
id: "kiro",
|
|
796
|
+
name: "Kiro CLI",
|
|
797
|
+
detect: {
|
|
798
|
+
projectDirs: [".kiro"],
|
|
799
|
+
globalDirs: [path.join(home, ".kiro")],
|
|
800
|
+
configFiles: []
|
|
801
|
+
},
|
|
802
|
+
rules: {
|
|
803
|
+
projectPath: ".kiro/rules",
|
|
804
|
+
globalPath: path.join(home, ".kiro", "rules"),
|
|
805
|
+
format: "markdown",
|
|
806
|
+
fileExtension: ".md"
|
|
807
|
+
},
|
|
808
|
+
skills: {
|
|
809
|
+
projectPath: ".kiro/skills",
|
|
810
|
+
globalPath: path.join(home, ".kiro", "skills")
|
|
811
|
+
}
|
|
812
|
+
},
|
|
813
|
+
qwen: {
|
|
814
|
+
id: "qwen",
|
|
815
|
+
name: "Qwen Code",
|
|
816
|
+
detect: {
|
|
817
|
+
projectDirs: [".qwen"],
|
|
818
|
+
globalDirs: [path.join(home, ".qwen")],
|
|
819
|
+
configFiles: []
|
|
820
|
+
},
|
|
821
|
+
rules: {
|
|
822
|
+
projectPath: ".qwen/rules",
|
|
823
|
+
globalPath: path.join(home, ".qwen", "rules"),
|
|
824
|
+
format: "markdown",
|
|
825
|
+
fileExtension: ".md"
|
|
826
|
+
},
|
|
827
|
+
skills: {
|
|
828
|
+
projectPath: ".qwen/skills",
|
|
829
|
+
globalPath: path.join(home, ".qwen", "skills")
|
|
830
|
+
}
|
|
831
|
+
},
|
|
832
|
+
trae: {
|
|
833
|
+
id: "trae",
|
|
834
|
+
name: "Trae",
|
|
835
|
+
detect: {
|
|
836
|
+
projectDirs: [".trae"],
|
|
837
|
+
globalDirs: [path.join(home, ".trae")],
|
|
838
|
+
configFiles: []
|
|
839
|
+
},
|
|
840
|
+
rules: {
|
|
841
|
+
projectPath: ".trae/rules",
|
|
842
|
+
globalPath: path.join(home, ".trae", "rules"),
|
|
843
|
+
format: "markdown",
|
|
844
|
+
fileExtension: ".md"
|
|
845
|
+
},
|
|
846
|
+
skills: {
|
|
847
|
+
projectPath: ".trae/skills",
|
|
848
|
+
globalPath: path.join(home, ".trae", "skills")
|
|
849
|
+
}
|
|
850
|
+
},
|
|
851
|
+
crush: {
|
|
852
|
+
id: "crush",
|
|
853
|
+
name: "Crush",
|
|
854
|
+
detect: {
|
|
855
|
+
projectDirs: [".crush"],
|
|
856
|
+
globalDirs: [path.join(home, ".config", "crush")],
|
|
857
|
+
configFiles: []
|
|
858
|
+
},
|
|
859
|
+
rules: {
|
|
860
|
+
projectPath: ".crush/rules",
|
|
861
|
+
globalPath: path.join(home, ".config", "crush", "rules"),
|
|
862
|
+
format: "markdown",
|
|
863
|
+
fileExtension: ".md"
|
|
864
|
+
},
|
|
865
|
+
skills: {
|
|
866
|
+
projectPath: ".crush/skills",
|
|
867
|
+
globalPath: path.join(home, ".config", "crush", "skills")
|
|
868
|
+
}
|
|
869
|
+
},
|
|
870
|
+
droid: {
|
|
871
|
+
id: "droid",
|
|
872
|
+
name: "Droid",
|
|
873
|
+
detect: {
|
|
874
|
+
projectDirs: [".factory"],
|
|
875
|
+
globalDirs: [path.join(home, ".factory")],
|
|
876
|
+
configFiles: []
|
|
877
|
+
},
|
|
878
|
+
rules: {
|
|
879
|
+
projectPath: ".factory/rules",
|
|
880
|
+
globalPath: path.join(home, ".factory", "rules"),
|
|
881
|
+
format: "markdown",
|
|
882
|
+
fileExtension: ".md"
|
|
883
|
+
},
|
|
884
|
+
skills: {
|
|
885
|
+
projectPath: ".factory/skills",
|
|
886
|
+
globalPath: path.join(home, ".factory", "skills")
|
|
887
|
+
}
|
|
888
|
+
},
|
|
889
|
+
mcpjam: {
|
|
890
|
+
id: "mcpjam",
|
|
891
|
+
name: "MCPJam",
|
|
892
|
+
detect: {
|
|
893
|
+
projectDirs: [".mcpjam"],
|
|
894
|
+
globalDirs: [path.join(home, ".mcpjam")],
|
|
895
|
+
configFiles: []
|
|
896
|
+
},
|
|
897
|
+
rules: {
|
|
898
|
+
projectPath: ".mcpjam/rules",
|
|
899
|
+
globalPath: path.join(home, ".mcpjam", "rules"),
|
|
900
|
+
format: "markdown",
|
|
901
|
+
fileExtension: ".md"
|
|
902
|
+
},
|
|
903
|
+
skills: {
|
|
904
|
+
projectPath: ".mcpjam/skills",
|
|
905
|
+
globalPath: path.join(home, ".mcpjam", "skills")
|
|
906
|
+
}
|
|
907
|
+
},
|
|
908
|
+
mux: {
|
|
909
|
+
id: "mux",
|
|
910
|
+
name: "Mux",
|
|
911
|
+
detect: {
|
|
912
|
+
projectDirs: [".mux"],
|
|
913
|
+
globalDirs: [path.join(home, ".mux")],
|
|
914
|
+
configFiles: []
|
|
915
|
+
},
|
|
916
|
+
rules: {
|
|
917
|
+
projectPath: ".mux/rules",
|
|
918
|
+
globalPath: path.join(home, ".mux", "rules"),
|
|
919
|
+
format: "markdown",
|
|
920
|
+
fileExtension: ".md"
|
|
921
|
+
},
|
|
922
|
+
skills: {
|
|
923
|
+
projectPath: ".mux/skills",
|
|
924
|
+
globalPath: path.join(home, ".mux", "skills")
|
|
925
|
+
}
|
|
926
|
+
},
|
|
927
|
+
openhands: {
|
|
928
|
+
id: "openhands",
|
|
929
|
+
name: "OpenHands",
|
|
930
|
+
detect: {
|
|
931
|
+
projectDirs: [".openhands"],
|
|
932
|
+
globalDirs: [path.join(home, ".openhands")],
|
|
933
|
+
configFiles: []
|
|
934
|
+
},
|
|
935
|
+
rules: {
|
|
936
|
+
projectPath: ".openhands/rules",
|
|
937
|
+
globalPath: path.join(home, ".openhands", "rules"),
|
|
938
|
+
format: "markdown",
|
|
939
|
+
fileExtension: ".md"
|
|
940
|
+
},
|
|
941
|
+
skills: {
|
|
942
|
+
projectPath: ".openhands/skills",
|
|
943
|
+
globalPath: path.join(home, ".openhands", "skills")
|
|
944
|
+
}
|
|
945
|
+
},
|
|
946
|
+
pi: {
|
|
947
|
+
id: "pi",
|
|
948
|
+
name: "Pi",
|
|
949
|
+
detect: {
|
|
950
|
+
projectDirs: [".pi"],
|
|
951
|
+
globalDirs: [path.join(home, ".pi", "agent")],
|
|
952
|
+
configFiles: []
|
|
953
|
+
},
|
|
954
|
+
rules: {
|
|
955
|
+
projectPath: ".pi/rules",
|
|
956
|
+
globalPath: path.join(home, ".pi", "agent", "rules"),
|
|
957
|
+
format: "markdown",
|
|
958
|
+
fileExtension: ".md"
|
|
959
|
+
},
|
|
960
|
+
skills: {
|
|
961
|
+
projectPath: ".pi/skills",
|
|
962
|
+
globalPath: path.join(home, ".pi", "agent", "skills")
|
|
963
|
+
}
|
|
964
|
+
},
|
|
965
|
+
qoder: {
|
|
966
|
+
id: "qoder",
|
|
967
|
+
name: "Qoder",
|
|
968
|
+
detect: {
|
|
969
|
+
projectDirs: [".qoder"],
|
|
970
|
+
globalDirs: [path.join(home, ".qoder")],
|
|
971
|
+
configFiles: []
|
|
972
|
+
},
|
|
973
|
+
rules: {
|
|
974
|
+
projectPath: ".qoder/rules",
|
|
975
|
+
globalPath: path.join(home, ".qoder", "rules"),
|
|
976
|
+
format: "markdown",
|
|
977
|
+
fileExtension: ".md"
|
|
978
|
+
},
|
|
979
|
+
skills: {
|
|
980
|
+
projectPath: ".qoder/skills",
|
|
981
|
+
globalPath: path.join(home, ".qoder", "skills")
|
|
982
|
+
}
|
|
983
|
+
},
|
|
984
|
+
clawdbot: {
|
|
985
|
+
id: "clawdbot",
|
|
986
|
+
name: "Clawdbot",
|
|
987
|
+
detect: {
|
|
988
|
+
projectDirs: ["skills"],
|
|
989
|
+
globalDirs: [path.join(home, ".clawdbot")],
|
|
990
|
+
configFiles: []
|
|
991
|
+
},
|
|
992
|
+
rules: {
|
|
993
|
+
projectPath: "skills",
|
|
994
|
+
globalPath: path.join(home, ".clawdbot", "skills"),
|
|
995
|
+
format: "markdown",
|
|
996
|
+
fileExtension: ".md"
|
|
997
|
+
},
|
|
998
|
+
skills: {
|
|
999
|
+
projectPath: "skills",
|
|
1000
|
+
globalPath: path.join(home, ".clawdbot", "skills")
|
|
1001
|
+
}
|
|
1002
|
+
},
|
|
1003
|
+
codebuddy: {
|
|
1004
|
+
id: "codebuddy",
|
|
1005
|
+
name: "CodeBuddy",
|
|
1006
|
+
detect: {
|
|
1007
|
+
projectDirs: [".codebuddy"],
|
|
1008
|
+
globalDirs: [path.join(home, ".codebuddy")],
|
|
1009
|
+
configFiles: []
|
|
1010
|
+
},
|
|
1011
|
+
rules: {
|
|
1012
|
+
projectPath: ".codebuddy/rules",
|
|
1013
|
+
globalPath: path.join(home, ".codebuddy", "rules"),
|
|
1014
|
+
format: "markdown",
|
|
1015
|
+
fileExtension: ".md"
|
|
1016
|
+
},
|
|
1017
|
+
skills: {
|
|
1018
|
+
projectPath: ".codebuddy/skills",
|
|
1019
|
+
globalPath: path.join(home, ".codebuddy", "skills")
|
|
1020
|
+
}
|
|
1021
|
+
},
|
|
1022
|
+
"command-code": {
|
|
1023
|
+
id: "command-code",
|
|
1024
|
+
name: "Command Code",
|
|
1025
|
+
detect: {
|
|
1026
|
+
projectDirs: [".commandcode"],
|
|
1027
|
+
globalDirs: [path.join(home, ".commandcode")],
|
|
1028
|
+
configFiles: []
|
|
1029
|
+
},
|
|
1030
|
+
rules: {
|
|
1031
|
+
projectPath: ".commandcode/rules",
|
|
1032
|
+
globalPath: path.join(home, ".commandcode", "rules"),
|
|
1033
|
+
format: "markdown",
|
|
1034
|
+
fileExtension: ".md"
|
|
1035
|
+
},
|
|
1036
|
+
skills: {
|
|
1037
|
+
projectPath: ".commandcode/skills",
|
|
1038
|
+
globalPath: path.join(home, ".commandcode", "skills")
|
|
1039
|
+
}
|
|
1040
|
+
},
|
|
1041
|
+
zencoder: {
|
|
1042
|
+
id: "zencoder",
|
|
1043
|
+
name: "Zencoder",
|
|
1044
|
+
detect: {
|
|
1045
|
+
projectDirs: [".zencoder"],
|
|
1046
|
+
globalDirs: [path.join(home, ".zencoder")],
|
|
1047
|
+
configFiles: []
|
|
1048
|
+
},
|
|
1049
|
+
rules: {
|
|
1050
|
+
projectPath: ".zencoder/rules",
|
|
1051
|
+
globalPath: path.join(home, ".zencoder", "rules"),
|
|
1052
|
+
format: "markdown",
|
|
1053
|
+
fileExtension: ".md"
|
|
1054
|
+
},
|
|
1055
|
+
skills: {
|
|
1056
|
+
projectPath: ".zencoder/skills",
|
|
1057
|
+
globalPath: path.join(home, ".zencoder", "skills")
|
|
1058
|
+
}
|
|
1059
|
+
},
|
|
1060
|
+
neovate: {
|
|
1061
|
+
id: "neovate",
|
|
1062
|
+
name: "Neovate",
|
|
1063
|
+
detect: {
|
|
1064
|
+
projectDirs: [".neovate"],
|
|
1065
|
+
globalDirs: [path.join(home, ".neovate")],
|
|
1066
|
+
configFiles: []
|
|
1067
|
+
},
|
|
1068
|
+
rules: {
|
|
1069
|
+
projectPath: ".neovate/rules",
|
|
1070
|
+
globalPath: path.join(home, ".neovate", "rules"),
|
|
1071
|
+
format: "markdown",
|
|
1072
|
+
fileExtension: ".md"
|
|
1073
|
+
},
|
|
1074
|
+
skills: {
|
|
1075
|
+
projectPath: ".neovate/skills",
|
|
1076
|
+
globalPath: path.join(home, ".neovate", "skills")
|
|
1077
|
+
}
|
|
670
1078
|
}
|
|
671
1079
|
};
|
|
672
1080
|
var RULE_TOOLS = Object.keys(TOOL_CONFIGS);
|
|
@@ -674,13 +1082,33 @@ var DEFAULT_TOOL_PRIORITY = [
|
|
|
674
1082
|
"claude-code",
|
|
675
1083
|
"cursor",
|
|
676
1084
|
"codex",
|
|
1085
|
+
"windsurf",
|
|
1086
|
+
"github-copilot",
|
|
677
1087
|
"cline",
|
|
678
1088
|
"roo-code",
|
|
679
1089
|
"continue",
|
|
680
1090
|
"aider",
|
|
681
1091
|
"gemini",
|
|
682
1092
|
"amp",
|
|
683
|
-
"opencode"
|
|
1093
|
+
"opencode",
|
|
1094
|
+
"antigravity",
|
|
1095
|
+
"goose",
|
|
1096
|
+
"kilo",
|
|
1097
|
+
"kiro",
|
|
1098
|
+
"qwen",
|
|
1099
|
+
"trae",
|
|
1100
|
+
"crush",
|
|
1101
|
+
"droid",
|
|
1102
|
+
"mcpjam",
|
|
1103
|
+
"mux",
|
|
1104
|
+
"openhands",
|
|
1105
|
+
"pi",
|
|
1106
|
+
"qoder",
|
|
1107
|
+
"clawdbot",
|
|
1108
|
+
"codebuddy",
|
|
1109
|
+
"command-code",
|
|
1110
|
+
"zencoder",
|
|
1111
|
+
"neovate"
|
|
684
1112
|
];
|
|
685
1113
|
|
|
686
1114
|
// src/detector.ts
|
|
@@ -794,8 +1222,8 @@ function registerDiscoveryCommands(program2) {
|
|
|
794
1222
|
import chalk8 from "chalk";
|
|
795
1223
|
|
|
796
1224
|
// src/installer.ts
|
|
797
|
-
import
|
|
798
|
-
import
|
|
1225
|
+
import fs3 from "fs/promises";
|
|
1226
|
+
import path4 from "path";
|
|
799
1227
|
|
|
800
1228
|
// src/converter.ts
|
|
801
1229
|
import YAML from "yaml";
|
|
@@ -896,10 +1324,18 @@ function convertToToolFormat(content, toolConfig, packageName) {
|
|
|
896
1324
|
}
|
|
897
1325
|
}
|
|
898
1326
|
|
|
899
|
-
// src/
|
|
900
|
-
import
|
|
901
|
-
|
|
902
|
-
|
|
1327
|
+
// src/symlinker.ts
|
|
1328
|
+
import fs2 from "fs/promises";
|
|
1329
|
+
import path3 from "path";
|
|
1330
|
+
import os2 from "os";
|
|
1331
|
+
var CANONICAL_DIR = path3.join(os2.homedir(), ".faster-dev", "packages");
|
|
1332
|
+
async function isSymlink(filePath) {
|
|
1333
|
+
try {
|
|
1334
|
+
const stats = await fs2.lstat(filePath);
|
|
1335
|
+
return stats.isSymbolicLink();
|
|
1336
|
+
} catch {
|
|
1337
|
+
return false;
|
|
1338
|
+
}
|
|
903
1339
|
}
|
|
904
1340
|
async function fileExists(p) {
|
|
905
1341
|
try {
|
|
@@ -909,9 +1345,106 @@ async function fileExists(p) {
|
|
|
909
1345
|
return false;
|
|
910
1346
|
}
|
|
911
1347
|
}
|
|
1348
|
+
async function ensureDir(dir) {
|
|
1349
|
+
await fs2.mkdir(dir, { recursive: true });
|
|
1350
|
+
}
|
|
1351
|
+
async function writeCanonicalPackage(packageName, originalContent, toolConfigs) {
|
|
1352
|
+
const packageDir = path3.join(CANONICAL_DIR, packageName);
|
|
1353
|
+
await ensureDir(packageDir);
|
|
1354
|
+
const files = /* @__PURE__ */ new Map();
|
|
1355
|
+
const originalPath = path3.join(packageDir, "original.md");
|
|
1356
|
+
await fs2.writeFile(originalPath, originalContent, "utf-8");
|
|
1357
|
+
files.set("original.md", originalPath);
|
|
1358
|
+
for (const config of toolConfigs) {
|
|
1359
|
+
const ext = config.rules.fileExtension;
|
|
1360
|
+
const filename = `rule-${config.id}${ext}`;
|
|
1361
|
+
const converted = convertToToolFormat(originalContent, config, packageName);
|
|
1362
|
+
const filePath = path3.join(packageDir, filename);
|
|
1363
|
+
await fs2.writeFile(filePath, converted, "utf-8");
|
|
1364
|
+
files.set(filename, filePath);
|
|
1365
|
+
}
|
|
1366
|
+
return { packageDir, files };
|
|
1367
|
+
}
|
|
1368
|
+
function getCanonicalFilePath(packageName, toolConfig) {
|
|
1369
|
+
const ext = toolConfig.rules.fileExtension;
|
|
1370
|
+
return path3.join(CANONICAL_DIR, packageName, `rule-${toolConfig.id}${ext}`);
|
|
1371
|
+
}
|
|
1372
|
+
async function createSymlink(canonicalPath, targetPath, options = {}) {
|
|
1373
|
+
if (await fileExists(targetPath)) {
|
|
1374
|
+
if (await isSymlink(targetPath)) {
|
|
1375
|
+
const existingTarget = await fs2.readlink(targetPath);
|
|
1376
|
+
if (existingTarget === canonicalPath) {
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
if (!options.force) {
|
|
1381
|
+
throw new Error(`File already exists at ${targetPath}`);
|
|
1382
|
+
}
|
|
1383
|
+
await fs2.unlink(targetPath);
|
|
1384
|
+
}
|
|
1385
|
+
await ensureDir(path3.dirname(targetPath));
|
|
1386
|
+
await fs2.symlink(canonicalPath, targetPath);
|
|
1387
|
+
}
|
|
1388
|
+
async function installWithSymlink(packageName, content, toolConfig, targetDir, options = {}) {
|
|
1389
|
+
await writeCanonicalPackage(packageName, content, [toolConfig]);
|
|
1390
|
+
const canonicalPath = getCanonicalFilePath(packageName, toolConfig);
|
|
1391
|
+
const filename = `${packageName}${toolConfig.rules.fileExtension}`;
|
|
1392
|
+
const targetPath = path3.join(targetDir, filename);
|
|
1393
|
+
await createSymlink(canonicalPath, targetPath, options);
|
|
1394
|
+
return targetPath;
|
|
1395
|
+
}
|
|
1396
|
+
async function installWithCopy(packageName, content, toolConfig, targetDir, options = {}) {
|
|
1397
|
+
const filename = `${packageName}${toolConfig.rules.fileExtension}`;
|
|
1398
|
+
const targetPath = path3.join(targetDir, filename);
|
|
1399
|
+
if (await fileExists(targetPath) && !options.force) {
|
|
1400
|
+
throw new Error(`File already exists at ${targetPath}`);
|
|
1401
|
+
}
|
|
1402
|
+
const converted = convertToToolFormat(content, toolConfig, packageName);
|
|
1403
|
+
await ensureDir(targetDir);
|
|
1404
|
+
await fs2.writeFile(targetPath, converted, "utf-8");
|
|
1405
|
+
return targetPath;
|
|
1406
|
+
}
|
|
1407
|
+
async function symlinkSupported() {
|
|
1408
|
+
const testDir = path3.join(CANONICAL_DIR, ".symlink-test");
|
|
1409
|
+
const testSource = path3.join(testDir, "source.txt");
|
|
1410
|
+
const testLink = path3.join(testDir, "link.txt");
|
|
1411
|
+
try {
|
|
1412
|
+
await ensureDir(testDir);
|
|
1413
|
+
await fs2.writeFile(testSource, "test", "utf-8");
|
|
1414
|
+
await fs2.symlink(testSource, testLink);
|
|
1415
|
+
await fs2.unlink(testLink);
|
|
1416
|
+
await fs2.unlink(testSource);
|
|
1417
|
+
await fs2.rmdir(testDir);
|
|
1418
|
+
return true;
|
|
1419
|
+
} catch (error) {
|
|
1420
|
+
try {
|
|
1421
|
+
await fs2.rm(testDir, { recursive: true, force: true });
|
|
1422
|
+
} catch {
|
|
1423
|
+
}
|
|
1424
|
+
const err = error;
|
|
1425
|
+
if (err.code === "EPERM" || err.code === "ENOTSUP") {
|
|
1426
|
+
return false;
|
|
1427
|
+
}
|
|
1428
|
+
return true;
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
// src/installer.ts
|
|
1433
|
+
import YAML2 from "yaml";
|
|
1434
|
+
async function ensureDir2(dir) {
|
|
1435
|
+
await fs3.mkdir(dir, { recursive: true });
|
|
1436
|
+
}
|
|
1437
|
+
async function fileExists2(p) {
|
|
1438
|
+
try {
|
|
1439
|
+
await fs3.access(p);
|
|
1440
|
+
return true;
|
|
1441
|
+
} catch {
|
|
1442
|
+
return false;
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
912
1445
|
async function readFile(p) {
|
|
913
1446
|
try {
|
|
914
|
-
return await
|
|
1447
|
+
return await fs3.readFile(p, "utf-8");
|
|
915
1448
|
} catch {
|
|
916
1449
|
return null;
|
|
917
1450
|
}
|
|
@@ -1027,15 +1560,14 @@ async function installPackage(pkg, detectedTools, projectRoot, options) {
|
|
|
1027
1560
|
async function installRule(pkg, tool, content, projectRoot, options) {
|
|
1028
1561
|
const toolId = tool.config.id;
|
|
1029
1562
|
const rulesConfig = tool.config.rules;
|
|
1030
|
-
const basePath = options.global ? rulesConfig.globalPath :
|
|
1563
|
+
const basePath = options.global ? rulesConfig.globalPath : path4.join(projectRoot, rulesConfig.projectPath);
|
|
1031
1564
|
const override = pkg.manifest.install?.[toolId];
|
|
1032
1565
|
if (override?.action) {
|
|
1033
1566
|
return handleSpecialAction(pkg, tool, content, projectRoot, options, override.action);
|
|
1034
1567
|
}
|
|
1035
|
-
const convertedContent = convertToToolFormat(content, tool.config, pkg.manifest.name);
|
|
1036
1568
|
const filename = `${pkg.manifest.name}${rulesConfig.fileExtension}`;
|
|
1037
|
-
const targetPath =
|
|
1038
|
-
if (!options.force && await
|
|
1569
|
+
const targetPath = path4.join(basePath, filename);
|
|
1570
|
+
if (!options.force && await fileExists2(targetPath)) {
|
|
1039
1571
|
return {
|
|
1040
1572
|
tool: toolId,
|
|
1041
1573
|
toolName: tool.config.name,
|
|
@@ -1047,6 +1579,7 @@ async function installRule(pkg, tool, content, projectRoot, options) {
|
|
|
1047
1579
|
};
|
|
1048
1580
|
}
|
|
1049
1581
|
if (options.dryRun) {
|
|
1582
|
+
const method = options.installMethod ?? "symlink";
|
|
1050
1583
|
return {
|
|
1051
1584
|
tool: toolId,
|
|
1052
1585
|
toolName: tool.config.name,
|
|
@@ -1054,18 +1587,68 @@ async function installRule(pkg, tool, content, projectRoot, options) {
|
|
|
1054
1587
|
path: targetPath,
|
|
1055
1588
|
success: true,
|
|
1056
1589
|
skipped: true,
|
|
1057
|
-
skipReason:
|
|
1590
|
+
skipReason: `Dry run (would use ${method})`
|
|
1591
|
+
};
|
|
1592
|
+
}
|
|
1593
|
+
const useSymlink = options.installMethod !== "copy";
|
|
1594
|
+
if (useSymlink) {
|
|
1595
|
+
try {
|
|
1596
|
+
const supported = await symlinkSupported();
|
|
1597
|
+
if (!supported) {
|
|
1598
|
+
return await installWithCopyMode(pkg, tool, content, basePath, options);
|
|
1599
|
+
}
|
|
1600
|
+
const installedPath = await installWithSymlink(
|
|
1601
|
+
pkg.manifest.name,
|
|
1602
|
+
content,
|
|
1603
|
+
tool.config,
|
|
1604
|
+
basePath,
|
|
1605
|
+
{ force: options.force }
|
|
1606
|
+
);
|
|
1607
|
+
return {
|
|
1608
|
+
tool: toolId,
|
|
1609
|
+
toolName: tool.config.name,
|
|
1610
|
+
type: "rule",
|
|
1611
|
+
path: installedPath,
|
|
1612
|
+
success: true
|
|
1613
|
+
};
|
|
1614
|
+
} catch (error) {
|
|
1615
|
+
const err = error;
|
|
1616
|
+
if (err.code === "EPERM" || err.code === "ENOTSUP") {
|
|
1617
|
+
return await installWithCopyMode(pkg, tool, content, basePath, options);
|
|
1618
|
+
}
|
|
1619
|
+
throw error;
|
|
1620
|
+
}
|
|
1621
|
+
} else {
|
|
1622
|
+
return await installWithCopyMode(pkg, tool, content, basePath, options);
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
async function installWithCopyMode(pkg, tool, content, basePath, options) {
|
|
1626
|
+
const toolId = tool.config.id;
|
|
1627
|
+
try {
|
|
1628
|
+
const installedPath = await installWithCopy(
|
|
1629
|
+
pkg.manifest.name,
|
|
1630
|
+
content,
|
|
1631
|
+
tool.config,
|
|
1632
|
+
basePath,
|
|
1633
|
+
{ force: options.force }
|
|
1634
|
+
);
|
|
1635
|
+
return {
|
|
1636
|
+
tool: toolId,
|
|
1637
|
+
toolName: tool.config.name,
|
|
1638
|
+
type: "rule",
|
|
1639
|
+
path: installedPath,
|
|
1640
|
+
success: true
|
|
1641
|
+
};
|
|
1642
|
+
} catch (error) {
|
|
1643
|
+
return {
|
|
1644
|
+
tool: toolId,
|
|
1645
|
+
toolName: tool.config.name,
|
|
1646
|
+
type: "rule",
|
|
1647
|
+
path: "",
|
|
1648
|
+
success: false,
|
|
1649
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1058
1650
|
};
|
|
1059
1651
|
}
|
|
1060
|
-
await ensureDir(basePath);
|
|
1061
|
-
await fs2.writeFile(targetPath, convertedContent, "utf-8");
|
|
1062
|
-
return {
|
|
1063
|
-
tool: toolId,
|
|
1064
|
-
toolName: tool.config.name,
|
|
1065
|
-
type: "rule",
|
|
1066
|
-
path: targetPath,
|
|
1067
|
-
success: true
|
|
1068
|
-
};
|
|
1069
1652
|
}
|
|
1070
1653
|
async function installSkill(pkg, tool, content, projectRoot, options) {
|
|
1071
1654
|
const toolId = tool.config.id;
|
|
@@ -1081,10 +1664,10 @@ async function installSkill(pkg, tool, content, projectRoot, options) {
|
|
|
1081
1664
|
skipReason: "Tool does not support skills"
|
|
1082
1665
|
};
|
|
1083
1666
|
}
|
|
1084
|
-
const basePath = options.global ? skillsConfig.globalPath :
|
|
1085
|
-
const skillDir =
|
|
1086
|
-
const skillPath =
|
|
1087
|
-
if (!options.force && await
|
|
1667
|
+
const basePath = options.global ? skillsConfig.globalPath : path4.join(projectRoot, skillsConfig.projectPath);
|
|
1668
|
+
const skillDir = path4.join(basePath, pkg.manifest.name);
|
|
1669
|
+
const skillPath = path4.join(skillDir, "SKILL.md");
|
|
1670
|
+
if (!options.force && await fileExists2(skillPath)) {
|
|
1088
1671
|
return {
|
|
1089
1672
|
tool: toolId,
|
|
1090
1673
|
toolName: tool.config.name,
|
|
@@ -1106,13 +1689,13 @@ async function installSkill(pkg, tool, content, projectRoot, options) {
|
|
|
1106
1689
|
skipReason: "Dry run"
|
|
1107
1690
|
};
|
|
1108
1691
|
}
|
|
1109
|
-
await
|
|
1110
|
-
await
|
|
1692
|
+
await ensureDir2(skillDir);
|
|
1693
|
+
await fs3.writeFile(skillPath, content, "utf-8");
|
|
1111
1694
|
for (const file of pkg.files) {
|
|
1112
1695
|
if (file.path !== "SKILL.md" && file.path !== "rule.md" && file.path !== "manifest.json") {
|
|
1113
|
-
const targetFile =
|
|
1114
|
-
await
|
|
1115
|
-
await
|
|
1696
|
+
const targetFile = path4.join(skillDir, file.path);
|
|
1697
|
+
await ensureDir2(path4.dirname(targetFile));
|
|
1698
|
+
await fs3.writeFile(targetFile, file.content, "utf-8");
|
|
1116
1699
|
}
|
|
1117
1700
|
}
|
|
1118
1701
|
return {
|
|
@@ -1128,7 +1711,7 @@ async function handleSpecialAction(pkg, tool, content, projectRoot, options, act
|
|
|
1128
1711
|
const { body } = parseFrontmatter(content);
|
|
1129
1712
|
switch (action) {
|
|
1130
1713
|
case "append-to-agents-md": {
|
|
1131
|
-
const agentsPath = options.global ?
|
|
1714
|
+
const agentsPath = options.global ? path4.join(tool.config.rules.globalPath, "AGENTS.md") : path4.join(projectRoot, "AGENTS.md");
|
|
1132
1715
|
if (options.dryRun) {
|
|
1133
1716
|
return {
|
|
1134
1717
|
tool: toolId,
|
|
@@ -1163,9 +1746,9 @@ ${body}
|
|
|
1163
1746
|
[\\s\\S]*?(?=
|
|
1164
1747
|
## |$)`, "g");
|
|
1165
1748
|
const updated = existing.replace(regex, "") + section;
|
|
1166
|
-
await
|
|
1749
|
+
await fs3.writeFile(agentsPath, updated, "utf-8");
|
|
1167
1750
|
} else {
|
|
1168
|
-
await
|
|
1751
|
+
await fs3.writeFile(agentsPath, existing + section, "utf-8");
|
|
1169
1752
|
}
|
|
1170
1753
|
return {
|
|
1171
1754
|
tool: toolId,
|
|
@@ -1176,7 +1759,7 @@ ${body}
|
|
|
1176
1759
|
};
|
|
1177
1760
|
}
|
|
1178
1761
|
case "append-to-gemini-md": {
|
|
1179
|
-
const geminiPath = options.global ?
|
|
1762
|
+
const geminiPath = options.global ? path4.join(tool.config.rules.globalPath, "GEMINI.md") : path4.join(projectRoot, "GEMINI.md");
|
|
1180
1763
|
if (options.dryRun) {
|
|
1181
1764
|
return {
|
|
1182
1765
|
tool: toolId,
|
|
@@ -1205,7 +1788,7 @@ ${body}
|
|
|
1205
1788
|
skipReason: "Section already exists in GEMINI.md"
|
|
1206
1789
|
};
|
|
1207
1790
|
}
|
|
1208
|
-
await
|
|
1791
|
+
await fs3.writeFile(geminiPath, existing + section, "utf-8");
|
|
1209
1792
|
return {
|
|
1210
1793
|
tool: toolId,
|
|
1211
1794
|
toolName: tool.config.name,
|
|
@@ -1215,8 +1798,8 @@ ${body}
|
|
|
1215
1798
|
};
|
|
1216
1799
|
}
|
|
1217
1800
|
case "add-to-read-config": {
|
|
1218
|
-
const rulePath =
|
|
1219
|
-
const configPath =
|
|
1801
|
+
const rulePath = path4.join(projectRoot, `${pkg.manifest.name}.md`);
|
|
1802
|
+
const configPath = path4.join(projectRoot, ".aider.conf.yml");
|
|
1220
1803
|
if (options.dryRun) {
|
|
1221
1804
|
return {
|
|
1222
1805
|
tool: toolId,
|
|
@@ -1228,7 +1811,7 @@ ${body}
|
|
|
1228
1811
|
skipReason: "Dry run - would create file and update .aider.conf.yml"
|
|
1229
1812
|
};
|
|
1230
1813
|
}
|
|
1231
|
-
await
|
|
1814
|
+
await fs3.writeFile(rulePath, body, "utf-8");
|
|
1232
1815
|
const existingConfig = await readFile(configPath);
|
|
1233
1816
|
let config = {};
|
|
1234
1817
|
if (existingConfig) {
|
|
@@ -1250,7 +1833,49 @@ ${body}
|
|
|
1250
1833
|
} else {
|
|
1251
1834
|
config.read = fileName;
|
|
1252
1835
|
}
|
|
1253
|
-
await
|
|
1836
|
+
await fs3.writeFile(configPath, YAML2.stringify(config), "utf-8");
|
|
1837
|
+
return {
|
|
1838
|
+
tool: toolId,
|
|
1839
|
+
toolName: tool.config.name,
|
|
1840
|
+
type: "rule",
|
|
1841
|
+
path: rulePath,
|
|
1842
|
+
success: true
|
|
1843
|
+
};
|
|
1844
|
+
}
|
|
1845
|
+
case "add-with-gemini-import": {
|
|
1846
|
+
const rulesDir = options.global ? tool.config.rules.globalPath : path4.join(projectRoot, ".gemini", "rules");
|
|
1847
|
+
const rulePath = path4.join(rulesDir, `${pkg.manifest.name}.md`);
|
|
1848
|
+
const geminiMdPath = options.global ? path4.join(path4.dirname(tool.config.rules.globalPath), "GEMINI.md") : path4.join(projectRoot, "GEMINI.md");
|
|
1849
|
+
if (options.dryRun) {
|
|
1850
|
+
return {
|
|
1851
|
+
tool: toolId,
|
|
1852
|
+
toolName: tool.config.name,
|
|
1853
|
+
type: "rule",
|
|
1854
|
+
path: rulePath,
|
|
1855
|
+
success: true,
|
|
1856
|
+
skipped: true,
|
|
1857
|
+
skipReason: "Dry run - would create rule file and add @import to GEMINI.md"
|
|
1858
|
+
};
|
|
1859
|
+
}
|
|
1860
|
+
if (!options.force && await fileExists2(rulePath)) {
|
|
1861
|
+
return {
|
|
1862
|
+
tool: toolId,
|
|
1863
|
+
toolName: tool.config.name,
|
|
1864
|
+
type: "rule",
|
|
1865
|
+
path: rulePath,
|
|
1866
|
+
success: false,
|
|
1867
|
+
skipped: true,
|
|
1868
|
+
skipReason: "File already exists (use --force to overwrite)"
|
|
1869
|
+
};
|
|
1870
|
+
}
|
|
1871
|
+
await ensureDir2(rulesDir);
|
|
1872
|
+
await fs3.writeFile(rulePath, body, "utf-8");
|
|
1873
|
+
const existingGemini = await readFile(geminiMdPath) || "";
|
|
1874
|
+
const importLine = `@rules/${pkg.manifest.name}.md`;
|
|
1875
|
+
if (!existingGemini.includes(importLine)) {
|
|
1876
|
+
const newContent = existingGemini.trim() ? existingGemini.trimEnd() + "\n" + importLine + "\n" : importLine + "\n";
|
|
1877
|
+
await fs3.writeFile(geminiMdPath, newContent, "utf-8");
|
|
1878
|
+
}
|
|
1254
1879
|
return {
|
|
1255
1880
|
tool: toolId,
|
|
1256
1881
|
toolName: tool.config.name,
|
|
@@ -1275,12 +1900,12 @@ async function uninstallPackage(packageName, detectedTools, projectRoot, options
|
|
|
1275
1900
|
for (const tool of detectedTools) {
|
|
1276
1901
|
const toolId = tool.config.id;
|
|
1277
1902
|
const rulesConfig = tool.config.rules;
|
|
1278
|
-
const ruleBasePath = options.global ? rulesConfig.globalPath :
|
|
1903
|
+
const ruleBasePath = options.global ? rulesConfig.globalPath : path4.join(projectRoot, rulesConfig.projectPath);
|
|
1279
1904
|
const ruleFilename = `${packageName}${rulesConfig.fileExtension}`;
|
|
1280
|
-
const rulePath =
|
|
1281
|
-
if (await
|
|
1905
|
+
const rulePath = path4.join(ruleBasePath, ruleFilename);
|
|
1906
|
+
if (await fileExists2(rulePath)) {
|
|
1282
1907
|
if (!options.dryRun) {
|
|
1283
|
-
await
|
|
1908
|
+
await fs3.unlink(rulePath);
|
|
1284
1909
|
}
|
|
1285
1910
|
results.push({
|
|
1286
1911
|
tool: toolId,
|
|
@@ -1293,11 +1918,11 @@ async function uninstallPackage(packageName, detectedTools, projectRoot, options
|
|
|
1293
1918
|
});
|
|
1294
1919
|
}
|
|
1295
1920
|
if (tool.config.skills) {
|
|
1296
|
-
const skillBasePath = options.global ? tool.config.skills.globalPath :
|
|
1297
|
-
const skillDir =
|
|
1298
|
-
if (await
|
|
1921
|
+
const skillBasePath = options.global ? tool.config.skills.globalPath : path4.join(projectRoot, tool.config.skills.projectPath);
|
|
1922
|
+
const skillDir = path4.join(skillBasePath, packageName);
|
|
1923
|
+
if (await fileExists2(skillDir)) {
|
|
1299
1924
|
if (!options.dryRun) {
|
|
1300
|
-
await
|
|
1925
|
+
await fs3.rm(skillDir, { recursive: true });
|
|
1301
1926
|
}
|
|
1302
1927
|
results.push({
|
|
1303
1928
|
tool: toolId,
|
|
@@ -1320,9 +1945,9 @@ async function listInstalled(detectedTools, projectRoot, options) {
|
|
|
1320
1945
|
const rulesConfig = tool.config.rules;
|
|
1321
1946
|
const rules = [];
|
|
1322
1947
|
const skills = [];
|
|
1323
|
-
const ruleBasePath = options.global ? rulesConfig.globalPath :
|
|
1948
|
+
const ruleBasePath = options.global ? rulesConfig.globalPath : path4.join(projectRoot, rulesConfig.projectPath);
|
|
1324
1949
|
try {
|
|
1325
|
-
const files = await
|
|
1950
|
+
const files = await fs3.readdir(ruleBasePath);
|
|
1326
1951
|
for (const file of files) {
|
|
1327
1952
|
if (file.endsWith(rulesConfig.fileExtension)) {
|
|
1328
1953
|
rules.push(file.replace(rulesConfig.fileExtension, ""));
|
|
@@ -1331,12 +1956,12 @@ async function listInstalled(detectedTools, projectRoot, options) {
|
|
|
1331
1956
|
} catch {
|
|
1332
1957
|
}
|
|
1333
1958
|
if (tool.config.skills) {
|
|
1334
|
-
const skillBasePath = options.global ? tool.config.skills.globalPath :
|
|
1959
|
+
const skillBasePath = options.global ? tool.config.skills.globalPath : path4.join(projectRoot, tool.config.skills.projectPath);
|
|
1335
1960
|
try {
|
|
1336
|
-
const dirs = await
|
|
1961
|
+
const dirs = await fs3.readdir(skillBasePath);
|
|
1337
1962
|
for (const dir of dirs) {
|
|
1338
|
-
const skillPath =
|
|
1339
|
-
if (await
|
|
1963
|
+
const skillPath = path4.join(skillBasePath, dir, "SKILL.md");
|
|
1964
|
+
if (await fileExists2(skillPath)) {
|
|
1340
1965
|
skills.push(dir);
|
|
1341
1966
|
}
|
|
1342
1967
|
}
|
|
@@ -1351,9 +1976,9 @@ async function listInstalled(detectedTools, projectRoot, options) {
|
|
|
1351
1976
|
}
|
|
1352
1977
|
|
|
1353
1978
|
// src/registry.ts
|
|
1354
|
-
import
|
|
1355
|
-
import
|
|
1356
|
-
import
|
|
1979
|
+
import fs4 from "fs/promises";
|
|
1980
|
+
import path5 from "path";
|
|
1981
|
+
import os3 from "os";
|
|
1357
1982
|
var SCHEMA_VERSION = 1;
|
|
1358
1983
|
function emptyRegistry() {
|
|
1359
1984
|
return { schemaVersion: SCHEMA_VERSION, packages: {} };
|
|
@@ -1363,17 +1988,17 @@ function registryKey(name, installType) {
|
|
|
1363
1988
|
}
|
|
1364
1989
|
function getRegistryPath(projectRoot, global) {
|
|
1365
1990
|
if (global) {
|
|
1366
|
-
return
|
|
1991
|
+
return path5.join(os3.homedir(), ".faster", "installed.json");
|
|
1367
1992
|
}
|
|
1368
|
-
return
|
|
1993
|
+
return path5.join(projectRoot, ".faster", "installed.json");
|
|
1369
1994
|
}
|
|
1370
|
-
async function
|
|
1371
|
-
await
|
|
1995
|
+
async function ensureDir3(dir) {
|
|
1996
|
+
await fs4.mkdir(dir, { recursive: true });
|
|
1372
1997
|
}
|
|
1373
1998
|
async function readRegistry(projectRoot, global) {
|
|
1374
1999
|
const registryPath = getRegistryPath(projectRoot, global);
|
|
1375
2000
|
try {
|
|
1376
|
-
const raw = await
|
|
2001
|
+
const raw = await fs4.readFile(registryPath, "utf-8");
|
|
1377
2002
|
const parsed = JSON.parse(raw);
|
|
1378
2003
|
if (!parsed || parsed.schemaVersion !== SCHEMA_VERSION || typeof parsed.packages !== "object") {
|
|
1379
2004
|
return emptyRegistry();
|
|
@@ -1385,8 +2010,8 @@ async function readRegistry(projectRoot, global) {
|
|
|
1385
2010
|
}
|
|
1386
2011
|
async function writeRegistry(projectRoot, global, registry) {
|
|
1387
2012
|
const registryPath = getRegistryPath(projectRoot, global);
|
|
1388
|
-
await
|
|
1389
|
-
await
|
|
2013
|
+
await ensureDir3(path5.dirname(registryPath));
|
|
2014
|
+
await fs4.writeFile(registryPath, JSON.stringify(registry, null, 2), "utf-8");
|
|
1390
2015
|
}
|
|
1391
2016
|
function upsertInstalledPackage(registry, record) {
|
|
1392
2017
|
registry.packages[registryKey(record.name, record.installType)] = record;
|
|
@@ -1404,13 +2029,13 @@ function listInstalledPackages(registry) {
|
|
|
1404
2029
|
}
|
|
1405
2030
|
|
|
1406
2031
|
// src/commands/shared/package-helpers.ts
|
|
1407
|
-
import
|
|
2032
|
+
import path6 from "path";
|
|
1408
2033
|
async function resolveDetectedTools(projectRoot, options, defaultTools) {
|
|
1409
2034
|
let detectedTools = await detectTools(projectRoot);
|
|
1410
2035
|
if (detectedTools.length === 0) {
|
|
1411
2036
|
detectedTools = Object.values(TOOL_CONFIGS).map((config) => ({
|
|
1412
2037
|
config,
|
|
1413
|
-
projectPath:
|
|
2038
|
+
projectPath: path6.join(projectRoot, config.rules.projectPath),
|
|
1414
2039
|
globalPath: config.rules.globalPath
|
|
1415
2040
|
}));
|
|
1416
2041
|
}
|
|
@@ -1431,24 +2056,93 @@ async function resolveDetectedTools(projectRoot, options, defaultTools) {
|
|
|
1431
2056
|
}
|
|
1432
2057
|
|
|
1433
2058
|
// src/commands/package/install.ts
|
|
1434
|
-
import
|
|
1435
|
-
import
|
|
2059
|
+
import fs5 from "fs/promises";
|
|
2060
|
+
import path7 from "path";
|
|
1436
2061
|
function registerInstallCommand(program2) {
|
|
1437
|
-
program2.command("install <package>").alias("add").description("Install a skill or rule from faster.dev").option("-g, --global", "Install globally instead of to project").option("-t, --tools <tools>", "Comma-separated list of tools to install to").option("--as-skill", "Install as a skill (where supported)").option("-f, --force", "Overwrite existing installations").option("--dry-run", "Show what would be installed without making changes").option("--from-file <path>", "Install from a local package directory").action(async (packageInput, opts) => {
|
|
2062
|
+
program2.command("install <package>").alias("add").description("Install a skill or rule from faster.dev").option("-g, --global", "Install globally instead of to project").option("-t, --tools <tools>", "Comma-separated list of tools to install to").option("--as-skill", "Install as a skill (where supported)").option("-f, --force", "Overwrite existing installations").option("--dry-run", "Show what would be installed without making changes").option("--from-file <path>", "Install from a local package directory").option("--copy", "Install as copies instead of symlinks").action(async (packageInput, opts) => {
|
|
1438
2063
|
const { json, verbose } = program2.opts();
|
|
1439
2064
|
const projectRoot = process.cwd();
|
|
1440
|
-
const { name: packageName, version } = parsePackageSpec(packageInput);
|
|
1441
2065
|
const options = {
|
|
1442
2066
|
global: opts.global ?? false,
|
|
1443
2067
|
tools: opts.tools ? opts.tools.split(",") : void 0,
|
|
1444
2068
|
asSkill: opts.asSkill ?? false,
|
|
1445
2069
|
force: opts.force ?? false,
|
|
1446
|
-
dryRun: opts.dryRun ?? false
|
|
2070
|
+
dryRun: opts.dryRun ?? false,
|
|
2071
|
+
installMethod: opts.copy ? "copy" : "symlink"
|
|
1447
2072
|
};
|
|
1448
2073
|
const defaultTools = getDefaultTools();
|
|
1449
2074
|
const spinner = new SpinnerManager("Detecting tools...", json ?? false);
|
|
1450
2075
|
try {
|
|
1451
2076
|
const detectedTools = await resolveDetectedTools(projectRoot, options, defaultTools);
|
|
2077
|
+
const scope = getScopeFromInput(packageInput);
|
|
2078
|
+
if (scope && !opts.fromFile) {
|
|
2079
|
+
const api = new FasterAPI(getConfig());
|
|
2080
|
+
spinner.text(`Fetching packages in @${scope}...`);
|
|
2081
|
+
const scopePackages = await api.getPackagesByScope(scope);
|
|
2082
|
+
if (scopePackages.length === 0) {
|
|
2083
|
+
spinner.fail(`No packages found in scope @${scope}`);
|
|
2084
|
+
if (json) outputJson({ ok: false, error: `No packages found in scope @${scope}` });
|
|
2085
|
+
setExitCode(EXIT_CODES.ERROR);
|
|
2086
|
+
return;
|
|
2087
|
+
}
|
|
2088
|
+
spinner.stop();
|
|
2089
|
+
console.log(chalk8.bold(`
|
|
2090
|
+
Found ${scopePackages.length} package(s) in @${scope}:
|
|
2091
|
+
`));
|
|
2092
|
+
for (const scopePkg of scopePackages) {
|
|
2093
|
+
console.log(` ${chalk8.cyan(scopePkg.name)} ${chalk8.dim(`v${scopePkg.version}`)}`);
|
|
2094
|
+
console.log(` ${chalk8.dim(scopePkg.description)}`);
|
|
2095
|
+
}
|
|
2096
|
+
console.log();
|
|
2097
|
+
if (!options.dryRun && !json) {
|
|
2098
|
+
const confirmed = await confirm("Install all packages?");
|
|
2099
|
+
if (!confirmed) {
|
|
2100
|
+
console.log(chalk8.yellow("\nInstallation cancelled."));
|
|
2101
|
+
return;
|
|
2102
|
+
}
|
|
2103
|
+
console.log();
|
|
2104
|
+
}
|
|
2105
|
+
const allResults = [];
|
|
2106
|
+
for (const scopePkg of scopePackages) {
|
|
2107
|
+
const pkgSpinner = new SpinnerManager(`Installing ${scopePkg.name}...`, json ?? false);
|
|
2108
|
+
try {
|
|
2109
|
+
const pkg2 = await api.downloadPackage(scopePkg.name);
|
|
2110
|
+
const results2 = await installPackage(pkg2, detectedTools, projectRoot, options);
|
|
2111
|
+
const installType2 = resolveInstallType(options.asSkill);
|
|
2112
|
+
const successTools2 = results2.filter((r) => r.success && !r.skipped).map((r) => r.tool);
|
|
2113
|
+
if (!options.dryRun && successTools2.length > 0) {
|
|
2114
|
+
const registry = await readRegistry(projectRoot, options.global);
|
|
2115
|
+
upsertInstalledPackage(registry, {
|
|
2116
|
+
name: pkg2.manifest.name,
|
|
2117
|
+
version: pkg2.manifest.version,
|
|
2118
|
+
installType: installType2,
|
|
2119
|
+
tools: successTools2,
|
|
2120
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2121
|
+
source: "registry"
|
|
2122
|
+
});
|
|
2123
|
+
await writeRegistry(projectRoot, options.global, registry);
|
|
2124
|
+
}
|
|
2125
|
+
pkgSpinner.stop();
|
|
2126
|
+
allResults.push({ pkg: pkg2, results: results2 });
|
|
2127
|
+
if (!json) {
|
|
2128
|
+
printInstallResults(pkg2, results2);
|
|
2129
|
+
}
|
|
2130
|
+
} catch (error) {
|
|
2131
|
+
pkgSpinner.fail(`Failed to install ${scopePkg.name}: ${stringifyError(error, verbose)}`);
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
if (json) {
|
|
2135
|
+
outputJson({
|
|
2136
|
+
scope,
|
|
2137
|
+
packages: allResults.map(({ pkg: pkg2, results: results2 }) => ({
|
|
2138
|
+
package: pkg2.manifest,
|
|
2139
|
+
results: results2
|
|
2140
|
+
}))
|
|
2141
|
+
});
|
|
2142
|
+
}
|
|
2143
|
+
return;
|
|
2144
|
+
}
|
|
2145
|
+
const { name: packageName, version } = parsePackageSpec(packageInput);
|
|
1452
2146
|
spinner.text(`Fetching package: ${packageName}...`);
|
|
1453
2147
|
let pkg;
|
|
1454
2148
|
const isLocal = Boolean(opts.fromFile);
|
|
@@ -1537,25 +2231,25 @@ function printInstallResults(pkg, results) {
|
|
|
1537
2231
|
}
|
|
1538
2232
|
async function loadLocalPackage(dir) {
|
|
1539
2233
|
const files = [];
|
|
1540
|
-
const manifestPath =
|
|
1541
|
-
const manifestContent = await
|
|
2234
|
+
const manifestPath = path7.join(dir, "manifest.json");
|
|
2235
|
+
const manifestContent = await fs5.readFile(manifestPath, "utf-8");
|
|
1542
2236
|
files.push({ path: "manifest.json", content: manifestContent });
|
|
1543
2237
|
const manifest = JSON.parse(manifestContent);
|
|
1544
|
-
const entries = await
|
|
2238
|
+
const entries = await fs5.readdir(dir, { withFileTypes: true });
|
|
1545
2239
|
for (const entry of entries) {
|
|
1546
2240
|
if (entry.isFile() && entry.name !== "manifest.json") {
|
|
1547
2241
|
if (entry.name.endsWith(".md") || entry.name.endsWith(".mdc") || entry.name.endsWith(".txt")) {
|
|
1548
|
-
const content = await
|
|
2242
|
+
const content = await fs5.readFile(path7.join(dir, entry.name), "utf-8");
|
|
1549
2243
|
files.push({ path: entry.name, content });
|
|
1550
2244
|
}
|
|
1551
2245
|
}
|
|
1552
2246
|
}
|
|
1553
|
-
const assetsDir =
|
|
2247
|
+
const assetsDir = path7.join(dir, "assets");
|
|
1554
2248
|
try {
|
|
1555
|
-
const assetEntries = await
|
|
2249
|
+
const assetEntries = await fs5.readdir(assetsDir, { withFileTypes: true });
|
|
1556
2250
|
for (const entry of assetEntries) {
|
|
1557
2251
|
if (entry.isFile()) {
|
|
1558
|
-
const content = await
|
|
2252
|
+
const content = await fs5.readFile(path7.join(assetsDir, entry.name), "utf-8");
|
|
1559
2253
|
files.push({ path: `assets/${entry.name}`, content });
|
|
1560
2254
|
}
|
|
1561
2255
|
}
|