@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.
Files changed (241) hide show
  1. package/dist/cli.d.ts +0 -1
  2. package/dist/cli.js +140 -10
  3. package/dist/command-registry.d.ts +10 -0
  4. package/dist/command-registry.js +65 -0
  5. package/dist/commands/audit.d.ts +0 -1
  6. package/dist/commands/audit.js +16 -6
  7. package/dist/commands/benchmark.d.ts +4 -0
  8. package/dist/commands/benchmark.js +178 -0
  9. package/dist/commands/clean.d.ts +2 -1
  10. package/dist/commands/clean.js +198 -47
  11. package/dist/commands/compact.d.ts +6 -0
  12. package/dist/commands/compact.js +239 -0
  13. package/dist/commands/completions.d.ts +3 -0
  14. package/dist/commands/completions.js +104 -0
  15. package/dist/commands/config.d.ts +0 -1
  16. package/dist/commands/config.js +15 -6
  17. package/dist/commands/create.d.ts +0 -1
  18. package/dist/commands/create.js +1 -1
  19. package/dist/commands/diff.d.ts +4 -0
  20. package/dist/commands/diff.js +166 -0
  21. package/dist/commands/doctor.d.ts +0 -1
  22. package/dist/commands/doctor.js +153 -24
  23. package/dist/commands/export-cmd.d.ts +4 -0
  24. package/dist/commands/export-cmd.js +66 -0
  25. package/dist/commands/import-cmd.d.ts +4 -0
  26. package/dist/commands/import-cmd.js +131 -0
  27. package/dist/commands/info.d.ts +0 -1
  28. package/dist/commands/info.js +29 -4
  29. package/dist/commands/init.d.ts +0 -1
  30. package/dist/commands/init.js +156 -117
  31. package/dist/commands/install.d.ts +1 -1
  32. package/dist/commands/install.js +118 -205
  33. package/dist/commands/list.d.ts +0 -1
  34. package/dist/commands/list.js +12 -4
  35. package/dist/commands/lock.d.ts +4 -0
  36. package/dist/commands/lock.js +171 -0
  37. package/dist/commands/optimize.d.ts +3 -0
  38. package/dist/commands/optimize.js +356 -0
  39. package/dist/commands/outdated.d.ts +4 -0
  40. package/dist/commands/outdated.js +159 -0
  41. package/dist/commands/profile.d.ts +3 -0
  42. package/dist/commands/profile.js +274 -0
  43. package/dist/commands/providers.d.ts +0 -1
  44. package/dist/commands/providers.js +1 -4
  45. package/dist/commands/recommend.d.ts +5 -0
  46. package/dist/commands/recommend.js +96 -0
  47. package/dist/commands/scan.d.ts +0 -1
  48. package/dist/commands/scan.js +13 -7
  49. package/dist/commands/search.d.ts +2 -1
  50. package/dist/commands/search.js +32 -9
  51. package/dist/commands/stats.d.ts +0 -1
  52. package/dist/commands/stats.js +83 -16
  53. package/dist/commands/team.d.ts +3 -0
  54. package/dist/commands/team.js +291 -0
  55. package/dist/commands/uninstall.d.ts +0 -1
  56. package/dist/commands/uninstall.js +18 -4
  57. package/dist/commands/update.d.ts +0 -1
  58. package/dist/commands/update.js +155 -155
  59. package/dist/commands/validate.d.ts +0 -1
  60. package/dist/commands/validate.js +14 -6
  61. package/dist/commands/verify.d.ts +4 -0
  62. package/dist/commands/verify.js +116 -0
  63. package/dist/constants.d.ts +10 -0
  64. package/dist/constants.js +13 -0
  65. package/dist/index.d.ts +0 -1
  66. package/dist/index.js +0 -1
  67. package/dist/interactive/browse.d.ts +4 -0
  68. package/dist/interactive/browse.js +103 -0
  69. package/dist/interactive/categories.d.ts +4 -0
  70. package/dist/interactive/categories.js +87 -0
  71. package/dist/interactive/health.d.ts +1 -0
  72. package/dist/interactive/health.js +57 -0
  73. package/dist/interactive/helpers.d.ts +11 -0
  74. package/dist/interactive/helpers.js +66 -0
  75. package/dist/interactive/index.d.ts +1 -0
  76. package/dist/interactive/index.js +1 -0
  77. package/dist/interactive/manage.d.ts +2 -0
  78. package/dist/interactive/manage.js +187 -0
  79. package/dist/interactive/menu.d.ts +1 -0
  80. package/dist/interactive/menu.js +107 -0
  81. package/dist/interactive/search.d.ts +2 -0
  82. package/dist/interactive/search.js +66 -0
  83. package/dist/interactive/setup.d.ts +2 -0
  84. package/dist/interactive/setup.js +48 -0
  85. package/dist/interactive/skill-detail.d.ts +5 -0
  86. package/dist/interactive/skill-detail.js +126 -0
  87. package/dist/interactive.d.ts +0 -1
  88. package/dist/interactive.js +89 -66
  89. package/dist/providers/arcana.d.ts +0 -1
  90. package/dist/providers/arcana.js +0 -1
  91. package/dist/providers/base.d.ts +0 -1
  92. package/dist/providers/base.js +0 -1
  93. package/dist/providers/github.d.ts +0 -1
  94. package/dist/providers/github.js +8 -3
  95. package/dist/registry.d.ts +0 -1
  96. package/dist/registry.js +1 -4
  97. package/dist/types.d.ts +10 -1
  98. package/dist/types.js +0 -1
  99. package/dist/utils/atomic.d.ts +0 -1
  100. package/dist/utils/atomic.js +3 -2
  101. package/dist/utils/cache.d.ts +0 -1
  102. package/dist/utils/cache.js +3 -2
  103. package/dist/utils/config.d.ts +2 -1
  104. package/dist/utils/config.js +30 -5
  105. package/dist/utils/conflict-check.d.ts +8 -0
  106. package/dist/utils/conflict-check.js +72 -0
  107. package/dist/utils/errors.d.ts +0 -1
  108. package/dist/utils/errors.js +0 -1
  109. package/dist/utils/frontmatter.d.ts +0 -1
  110. package/dist/utils/frontmatter.js +37 -10
  111. package/dist/utils/fs.d.ts +19 -1
  112. package/dist/utils/fs.js +105 -8
  113. package/dist/utils/help.d.ts +0 -1
  114. package/dist/utils/help.js +15 -28
  115. package/dist/utils/history.d.ts +0 -1
  116. package/dist/utils/history.js +0 -1
  117. package/dist/utils/http.d.ts +0 -1
  118. package/dist/utils/http.js +14 -5
  119. package/dist/utils/install-core.d.ts +48 -0
  120. package/dist/utils/install-core.js +108 -0
  121. package/dist/utils/integrity.d.ts +17 -0
  122. package/dist/utils/integrity.js +84 -0
  123. package/dist/utils/parallel.d.ts +0 -1
  124. package/dist/utils/parallel.js +0 -1
  125. package/dist/utils/project-context.d.ts +19 -0
  126. package/dist/utils/project-context.js +283 -0
  127. package/dist/utils/scanner.d.ts +0 -1
  128. package/dist/utils/scanner.js +138 -10
  129. package/dist/utils/scoring.d.ts +10 -0
  130. package/dist/utils/scoring.js +84 -0
  131. package/dist/utils/ui.d.ts +0 -1
  132. package/dist/utils/ui.js +11 -4
  133. package/dist/utils/validate.d.ts +0 -1
  134. package/dist/utils/validate.js +4 -1
  135. package/package.json +19 -7
  136. package/dist/cli.d.ts.map +0 -1
  137. package/dist/cli.js.map +0 -1
  138. package/dist/commands/audit.d.ts.map +0 -1
  139. package/dist/commands/audit.js.map +0 -1
  140. package/dist/commands/audit.test.d.ts +0 -2
  141. package/dist/commands/audit.test.d.ts.map +0 -1
  142. package/dist/commands/audit.test.js +0 -217
  143. package/dist/commands/audit.test.js.map +0 -1
  144. package/dist/commands/clean.d.ts.map +0 -1
  145. package/dist/commands/clean.js.map +0 -1
  146. package/dist/commands/config.d.ts.map +0 -1
  147. package/dist/commands/config.js.map +0 -1
  148. package/dist/commands/create.d.ts.map +0 -1
  149. package/dist/commands/create.js.map +0 -1
  150. package/dist/commands/doctor.d.ts.map +0 -1
  151. package/dist/commands/doctor.js.map +0 -1
  152. package/dist/commands/info.d.ts.map +0 -1
  153. package/dist/commands/info.js.map +0 -1
  154. package/dist/commands/init.d.ts.map +0 -1
  155. package/dist/commands/init.js.map +0 -1
  156. package/dist/commands/install.d.ts.map +0 -1
  157. package/dist/commands/install.js.map +0 -1
  158. package/dist/commands/list.d.ts.map +0 -1
  159. package/dist/commands/list.js.map +0 -1
  160. package/dist/commands/providers.d.ts.map +0 -1
  161. package/dist/commands/providers.js.map +0 -1
  162. package/dist/commands/scan.d.ts.map +0 -1
  163. package/dist/commands/scan.js.map +0 -1
  164. package/dist/commands/search.d.ts.map +0 -1
  165. package/dist/commands/search.js.map +0 -1
  166. package/dist/commands/stats.d.ts.map +0 -1
  167. package/dist/commands/stats.js.map +0 -1
  168. package/dist/commands/uninstall.d.ts.map +0 -1
  169. package/dist/commands/uninstall.js.map +0 -1
  170. package/dist/commands/update.d.ts.map +0 -1
  171. package/dist/commands/update.js.map +0 -1
  172. package/dist/commands/validate.d.ts.map +0 -1
  173. package/dist/commands/validate.js.map +0 -1
  174. package/dist/index.d.ts.map +0 -1
  175. package/dist/index.js.map +0 -1
  176. package/dist/interactive.d.ts.map +0 -1
  177. package/dist/interactive.js.map +0 -1
  178. package/dist/providers/arcana.d.ts.map +0 -1
  179. package/dist/providers/arcana.js.map +0 -1
  180. package/dist/providers/base.d.ts.map +0 -1
  181. package/dist/providers/base.js.map +0 -1
  182. package/dist/providers/github.d.ts.map +0 -1
  183. package/dist/providers/github.js.map +0 -1
  184. package/dist/registry.d.ts.map +0 -1
  185. package/dist/registry.js.map +0 -1
  186. package/dist/types.d.ts.map +0 -1
  187. package/dist/types.js.map +0 -1
  188. package/dist/utils/atomic.d.ts.map +0 -1
  189. package/dist/utils/atomic.js.map +0 -1
  190. package/dist/utils/atomic.test.d.ts +0 -2
  191. package/dist/utils/atomic.test.d.ts.map +0 -1
  192. package/dist/utils/atomic.test.js +0 -31
  193. package/dist/utils/atomic.test.js.map +0 -1
  194. package/dist/utils/cache.d.ts.map +0 -1
  195. package/dist/utils/cache.js.map +0 -1
  196. package/dist/utils/config.d.ts.map +0 -1
  197. package/dist/utils/config.js.map +0 -1
  198. package/dist/utils/config.test.d.ts +0 -2
  199. package/dist/utils/config.test.d.ts.map +0 -1
  200. package/dist/utils/config.test.js +0 -38
  201. package/dist/utils/config.test.js.map +0 -1
  202. package/dist/utils/errors.d.ts.map +0 -1
  203. package/dist/utils/errors.js.map +0 -1
  204. package/dist/utils/frontmatter.d.ts.map +0 -1
  205. package/dist/utils/frontmatter.js.map +0 -1
  206. package/dist/utils/frontmatter.test.d.ts +0 -2
  207. package/dist/utils/frontmatter.test.d.ts.map +0 -1
  208. package/dist/utils/frontmatter.test.js +0 -152
  209. package/dist/utils/frontmatter.test.js.map +0 -1
  210. package/dist/utils/fs.d.ts.map +0 -1
  211. package/dist/utils/fs.js.map +0 -1
  212. package/dist/utils/fs.test.d.ts +0 -2
  213. package/dist/utils/fs.test.d.ts.map +0 -1
  214. package/dist/utils/fs.test.js +0 -145
  215. package/dist/utils/fs.test.js.map +0 -1
  216. package/dist/utils/help.d.ts.map +0 -1
  217. package/dist/utils/help.js.map +0 -1
  218. package/dist/utils/help.test.d.ts +0 -2
  219. package/dist/utils/help.test.d.ts.map +0 -1
  220. package/dist/utils/help.test.js +0 -66
  221. package/dist/utils/help.test.js.map +0 -1
  222. package/dist/utils/history.d.ts.map +0 -1
  223. package/dist/utils/history.js.map +0 -1
  224. package/dist/utils/http.d.ts.map +0 -1
  225. package/dist/utils/http.js.map +0 -1
  226. package/dist/utils/http.test.d.ts +0 -2
  227. package/dist/utils/http.test.d.ts.map +0 -1
  228. package/dist/utils/http.test.js +0 -55
  229. package/dist/utils/http.test.js.map +0 -1
  230. package/dist/utils/parallel.d.ts.map +0 -1
  231. package/dist/utils/parallel.js.map +0 -1
  232. package/dist/utils/scanner.d.ts.map +0 -1
  233. package/dist/utils/scanner.js.map +0 -1
  234. package/dist/utils/ui.d.ts.map +0 -1
  235. package/dist/utils/ui.js.map +0 -1
  236. package/dist/utils/ui.test.d.ts +0 -2
  237. package/dist/utils/ui.test.d.ts.map +0 -1
  238. package/dist/utils/ui.test.js +0 -31
  239. package/dist/utils/ui.test.js.map +0 -1
  240. package/dist/utils/validate.d.ts.map +0 -1
  241. 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
+ }
@@ -3,4 +3,3 @@ export declare function providersCommand(opts: {
3
3
  remove?: string;
4
4
  json?: boolean;
5
5
  }): Promise<void>;
6
- //# sourceMappingURL=providers.d.ts.map
@@ -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,5 @@
1
+ export declare function recommendCommand(opts: {
2
+ json?: boolean;
3
+ limit?: number;
4
+ provider?: string;
5
+ }): Promise<void>;
@@ -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
+ }
@@ -2,4 +2,3 @@ export declare function scanCommand(skill: string | undefined, opts: {
2
2
  all?: boolean;
3
3
  json?: boolean;
4
4
  }): Promise<void>;
5
- //# sourceMappingURL=scan.d.ts.map
@@ -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: { total: skills.length, clean: cleanCount, issues: totalIssues, critical: criticalCount, high: highCount, medium: mediumCount },
74
- results: results.map(r => ({
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
@@ -2,5 +2,6 @@ export declare function searchCommand(query: string, opts: {
2
2
  provider?: string;
3
3
  cache?: boolean;
4
4
  json?: boolean;
5
+ tag?: string;
6
+ smart?: boolean;
5
7
  }): Promise<void>;
6
- //# sourceMappingURL=search.d.ts.map
@@ -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
- const results = [];
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
- name: skill.name,
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({ query, results }, null, 2));
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, 80) + (r.description.length > 80 ? "..." : ""),
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
@@ -1,4 +1,3 @@
1
1
  export declare function statsCommand(opts: {
2
2
  json?: boolean;
3
3
  }): Promise<void>;
4
- //# sourceMappingURL=stats.d.ts.map