@sporesec/arcana 2.3.1 → 3.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.d.ts +0 -1
- package/dist/cli.js +140 -10
- package/dist/command-registry.d.ts +10 -0
- package/dist/command-registry.js +65 -0
- package/dist/commands/audit.d.ts +0 -1
- package/dist/commands/audit.js +16 -6
- package/dist/commands/benchmark.d.ts +4 -0
- package/dist/commands/benchmark.js +178 -0
- package/dist/commands/clean.d.ts +2 -1
- package/dist/commands/clean.js +198 -47
- package/dist/commands/compact.d.ts +6 -0
- package/dist/commands/compact.js +239 -0
- package/dist/commands/completions.d.ts +3 -0
- package/dist/commands/completions.js +104 -0
- package/dist/commands/config.d.ts +0 -1
- package/dist/commands/config.js +15 -6
- package/dist/commands/create.d.ts +0 -1
- package/dist/commands/create.js +1 -1
- package/dist/commands/diff.d.ts +4 -0
- package/dist/commands/diff.js +166 -0
- package/dist/commands/doctor.d.ts +0 -1
- package/dist/commands/doctor.js +153 -24
- package/dist/commands/export-cmd.d.ts +4 -0
- package/dist/commands/export-cmd.js +66 -0
- package/dist/commands/import-cmd.d.ts +4 -0
- package/dist/commands/import-cmd.js +131 -0
- package/dist/commands/info.d.ts +0 -1
- package/dist/commands/info.js +29 -4
- package/dist/commands/init.d.ts +0 -1
- package/dist/commands/init.js +156 -117
- package/dist/commands/install.d.ts +1 -1
- package/dist/commands/install.js +118 -205
- package/dist/commands/list.d.ts +0 -1
- package/dist/commands/list.js +12 -4
- package/dist/commands/lock.d.ts +4 -0
- package/dist/commands/lock.js +171 -0
- package/dist/commands/optimize.d.ts +3 -0
- package/dist/commands/optimize.js +356 -0
- package/dist/commands/outdated.d.ts +4 -0
- package/dist/commands/outdated.js +159 -0
- package/dist/commands/profile.d.ts +3 -0
- package/dist/commands/profile.js +274 -0
- package/dist/commands/providers.d.ts +0 -1
- package/dist/commands/providers.js +1 -4
- package/dist/commands/recommend.d.ts +5 -0
- package/dist/commands/recommend.js +96 -0
- package/dist/commands/scan.d.ts +0 -1
- package/dist/commands/scan.js +13 -7
- package/dist/commands/search.d.ts +2 -1
- package/dist/commands/search.js +32 -9
- package/dist/commands/stats.d.ts +0 -1
- package/dist/commands/stats.js +83 -16
- package/dist/commands/team.d.ts +3 -0
- package/dist/commands/team.js +291 -0
- package/dist/commands/uninstall.d.ts +0 -1
- package/dist/commands/uninstall.js +18 -4
- package/dist/commands/update.d.ts +0 -1
- package/dist/commands/update.js +155 -155
- package/dist/commands/validate.d.ts +0 -1
- package/dist/commands/validate.js +14 -6
- package/dist/commands/verify.d.ts +4 -0
- package/dist/commands/verify.js +116 -0
- package/dist/constants.d.ts +10 -0
- package/dist/constants.js +13 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/interactive/browse.d.ts +4 -0
- package/dist/interactive/browse.js +103 -0
- package/dist/interactive/categories.d.ts +4 -0
- package/dist/interactive/categories.js +87 -0
- package/dist/interactive/health.d.ts +1 -0
- package/dist/interactive/health.js +57 -0
- package/dist/interactive/helpers.d.ts +11 -0
- package/dist/interactive/helpers.js +66 -0
- package/dist/interactive/index.d.ts +1 -0
- package/dist/interactive/index.js +1 -0
- package/dist/interactive/manage.d.ts +2 -0
- package/dist/interactive/manage.js +187 -0
- package/dist/interactive/menu.d.ts +1 -0
- package/dist/interactive/menu.js +107 -0
- package/dist/interactive/search.d.ts +2 -0
- package/dist/interactive/search.js +66 -0
- package/dist/interactive/setup.d.ts +2 -0
- package/dist/interactive/setup.js +48 -0
- package/dist/interactive/skill-detail.d.ts +5 -0
- package/dist/interactive/skill-detail.js +126 -0
- package/dist/interactive.d.ts +0 -1
- package/dist/interactive.js +89 -66
- package/dist/providers/arcana.d.ts +0 -1
- package/dist/providers/arcana.js +0 -1
- package/dist/providers/base.d.ts +0 -1
- package/dist/providers/base.js +0 -1
- package/dist/providers/github.d.ts +0 -1
- package/dist/providers/github.js +8 -3
- package/dist/registry.d.ts +0 -1
- package/dist/registry.js +1 -4
- package/dist/types.d.ts +10 -1
- package/dist/types.js +0 -1
- package/dist/utils/atomic.d.ts +0 -1
- package/dist/utils/atomic.js +3 -2
- package/dist/utils/cache.d.ts +0 -1
- package/dist/utils/cache.js +3 -2
- package/dist/utils/config.d.ts +2 -1
- package/dist/utils/config.js +30 -5
- package/dist/utils/conflict-check.d.ts +8 -0
- package/dist/utils/conflict-check.js +72 -0
- package/dist/utils/errors.d.ts +0 -1
- package/dist/utils/errors.js +0 -1
- package/dist/utils/frontmatter.d.ts +0 -1
- package/dist/utils/frontmatter.js +37 -10
- package/dist/utils/fs.d.ts +19 -1
- package/dist/utils/fs.js +105 -8
- package/dist/utils/help.d.ts +0 -1
- package/dist/utils/help.js +15 -28
- package/dist/utils/history.d.ts +0 -1
- package/dist/utils/history.js +0 -1
- package/dist/utils/http.d.ts +0 -1
- package/dist/utils/http.js +14 -5
- package/dist/utils/install-core.d.ts +48 -0
- package/dist/utils/install-core.js +108 -0
- package/dist/utils/integrity.d.ts +17 -0
- package/dist/utils/integrity.js +84 -0
- package/dist/utils/parallel.d.ts +0 -1
- package/dist/utils/parallel.js +0 -1
- package/dist/utils/project-context.d.ts +19 -0
- package/dist/utils/project-context.js +283 -0
- package/dist/utils/scanner.d.ts +0 -1
- package/dist/utils/scanner.js +138 -10
- package/dist/utils/scoring.d.ts +10 -0
- package/dist/utils/scoring.js +84 -0
- package/dist/utils/ui.d.ts +0 -1
- package/dist/utils/ui.js +11 -4
- package/dist/utils/validate.d.ts +0 -1
- package/dist/utils/validate.js +4 -1
- package/package.json +19 -7
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/commands/audit.d.ts.map +0 -1
- package/dist/commands/audit.js.map +0 -1
- package/dist/commands/audit.test.d.ts +0 -2
- package/dist/commands/audit.test.d.ts.map +0 -1
- package/dist/commands/audit.test.js +0 -217
- package/dist/commands/audit.test.js.map +0 -1
- package/dist/commands/clean.d.ts.map +0 -1
- package/dist/commands/clean.js.map +0 -1
- package/dist/commands/config.d.ts.map +0 -1
- package/dist/commands/config.js.map +0 -1
- package/dist/commands/create.d.ts.map +0 -1
- package/dist/commands/create.js.map +0 -1
- package/dist/commands/doctor.d.ts.map +0 -1
- package/dist/commands/doctor.js.map +0 -1
- package/dist/commands/info.d.ts.map +0 -1
- package/dist/commands/info.js.map +0 -1
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/install.d.ts.map +0 -1
- package/dist/commands/install.js.map +0 -1
- package/dist/commands/list.d.ts.map +0 -1
- package/dist/commands/list.js.map +0 -1
- package/dist/commands/providers.d.ts.map +0 -1
- package/dist/commands/providers.js.map +0 -1
- package/dist/commands/scan.d.ts.map +0 -1
- package/dist/commands/scan.js.map +0 -1
- package/dist/commands/search.d.ts.map +0 -1
- package/dist/commands/search.js.map +0 -1
- package/dist/commands/stats.d.ts.map +0 -1
- package/dist/commands/stats.js.map +0 -1
- package/dist/commands/uninstall.d.ts.map +0 -1
- package/dist/commands/uninstall.js.map +0 -1
- package/dist/commands/update.d.ts.map +0 -1
- package/dist/commands/update.js.map +0 -1
- package/dist/commands/validate.d.ts.map +0 -1
- package/dist/commands/validate.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/interactive.d.ts.map +0 -1
- package/dist/interactive.js.map +0 -1
- package/dist/providers/arcana.d.ts.map +0 -1
- package/dist/providers/arcana.js.map +0 -1
- package/dist/providers/base.d.ts.map +0 -1
- package/dist/providers/base.js.map +0 -1
- package/dist/providers/github.d.ts.map +0 -1
- package/dist/providers/github.js.map +0 -1
- package/dist/registry.d.ts.map +0 -1
- package/dist/registry.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/utils/atomic.d.ts.map +0 -1
- package/dist/utils/atomic.js.map +0 -1
- package/dist/utils/atomic.test.d.ts +0 -2
- package/dist/utils/atomic.test.d.ts.map +0 -1
- package/dist/utils/atomic.test.js +0 -31
- package/dist/utils/atomic.test.js.map +0 -1
- package/dist/utils/cache.d.ts.map +0 -1
- package/dist/utils/cache.js.map +0 -1
- package/dist/utils/config.d.ts.map +0 -1
- package/dist/utils/config.js.map +0 -1
- package/dist/utils/config.test.d.ts +0 -2
- package/dist/utils/config.test.d.ts.map +0 -1
- package/dist/utils/config.test.js +0 -38
- package/dist/utils/config.test.js.map +0 -1
- package/dist/utils/errors.d.ts.map +0 -1
- package/dist/utils/errors.js.map +0 -1
- package/dist/utils/frontmatter.d.ts.map +0 -1
- package/dist/utils/frontmatter.js.map +0 -1
- package/dist/utils/frontmatter.test.d.ts +0 -2
- package/dist/utils/frontmatter.test.d.ts.map +0 -1
- package/dist/utils/frontmatter.test.js +0 -152
- package/dist/utils/frontmatter.test.js.map +0 -1
- package/dist/utils/fs.d.ts.map +0 -1
- package/dist/utils/fs.js.map +0 -1
- package/dist/utils/fs.test.d.ts +0 -2
- package/dist/utils/fs.test.d.ts.map +0 -1
- package/dist/utils/fs.test.js +0 -145
- package/dist/utils/fs.test.js.map +0 -1
- package/dist/utils/help.d.ts.map +0 -1
- package/dist/utils/help.js.map +0 -1
- package/dist/utils/help.test.d.ts +0 -2
- package/dist/utils/help.test.d.ts.map +0 -1
- package/dist/utils/help.test.js +0 -66
- package/dist/utils/help.test.js.map +0 -1
- package/dist/utils/history.d.ts.map +0 -1
- package/dist/utils/history.js.map +0 -1
- package/dist/utils/http.d.ts.map +0 -1
- package/dist/utils/http.js.map +0 -1
- package/dist/utils/http.test.d.ts +0 -2
- package/dist/utils/http.test.d.ts.map +0 -1
- package/dist/utils/http.test.js +0 -55
- package/dist/utils/http.test.js.map +0 -1
- package/dist/utils/parallel.d.ts.map +0 -1
- package/dist/utils/parallel.js.map +0 -1
- package/dist/utils/scanner.d.ts.map +0 -1
- package/dist/utils/scanner.js.map +0 -1
- package/dist/utils/ui.d.ts.map +0 -1
- package/dist/utils/ui.js.map +0 -1
- package/dist/utils/ui.test.d.ts +0 -2
- package/dist/utils/ui.test.d.ts.map +0 -1
- package/dist/utils/ui.test.js +0 -31
- package/dist/utils/ui.test.js.map +0 -1
- package/dist/utils/validate.d.ts.map +0 -1
- package/dist/utils/validate.js.map +0 -1
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { existsSync, readFileSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { atomicWriteSync } from "../utils/atomic.js";
|
|
5
|
+
import { validateSlug } from "../utils/validate.js";
|
|
6
|
+
function getProfilesPath() {
|
|
7
|
+
return join(homedir(), ".arcana", "profiles.json");
|
|
8
|
+
}
|
|
9
|
+
function readProfiles() {
|
|
10
|
+
const path = getProfilesPath();
|
|
11
|
+
if (!existsSync(path))
|
|
12
|
+
return {};
|
|
13
|
+
try {
|
|
14
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return {};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function writeProfiles(profiles) {
|
|
21
|
+
const dir = join(homedir(), ".arcana");
|
|
22
|
+
if (!existsSync(dir)) {
|
|
23
|
+
mkdirSync(dir, { recursive: true });
|
|
24
|
+
}
|
|
25
|
+
atomicWriteSync(getProfilesPath(), JSON.stringify(profiles, null, 2) + "\n");
|
|
26
|
+
}
|
|
27
|
+
export async function profileCommand(action, name, skills, opts) {
|
|
28
|
+
const resolved = action ?? "list";
|
|
29
|
+
switch (resolved) {
|
|
30
|
+
case "list":
|
|
31
|
+
return listProfiles(opts.json);
|
|
32
|
+
case "create":
|
|
33
|
+
return createProfile(name, skills, opts.json);
|
|
34
|
+
case "delete":
|
|
35
|
+
return deleteProfile(name, opts.json);
|
|
36
|
+
case "show":
|
|
37
|
+
return showProfile(name, opts.json);
|
|
38
|
+
case "apply":
|
|
39
|
+
return applyProfile(name, opts.json);
|
|
40
|
+
default:
|
|
41
|
+
console.error(`Unknown action: ${resolved}`);
|
|
42
|
+
console.error("Valid actions: list, create, delete, show, apply");
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function listProfiles(json) {
|
|
47
|
+
const profiles = readProfiles();
|
|
48
|
+
const names = Object.keys(profiles);
|
|
49
|
+
if (json) {
|
|
50
|
+
console.log(JSON.stringify({ profiles }));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (names.length === 0) {
|
|
54
|
+
console.log("No profiles defined.");
|
|
55
|
+
console.log("Create one: arcana profile create <name> <skill1> <skill2> ...");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
console.log(`${names.length} profile(s):\n`);
|
|
59
|
+
for (const profileName of names) {
|
|
60
|
+
const skillList = profiles[profileName];
|
|
61
|
+
console.log(` ${profileName.padEnd(20)} ${skillList.length} skill(s): ${skillList.join(", ")}`);
|
|
62
|
+
}
|
|
63
|
+
console.log();
|
|
64
|
+
}
|
|
65
|
+
function createProfile(name, skills, json) {
|
|
66
|
+
if (!name) {
|
|
67
|
+
if (json) {
|
|
68
|
+
console.log(JSON.stringify({ error: "Profile name is required" }));
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
console.error("Profile name is required.");
|
|
72
|
+
console.error("Usage: arcana profile create <name> <skill1> <skill2> ...");
|
|
73
|
+
}
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
validateSlug(name, "profile name");
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
if (json) {
|
|
81
|
+
console.log(JSON.stringify({ error: err instanceof Error ? err.message : "Invalid profile name" }));
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
console.error(err instanceof Error ? err.message : "Invalid profile name");
|
|
85
|
+
}
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
if (skills.length === 0) {
|
|
89
|
+
if (json) {
|
|
90
|
+
console.log(JSON.stringify({ error: "At least one skill is required" }));
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
console.error("At least one skill is required.");
|
|
94
|
+
console.error("Usage: arcana profile create <name> <skill1> <skill2> ...");
|
|
95
|
+
}
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
for (const skill of skills) {
|
|
99
|
+
try {
|
|
100
|
+
validateSlug(skill, "skill name");
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
if (json) {
|
|
104
|
+
console.log(JSON.stringify({ error: err instanceof Error ? err.message : `Invalid skill name: ${skill}` }));
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
console.error(err instanceof Error ? err.message : `Invalid skill name: ${skill}`);
|
|
108
|
+
}
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const profiles = readProfiles();
|
|
113
|
+
if (profiles[name]) {
|
|
114
|
+
if (json) {
|
|
115
|
+
console.log(JSON.stringify({ error: `Profile "${name}" already exists` }));
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
console.error(`Profile "${name}" already exists. Delete it first or choose a different name.`);
|
|
119
|
+
}
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
profiles[name] = skills;
|
|
123
|
+
writeProfiles(profiles);
|
|
124
|
+
if (json) {
|
|
125
|
+
console.log(JSON.stringify({ created: name, skills }));
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
console.log(`Created profile "${name}" with ${skills.length} skill(s): ${skills.join(", ")}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function deleteProfile(name, json) {
|
|
132
|
+
if (!name) {
|
|
133
|
+
if (json) {
|
|
134
|
+
console.log(JSON.stringify({ error: "Profile name is required" }));
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
console.error("Profile name is required.");
|
|
138
|
+
console.error("Usage: arcana profile delete <name>");
|
|
139
|
+
}
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
const profiles = readProfiles();
|
|
143
|
+
if (!profiles[name]) {
|
|
144
|
+
if (json) {
|
|
145
|
+
console.log(JSON.stringify({ error: `Profile "${name}" not found` }));
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
console.error(`Profile "${name}" not found.`);
|
|
149
|
+
}
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
delete profiles[name];
|
|
153
|
+
writeProfiles(profiles);
|
|
154
|
+
if (json) {
|
|
155
|
+
console.log(JSON.stringify({ deleted: name }));
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
console.log(`Deleted profile "${name}".`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function showProfile(name, json) {
|
|
162
|
+
if (!name) {
|
|
163
|
+
if (json) {
|
|
164
|
+
console.log(JSON.stringify({ error: "Profile name is required" }));
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
console.error("Profile name is required.");
|
|
168
|
+
console.error("Usage: arcana profile show <name>");
|
|
169
|
+
}
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
const profiles = readProfiles();
|
|
173
|
+
const skills = profiles[name];
|
|
174
|
+
if (!skills) {
|
|
175
|
+
if (json) {
|
|
176
|
+
console.log(JSON.stringify({ error: `Profile "${name}" not found` }));
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
console.error(`Profile "${name}" not found.`);
|
|
180
|
+
}
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
if (json) {
|
|
184
|
+
console.log(JSON.stringify({ name, skills }));
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
console.log(`Profile "${name}" (${skills.length} skill(s)):\n`);
|
|
188
|
+
for (const skill of skills) {
|
|
189
|
+
console.log(` - ${skill}`);
|
|
190
|
+
}
|
|
191
|
+
console.log();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async function applyProfile(name, json) {
|
|
195
|
+
if (!name) {
|
|
196
|
+
if (json) {
|
|
197
|
+
console.log(JSON.stringify({ error: "Profile name is required" }));
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
console.error("Profile name is required.");
|
|
201
|
+
console.error("Usage: arcana profile apply <name>");
|
|
202
|
+
}
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
const profiles = readProfiles();
|
|
206
|
+
const skills = profiles[name];
|
|
207
|
+
if (!skills) {
|
|
208
|
+
if (json) {
|
|
209
|
+
console.log(JSON.stringify({ error: `Profile "${name}" not found` }));
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
console.error(`Profile "${name}" not found.`);
|
|
213
|
+
}
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
if (skills.length === 0) {
|
|
217
|
+
if (json) {
|
|
218
|
+
console.log(JSON.stringify({ applied: name, installed: [], skipped: [], failed: [] }));
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
console.log(`Profile "${name}" has no skills.`);
|
|
222
|
+
}
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const { getProvider } = await import("../registry.js");
|
|
226
|
+
const { installSkill, writeSkillMeta, isSkillInstalled } = await import("../utils/fs.js");
|
|
227
|
+
const { loadConfig } = await import("../utils/config.js");
|
|
228
|
+
const config = loadConfig();
|
|
229
|
+
const provider = getProvider(config.defaultProvider);
|
|
230
|
+
const installed = [];
|
|
231
|
+
const skipped = [];
|
|
232
|
+
const failed = [];
|
|
233
|
+
for (const skillName of skills) {
|
|
234
|
+
if (isSkillInstalled(skillName)) {
|
|
235
|
+
skipped.push(skillName);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
const files = await provider.fetch(skillName);
|
|
240
|
+
installSkill(skillName, files);
|
|
241
|
+
const remote = await provider.info(skillName);
|
|
242
|
+
writeSkillMeta(skillName, {
|
|
243
|
+
version: remote?.version ?? "0.0.0",
|
|
244
|
+
installedAt: new Date().toISOString(),
|
|
245
|
+
source: provider.name,
|
|
246
|
+
description: remote?.description,
|
|
247
|
+
fileCount: files.length,
|
|
248
|
+
sizeBytes: files.reduce((s, f) => s + f.content.length, 0),
|
|
249
|
+
});
|
|
250
|
+
installed.push(skillName);
|
|
251
|
+
}
|
|
252
|
+
catch (err) {
|
|
253
|
+
failed.push(skillName);
|
|
254
|
+
if (!json && err instanceof Error) {
|
|
255
|
+
console.error(` Failed to install ${skillName}: ${err.message}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (json) {
|
|
260
|
+
console.log(JSON.stringify({ applied: name, installed, skipped, failed }));
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
console.log(`Applied profile "${name}":`);
|
|
264
|
+
if (installed.length > 0)
|
|
265
|
+
console.log(` Installed: ${installed.join(", ")}`);
|
|
266
|
+
if (skipped.length > 0)
|
|
267
|
+
console.log(` Skipped (already installed): ${skipped.join(", ")}`);
|
|
268
|
+
if (failed.length > 0)
|
|
269
|
+
console.log(` Failed: ${failed.join(", ")}`);
|
|
270
|
+
console.log();
|
|
271
|
+
}
|
|
272
|
+
if (failed.length > 0)
|
|
273
|
+
process.exit(1);
|
|
274
|
+
}
|
|
@@ -87,9 +87,7 @@ export async function providersCommand(opts) {
|
|
|
87
87
|
console.log(ui.bold(" Configured providers:"));
|
|
88
88
|
console.log();
|
|
89
89
|
const rows = config.providers.map((p) => [
|
|
90
|
-
p.name === config.defaultProvider
|
|
91
|
-
? ui.brand(p.name) + ui.dim(" (default)")
|
|
92
|
-
: ui.bold(p.name),
|
|
90
|
+
p.name === config.defaultProvider ? ui.brand(p.name) + ui.dim(" (default)") : ui.bold(p.name),
|
|
93
91
|
ui.dim(p.type),
|
|
94
92
|
ui.dim(p.url),
|
|
95
93
|
p.enabled ? ui.success("enabled") : ui.dim("disabled"),
|
|
@@ -100,4 +98,3 @@ export async function providersCommand(opts) {
|
|
|
100
98
|
console.log(ui.dim(" Remove: arcana providers --remove name"));
|
|
101
99
|
console.log();
|
|
102
100
|
}
|
|
103
|
-
//# sourceMappingURL=providers.js.map
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import * as p from "@clack/prompts";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { detectProjectContext } from "../utils/project-context.js";
|
|
4
|
+
import { rankSkills } from "../utils/scoring.js";
|
|
5
|
+
import { getProviders } from "../registry.js";
|
|
6
|
+
export async function recommendCommand(opts) {
|
|
7
|
+
const cwd = process.cwd();
|
|
8
|
+
const context = detectProjectContext(cwd);
|
|
9
|
+
if (!opts.json) {
|
|
10
|
+
p.intro(chalk.bold("Smart Recommendations"));
|
|
11
|
+
p.log.step(`Project: ${chalk.cyan(context.name)} (${context.type} / ${context.lang})`);
|
|
12
|
+
if (context.tags.length > 0) {
|
|
13
|
+
p.log.info(`Tags detected: ${context.tags.join(", ")}`);
|
|
14
|
+
}
|
|
15
|
+
if (context.ruleFiles.length > 0) {
|
|
16
|
+
p.log.info(`Rules found: ${context.ruleFiles.join(", ")}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
// Fetch all skills from providers
|
|
20
|
+
const providers = getProviders(opts.provider);
|
|
21
|
+
const allSkills = [];
|
|
22
|
+
for (const prov of providers) {
|
|
23
|
+
try {
|
|
24
|
+
const skills = await prov.list();
|
|
25
|
+
allSkills.push(...skills);
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
if (!opts.json) {
|
|
29
|
+
p.log.warn(`Could not fetch from ${prov.displayName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (allSkills.length === 0) {
|
|
34
|
+
if (opts.json) {
|
|
35
|
+
console.log(JSON.stringify({ error: "No skills available" }));
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
p.log.error("No skills available from any provider.");
|
|
39
|
+
}
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
// Score and rank
|
|
43
|
+
const verdicts = rankSkills(allSkills, context);
|
|
44
|
+
const limit = opts.limit ?? Infinity;
|
|
45
|
+
const recommended = verdicts.filter((v) => v.verdict === "recommended").slice(0, limit);
|
|
46
|
+
const optional = verdicts.filter((v) => v.verdict === "optional").slice(0, limit);
|
|
47
|
+
const conflicts = verdicts.filter((v) => v.verdict === "conflict");
|
|
48
|
+
const skipped = verdicts.filter((v) => v.verdict === "skip");
|
|
49
|
+
if (opts.json) {
|
|
50
|
+
const output = {
|
|
51
|
+
project: { name: context.name, type: context.type, lang: context.lang, tags: context.tags },
|
|
52
|
+
recommended: recommended.map(formatVerdict),
|
|
53
|
+
optional: optional.map(formatVerdict),
|
|
54
|
+
conflicts: conflicts.map(formatVerdict),
|
|
55
|
+
skippedCount: skipped.length,
|
|
56
|
+
};
|
|
57
|
+
console.log(JSON.stringify(output, null, 2));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
// Display results
|
|
61
|
+
if (recommended.length > 0) {
|
|
62
|
+
console.log();
|
|
63
|
+
console.log(chalk.green.bold(" RECOMMENDED"));
|
|
64
|
+
for (const v of recommended) {
|
|
65
|
+
printVerdict(v);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (optional.length > 0) {
|
|
69
|
+
console.log();
|
|
70
|
+
console.log(chalk.yellow.bold(" OPTIONAL"));
|
|
71
|
+
for (const v of optional) {
|
|
72
|
+
printVerdict(v);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (conflicts.length > 0) {
|
|
76
|
+
console.log();
|
|
77
|
+
console.log(chalk.red.bold(" CONFLICTS"));
|
|
78
|
+
for (const v of conflicts) {
|
|
79
|
+
console.log(` ${chalk.red(v.skill.padEnd(28))} ${chalk.red("!!")} ${v.reasons.join(" | ")}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const skipCount = skipped.length;
|
|
83
|
+
if (skipCount > 0) {
|
|
84
|
+
console.log();
|
|
85
|
+
console.log(chalk.dim(` ${skipCount} skill${skipCount === 1 ? "" : "s"} skipped (installed or no relevance)`));
|
|
86
|
+
}
|
|
87
|
+
console.log();
|
|
88
|
+
p.outro(`Install: ${chalk.cyan("arcana install <skill>")}`);
|
|
89
|
+
}
|
|
90
|
+
function printVerdict(v) {
|
|
91
|
+
const scoreStr = v.score > 0 ? `+${v.score}` : String(v.score);
|
|
92
|
+
console.log(` ${chalk.bold(v.skill.padEnd(28))} ${chalk.cyan(scoreStr.padStart(4))} ${chalk.dim(v.reasons.join(" | "))}`);
|
|
93
|
+
}
|
|
94
|
+
function formatVerdict(v) {
|
|
95
|
+
return { skill: v.skill, score: v.score, reasons: v.reasons };
|
|
96
|
+
}
|
package/dist/commands/scan.d.ts
CHANGED
package/dist/commands/scan.js
CHANGED
|
@@ -59,9 +59,9 @@ export async function scanCommand(skill, opts) {
|
|
|
59
59
|
}
|
|
60
60
|
else {
|
|
61
61
|
totalIssues += issues.length;
|
|
62
|
-
criticalCount += issues.filter(i => i.level === "critical").length;
|
|
63
|
-
highCount += issues.filter(i => i.level === "high").length;
|
|
64
|
-
mediumCount += issues.filter(i => i.level === "medium").length;
|
|
62
|
+
criticalCount += issues.filter((i) => i.level === "critical").length;
|
|
63
|
+
highCount += issues.filter((i) => i.level === "high").length;
|
|
64
|
+
mediumCount += issues.filter((i) => i.level === "medium").length;
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
catch (err) {
|
|
@@ -70,11 +70,18 @@ export async function scanCommand(skill, opts) {
|
|
|
70
70
|
}
|
|
71
71
|
if (opts.json) {
|
|
72
72
|
console.log(JSON.stringify({
|
|
73
|
-
summary: {
|
|
74
|
-
|
|
73
|
+
summary: {
|
|
74
|
+
total: skills.length,
|
|
75
|
+
clean: cleanCount,
|
|
76
|
+
issues: totalIssues,
|
|
77
|
+
critical: criticalCount,
|
|
78
|
+
high: highCount,
|
|
79
|
+
medium: mediumCount,
|
|
80
|
+
},
|
|
81
|
+
results: results.map((r) => ({
|
|
75
82
|
skill: r.skill,
|
|
76
83
|
...(r.error ? { error: r.error } : {}),
|
|
77
|
-
issues: r.issues.map(i => ({ level: i.level, category: i.category, detail: i.detail, line: i.line })),
|
|
84
|
+
issues: r.issues.map((i) => ({ level: i.level, category: i.category, detail: i.detail, line: i.line })),
|
|
78
85
|
})),
|
|
79
86
|
}, null, 2));
|
|
80
87
|
if (criticalCount > 0)
|
|
@@ -107,4 +114,3 @@ export async function scanCommand(skill, opts) {
|
|
|
107
114
|
if (criticalCount > 0)
|
|
108
115
|
process.exit(1);
|
|
109
116
|
}
|
|
110
|
-
//# sourceMappingURL=scan.js.map
|
package/dist/commands/search.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ui, banner, spinner, noopSpinner, table, printErrorWithHint } from "../utils/ui.js";
|
|
2
2
|
import { isSkillInstalled } from "../utils/fs.js";
|
|
3
3
|
import { getProviders } from "../registry.js";
|
|
4
|
+
import { detectProjectContext } from "../utils/project-context.js";
|
|
4
5
|
export async function searchCommand(query, opts) {
|
|
5
6
|
if (!opts.json)
|
|
6
7
|
banner();
|
|
@@ -11,15 +12,13 @@ export async function searchCommand(query, opts) {
|
|
|
11
12
|
}
|
|
12
13
|
const s = opts.json ? noopSpinner() : spinner(`Searching for "${query}"...`);
|
|
13
14
|
s.start();
|
|
14
|
-
|
|
15
|
+
let results = [];
|
|
15
16
|
try {
|
|
16
17
|
for (const provider of providers) {
|
|
17
18
|
const skills = await provider.search(query);
|
|
18
19
|
for (const skill of skills) {
|
|
19
20
|
results.push({
|
|
20
|
-
|
|
21
|
-
description: skill.description,
|
|
22
|
-
source: skill.source,
|
|
21
|
+
...skill,
|
|
23
22
|
installed: isSkillInstalled(skill.name),
|
|
24
23
|
});
|
|
25
24
|
}
|
|
@@ -34,20 +33,45 @@ export async function searchCommand(query, opts) {
|
|
|
34
33
|
printErrorWithHint(err, true);
|
|
35
34
|
process.exit(1);
|
|
36
35
|
}
|
|
36
|
+
// Filter by tag
|
|
37
|
+
if (opts.tag) {
|
|
38
|
+
const tag = opts.tag.toLowerCase();
|
|
39
|
+
results = results.filter((r) => r.tags?.some((t) => t.toLowerCase() === tag));
|
|
40
|
+
}
|
|
41
|
+
// Smart ranking: boost results matching project context
|
|
42
|
+
if (opts.smart) {
|
|
43
|
+
const context = detectProjectContext(process.cwd());
|
|
44
|
+
results.sort((a, b) => {
|
|
45
|
+
const aScore = (a.tags ?? []).filter((t) => context.tags.includes(t)).length;
|
|
46
|
+
const bScore = (b.tags ?? []).filter((t) => context.tags.includes(t)).length;
|
|
47
|
+
return bScore - aScore;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
37
50
|
s.stop();
|
|
38
51
|
if (opts.json) {
|
|
39
|
-
console.log(JSON.stringify({
|
|
52
|
+
console.log(JSON.stringify({
|
|
53
|
+
query,
|
|
54
|
+
results: results.map((r) => ({
|
|
55
|
+
name: r.name,
|
|
56
|
+
description: r.description,
|
|
57
|
+
source: r.source,
|
|
58
|
+
installed: r.installed,
|
|
59
|
+
tags: r.tags,
|
|
60
|
+
verified: r.verified,
|
|
61
|
+
})),
|
|
62
|
+
}, null, 2));
|
|
40
63
|
return;
|
|
41
64
|
}
|
|
42
65
|
if (results.length === 0) {
|
|
43
|
-
console.log(ui.dim(` No skills matching "${query}"`));
|
|
66
|
+
console.log(ui.dim(` No skills matching "${query}"${opts.tag ? ` with tag "${opts.tag}"` : ""}`));
|
|
44
67
|
}
|
|
45
68
|
else {
|
|
46
69
|
console.log(ui.bold(` ${results.length} results for "${query}":`));
|
|
47
70
|
console.log();
|
|
48
71
|
const rows = results.map((r) => [
|
|
49
|
-
ui.bold(r.name),
|
|
50
|
-
r.description.slice(0,
|
|
72
|
+
ui.bold(r.name) + (r.verified ? " " + ui.success("[V]") : ""),
|
|
73
|
+
r.description.slice(0, 60) + (r.description.length > 60 ? "..." : ""),
|
|
74
|
+
r.tags?.slice(0, 3).join(", ") ?? "",
|
|
51
75
|
ui.dim(r.source),
|
|
52
76
|
r.installed ? ui.success("[installed]") : "",
|
|
53
77
|
]);
|
|
@@ -55,4 +79,3 @@ export async function searchCommand(query, opts) {
|
|
|
55
79
|
}
|
|
56
80
|
console.log();
|
|
57
81
|
}
|
|
58
|
-
//# sourceMappingURL=search.js.map
|
package/dist/commands/stats.d.ts
CHANGED