@sporesec/arcana 2.4.0 → 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 (245) hide show
  1. package/dist/cli.d.ts +0 -1
  2. package/dist/cli.js +120 -9
  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 +0 -1
  10. package/dist/commands/clean.js +19 -8
  11. package/dist/commands/compact.d.ts +2 -1
  12. package/dist/commands/compact.js +74 -14
  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 +64 -23
  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 +26 -33
  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 +0 -1
  38. package/dist/commands/optimize.js +111 -20
  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 +24 -20
  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 +0 -1
  112. package/dist/utils/fs.js +30 -11
  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 +74 -62
  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/compact.d.ts.map +0 -1
  147. package/dist/commands/compact.js.map +0 -1
  148. package/dist/commands/config.d.ts.map +0 -1
  149. package/dist/commands/config.js.map +0 -1
  150. package/dist/commands/create.d.ts.map +0 -1
  151. package/dist/commands/create.js.map +0 -1
  152. package/dist/commands/doctor.d.ts.map +0 -1
  153. package/dist/commands/doctor.js.map +0 -1
  154. package/dist/commands/info.d.ts.map +0 -1
  155. package/dist/commands/info.js.map +0 -1
  156. package/dist/commands/init.d.ts.map +0 -1
  157. package/dist/commands/init.js.map +0 -1
  158. package/dist/commands/install.d.ts.map +0 -1
  159. package/dist/commands/install.js.map +0 -1
  160. package/dist/commands/list.d.ts.map +0 -1
  161. package/dist/commands/list.js.map +0 -1
  162. package/dist/commands/optimize.d.ts.map +0 -1
  163. package/dist/commands/optimize.js.map +0 -1
  164. package/dist/commands/providers.d.ts.map +0 -1
  165. package/dist/commands/providers.js.map +0 -1
  166. package/dist/commands/scan.d.ts.map +0 -1
  167. package/dist/commands/scan.js.map +0 -1
  168. package/dist/commands/search.d.ts.map +0 -1
  169. package/dist/commands/search.js.map +0 -1
  170. package/dist/commands/stats.d.ts.map +0 -1
  171. package/dist/commands/stats.js.map +0 -1
  172. package/dist/commands/uninstall.d.ts.map +0 -1
  173. package/dist/commands/uninstall.js.map +0 -1
  174. package/dist/commands/update.d.ts.map +0 -1
  175. package/dist/commands/update.js.map +0 -1
  176. package/dist/commands/validate.d.ts.map +0 -1
  177. package/dist/commands/validate.js.map +0 -1
  178. package/dist/index.d.ts.map +0 -1
  179. package/dist/index.js.map +0 -1
  180. package/dist/interactive.d.ts.map +0 -1
  181. package/dist/interactive.js.map +0 -1
  182. package/dist/providers/arcana.d.ts.map +0 -1
  183. package/dist/providers/arcana.js.map +0 -1
  184. package/dist/providers/base.d.ts.map +0 -1
  185. package/dist/providers/base.js.map +0 -1
  186. package/dist/providers/github.d.ts.map +0 -1
  187. package/dist/providers/github.js.map +0 -1
  188. package/dist/registry.d.ts.map +0 -1
  189. package/dist/registry.js.map +0 -1
  190. package/dist/types.d.ts.map +0 -1
  191. package/dist/types.js.map +0 -1
  192. package/dist/utils/atomic.d.ts.map +0 -1
  193. package/dist/utils/atomic.js.map +0 -1
  194. package/dist/utils/atomic.test.d.ts +0 -2
  195. package/dist/utils/atomic.test.d.ts.map +0 -1
  196. package/dist/utils/atomic.test.js +0 -31
  197. package/dist/utils/atomic.test.js.map +0 -1
  198. package/dist/utils/cache.d.ts.map +0 -1
  199. package/dist/utils/cache.js.map +0 -1
  200. package/dist/utils/config.d.ts.map +0 -1
  201. package/dist/utils/config.js.map +0 -1
  202. package/dist/utils/config.test.d.ts +0 -2
  203. package/dist/utils/config.test.d.ts.map +0 -1
  204. package/dist/utils/config.test.js +0 -38
  205. package/dist/utils/config.test.js.map +0 -1
  206. package/dist/utils/errors.d.ts.map +0 -1
  207. package/dist/utils/errors.js.map +0 -1
  208. package/dist/utils/frontmatter.d.ts.map +0 -1
  209. package/dist/utils/frontmatter.js.map +0 -1
  210. package/dist/utils/frontmatter.test.d.ts +0 -2
  211. package/dist/utils/frontmatter.test.d.ts.map +0 -1
  212. package/dist/utils/frontmatter.test.js +0 -152
  213. package/dist/utils/frontmatter.test.js.map +0 -1
  214. package/dist/utils/fs.d.ts.map +0 -1
  215. package/dist/utils/fs.js.map +0 -1
  216. package/dist/utils/fs.test.d.ts +0 -2
  217. package/dist/utils/fs.test.d.ts.map +0 -1
  218. package/dist/utils/fs.test.js +0 -145
  219. package/dist/utils/fs.test.js.map +0 -1
  220. package/dist/utils/help.d.ts.map +0 -1
  221. package/dist/utils/help.js.map +0 -1
  222. package/dist/utils/help.test.d.ts +0 -2
  223. package/dist/utils/help.test.d.ts.map +0 -1
  224. package/dist/utils/help.test.js +0 -66
  225. package/dist/utils/help.test.js.map +0 -1
  226. package/dist/utils/history.d.ts.map +0 -1
  227. package/dist/utils/history.js.map +0 -1
  228. package/dist/utils/http.d.ts.map +0 -1
  229. package/dist/utils/http.js.map +0 -1
  230. package/dist/utils/http.test.d.ts +0 -2
  231. package/dist/utils/http.test.d.ts.map +0 -1
  232. package/dist/utils/http.test.js +0 -55
  233. package/dist/utils/http.test.js.map +0 -1
  234. package/dist/utils/parallel.d.ts.map +0 -1
  235. package/dist/utils/parallel.js.map +0 -1
  236. package/dist/utils/scanner.d.ts.map +0 -1
  237. package/dist/utils/scanner.js.map +0 -1
  238. package/dist/utils/ui.d.ts.map +0 -1
  239. package/dist/utils/ui.js.map +0 -1
  240. package/dist/utils/ui.test.d.ts +0 -2
  241. package/dist/utils/ui.test.d.ts.map +0 -1
  242. package/dist/utils/ui.test.js +0 -31
  243. package/dist/utils/ui.test.js.map +0 -1
  244. package/dist/utils/validate.d.ts.map +0 -1
  245. 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
@@ -61,7 +61,17 @@ function formatBytes(bytes) {
61
61
  }
62
62
  function getDiskBreakdown() {
63
63
  const claudeDir = join(homedir(), ".claude");
64
- const dirs = ["projects", "file-history", "skills", "debug", "todos", "shell-snapshots", "plans", "cache", "usage-data"];
64
+ const dirs = [
65
+ "projects",
66
+ "file-history",
67
+ "skills",
68
+ "debug",
69
+ "todos",
70
+ "shell-snapshots",
71
+ "plans",
72
+ "cache",
73
+ "usage-data",
74
+ ];
65
75
  const breakdown = [];
66
76
  for (const dirName of dirs) {
67
77
  const dir = join(claudeDir, dirName);
@@ -81,7 +91,7 @@ function getProjectBreakdown() {
81
91
  const full = join(projectsDir, entry);
82
92
  if (!statSync(full).isDirectory())
83
93
  continue;
84
- const jsonlCount = readdirSync(full).filter(f => f.endsWith(".jsonl")).length;
94
+ const jsonlCount = readdirSync(full).filter((f) => f.endsWith(".jsonl")).length;
85
95
  projects.push({ name: entry, sizeBytes: getDirSize(full), sessionCount: jsonlCount });
86
96
  }
87
97
  projects.sort((a, b) => b.sizeBytes - a.sizeBytes);
@@ -115,9 +125,7 @@ export async function statsCommand(opts) {
115
125
  for (const s of sessions) {
116
126
  projectCounts.set(s.project, (projectCounts.get(s.project) ?? 0) + 1);
117
127
  }
118
- const topProjects = [...projectCounts.entries()]
119
- .sort((a, b) => b[1] - a[1])
120
- .slice(0, 5);
128
+ const topProjects = [...projectCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5);
121
129
  // Disk breakdown
122
130
  const diskBreakdown = getDiskBreakdown();
123
131
  const projectBreakdown = getProjectBreakdown();
@@ -125,7 +133,7 @@ export async function statsCommand(opts) {
125
133
  // Calculate reclaimable space (auxiliary dirs)
126
134
  const reclaimableDirs = ["file-history", "debug", "shell-snapshots", "todos", "plans"];
127
135
  const reclaimable = diskBreakdown
128
- .filter(d => reclaimableDirs.includes(d.name))
136
+ .filter((d) => reclaimableDirs.includes(d.name))
129
137
  .reduce((sum, d) => sum + d.sizeBytes, 0);
130
138
  if (opts.json) {
131
139
  const data = {
@@ -135,8 +143,10 @@ export async function statsCommand(opts) {
135
143
  estimatedTokens,
136
144
  avgLinesPerSession: avgLines,
137
145
  topProjects: topProjects.map(([name, count]) => ({ name, sessions: count })),
138
- diskBreakdown: diskBreakdown.map(d => ({ name: d.name, sizeBytes: d.sizeBytes })),
139
- projectBreakdown: projectBreakdown.slice(0, 10).map(p => ({ name: p.name, sizeBytes: p.sizeBytes, sessions: p.sessionCount })),
146
+ diskBreakdown: diskBreakdown.map((d) => ({ name: d.name, sizeBytes: d.sizeBytes })),
147
+ projectBreakdown: projectBreakdown
148
+ .slice(0, 10)
149
+ .map((p) => ({ name: p.name, sizeBytes: p.sizeBytes, sessions: p.sessionCount })),
140
150
  reclaimableBytes: reclaimable,
141
151
  };
142
152
  console.log(JSON.stringify(data, null, 2));
@@ -157,18 +167,17 @@ export async function statsCommand(opts) {
157
167
  console.log();
158
168
  console.log(ui.bold(" Disk Breakdown\n"));
159
169
  const diskRows = diskBreakdown
160
- .filter(d => d.sizeBytes > 0)
161
- .map(d => [
162
- ui.dim(`~/.claude/${d.name}/`),
163
- formatBytes(d.sizeBytes),
164
- ]);
170
+ .filter((d) => d.sizeBytes > 0)
171
+ .map((d) => [ui.dim(`~/.claude/${d.name}/`), formatBytes(d.sizeBytes)]);
165
172
  diskRows.push([ui.bold("Total"), ui.bold(formatBytes(totalDisk))]);
166
173
  table(diskRows);
167
174
  // Per-project breakdown (top 5)
168
175
  if (projectBreakdown.length > 0) {
169
176
  console.log();
170
177
  console.log(ui.bold(" Projects by Size\n"));
171
- const projRows = projectBreakdown.slice(0, 5).map(p => [
178
+ const projRows = projectBreakdown
179
+ .slice(0, 5)
180
+ .map((p) => [
172
181
  ui.dim(p.name.length > 45 ? p.name.slice(0, 42) + "..." : p.name),
173
182
  formatBytes(p.sizeBytes),
174
183
  `${p.sessionCount} sessions`,
@@ -194,13 +203,8 @@ export async function statsCommand(opts) {
194
203
  console.log();
195
204
  console.log(ui.bold(" Recent Arcana Activity\n"));
196
205
  const recent = history.slice(-5).reverse();
197
- const histRows = recent.map(e => [
198
- ui.dim(new Date(e.timestamp).toLocaleDateString()),
199
- e.action,
200
- e.target ?? "",
201
- ]);
206
+ const histRows = recent.map((e) => [ui.dim(new Date(e.timestamp).toLocaleDateString()), e.action, e.target ?? ""]);
202
207
  table(histRows);
203
208
  }
204
209
  console.log();
205
210
  }
206
- //# sourceMappingURL=stats.js.map
@@ -0,0 +1,3 @@
1
+ export declare function teamCommand(action: string | undefined, skill: string | undefined, opts: {
2
+ json?: boolean;
3
+ }): Promise<void>;