@sporesec/arcana 2.4.0 → 3.0.1

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 (247) hide show
  1. package/dist/cli.d.ts +0 -1
  2. package/dist/cli.js +124 -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 +2 -3
  6. package/dist/commands/audit.js +47 -14
  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 +3 -1
  60. package/dist/commands/validate.js +90 -15
  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 +44 -14
  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/quality.d.ts +27 -0
  128. package/dist/utils/quality.js +174 -0
  129. package/dist/utils/scanner.d.ts +0 -1
  130. package/dist/utils/scanner.js +138 -10
  131. package/dist/utils/scoring.d.ts +10 -0
  132. package/dist/utils/scoring.js +84 -0
  133. package/dist/utils/ui.d.ts +0 -1
  134. package/dist/utils/ui.js +11 -4
  135. package/dist/utils/validate.d.ts +0 -1
  136. package/dist/utils/validate.js +4 -1
  137. package/package.json +74 -62
  138. package/dist/cli.d.ts.map +0 -1
  139. package/dist/cli.js.map +0 -1
  140. package/dist/commands/audit.d.ts.map +0 -1
  141. package/dist/commands/audit.js.map +0 -1
  142. package/dist/commands/audit.test.d.ts +0 -2
  143. package/dist/commands/audit.test.d.ts.map +0 -1
  144. package/dist/commands/audit.test.js +0 -217
  145. package/dist/commands/audit.test.js.map +0 -1
  146. package/dist/commands/clean.d.ts.map +0 -1
  147. package/dist/commands/clean.js.map +0 -1
  148. package/dist/commands/compact.d.ts.map +0 -1
  149. package/dist/commands/compact.js.map +0 -1
  150. package/dist/commands/config.d.ts.map +0 -1
  151. package/dist/commands/config.js.map +0 -1
  152. package/dist/commands/create.d.ts.map +0 -1
  153. package/dist/commands/create.js.map +0 -1
  154. package/dist/commands/doctor.d.ts.map +0 -1
  155. package/dist/commands/doctor.js.map +0 -1
  156. package/dist/commands/info.d.ts.map +0 -1
  157. package/dist/commands/info.js.map +0 -1
  158. package/dist/commands/init.d.ts.map +0 -1
  159. package/dist/commands/init.js.map +0 -1
  160. package/dist/commands/install.d.ts.map +0 -1
  161. package/dist/commands/install.js.map +0 -1
  162. package/dist/commands/list.d.ts.map +0 -1
  163. package/dist/commands/list.js.map +0 -1
  164. package/dist/commands/optimize.d.ts.map +0 -1
  165. package/dist/commands/optimize.js.map +0 -1
  166. package/dist/commands/providers.d.ts.map +0 -1
  167. package/dist/commands/providers.js.map +0 -1
  168. package/dist/commands/scan.d.ts.map +0 -1
  169. package/dist/commands/scan.js.map +0 -1
  170. package/dist/commands/search.d.ts.map +0 -1
  171. package/dist/commands/search.js.map +0 -1
  172. package/dist/commands/stats.d.ts.map +0 -1
  173. package/dist/commands/stats.js.map +0 -1
  174. package/dist/commands/uninstall.d.ts.map +0 -1
  175. package/dist/commands/uninstall.js.map +0 -1
  176. package/dist/commands/update.d.ts.map +0 -1
  177. package/dist/commands/update.js.map +0 -1
  178. package/dist/commands/validate.d.ts.map +0 -1
  179. package/dist/commands/validate.js.map +0 -1
  180. package/dist/index.d.ts.map +0 -1
  181. package/dist/index.js.map +0 -1
  182. package/dist/interactive.d.ts.map +0 -1
  183. package/dist/interactive.js.map +0 -1
  184. package/dist/providers/arcana.d.ts.map +0 -1
  185. package/dist/providers/arcana.js.map +0 -1
  186. package/dist/providers/base.d.ts.map +0 -1
  187. package/dist/providers/base.js.map +0 -1
  188. package/dist/providers/github.d.ts.map +0 -1
  189. package/dist/providers/github.js.map +0 -1
  190. package/dist/registry.d.ts.map +0 -1
  191. package/dist/registry.js.map +0 -1
  192. package/dist/types.d.ts.map +0 -1
  193. package/dist/types.js.map +0 -1
  194. package/dist/utils/atomic.d.ts.map +0 -1
  195. package/dist/utils/atomic.js.map +0 -1
  196. package/dist/utils/atomic.test.d.ts +0 -2
  197. package/dist/utils/atomic.test.d.ts.map +0 -1
  198. package/dist/utils/atomic.test.js +0 -31
  199. package/dist/utils/atomic.test.js.map +0 -1
  200. package/dist/utils/cache.d.ts.map +0 -1
  201. package/dist/utils/cache.js.map +0 -1
  202. package/dist/utils/config.d.ts.map +0 -1
  203. package/dist/utils/config.js.map +0 -1
  204. package/dist/utils/config.test.d.ts +0 -2
  205. package/dist/utils/config.test.d.ts.map +0 -1
  206. package/dist/utils/config.test.js +0 -38
  207. package/dist/utils/config.test.js.map +0 -1
  208. package/dist/utils/errors.d.ts.map +0 -1
  209. package/dist/utils/errors.js.map +0 -1
  210. package/dist/utils/frontmatter.d.ts.map +0 -1
  211. package/dist/utils/frontmatter.js.map +0 -1
  212. package/dist/utils/frontmatter.test.d.ts +0 -2
  213. package/dist/utils/frontmatter.test.d.ts.map +0 -1
  214. package/dist/utils/frontmatter.test.js +0 -152
  215. package/dist/utils/frontmatter.test.js.map +0 -1
  216. package/dist/utils/fs.d.ts.map +0 -1
  217. package/dist/utils/fs.js.map +0 -1
  218. package/dist/utils/fs.test.d.ts +0 -2
  219. package/dist/utils/fs.test.d.ts.map +0 -1
  220. package/dist/utils/fs.test.js +0 -145
  221. package/dist/utils/fs.test.js.map +0 -1
  222. package/dist/utils/help.d.ts.map +0 -1
  223. package/dist/utils/help.js.map +0 -1
  224. package/dist/utils/help.test.d.ts +0 -2
  225. package/dist/utils/help.test.d.ts.map +0 -1
  226. package/dist/utils/help.test.js +0 -66
  227. package/dist/utils/help.test.js.map +0 -1
  228. package/dist/utils/history.d.ts.map +0 -1
  229. package/dist/utils/history.js.map +0 -1
  230. package/dist/utils/http.d.ts.map +0 -1
  231. package/dist/utils/http.js.map +0 -1
  232. package/dist/utils/http.test.d.ts +0 -2
  233. package/dist/utils/http.test.d.ts.map +0 -1
  234. package/dist/utils/http.test.js +0 -55
  235. package/dist/utils/http.test.js.map +0 -1
  236. package/dist/utils/parallel.d.ts.map +0 -1
  237. package/dist/utils/parallel.js.map +0 -1
  238. package/dist/utils/scanner.d.ts.map +0 -1
  239. package/dist/utils/scanner.js.map +0 -1
  240. package/dist/utils/ui.d.ts.map +0 -1
  241. package/dist/utils/ui.js.map +0 -1
  242. package/dist/utils/ui.test.d.ts +0 -2
  243. package/dist/utils/ui.test.d.ts.map +0 -1
  244. package/dist/utils/ui.test.js +0 -31
  245. package/dist/utils/ui.test.js.map +0 -1
  246. package/dist/utils/validate.d.ts.map +0 -1
  247. package/dist/utils/validate.js.map +0 -1
@@ -6,15 +6,29 @@ import { getProvider, getProviders } from "../registry.js";
6
6
  import { ui, banner, spinner, noopSpinner } from "../utils/ui.js";
7
7
  import { loadConfig } from "../utils/config.js";
8
8
  import { validateSlug } from "../utils/validate.js";
9
+ import { updateLockEntry } from "../utils/integrity.js";
9
10
  function isNewer(remoteVersion, localVersion) {
10
11
  const local = semver.valid(semver.coerce(localVersion)) ?? "0.0.0";
11
12
  const remote = semver.valid(semver.coerce(remoteVersion)) ?? "0.0.0";
12
13
  return semver.gt(remote, local);
13
14
  }
15
+ /** Fetch, write files, write meta, update lock for a single skill. */
16
+ async function applyUpdate(skillName, remote, provider) {
17
+ const files = await provider.fetch(skillName);
18
+ installSkill(skillName, files);
19
+ writeSkillMeta(skillName, {
20
+ version: remote.version,
21
+ installedAt: new Date().toISOString(),
22
+ source: provider.name,
23
+ description: remote.description,
24
+ fileCount: files.length,
25
+ });
26
+ updateLockEntry(skillName, remote.version, provider.name, files);
27
+ return files;
28
+ }
14
29
  export async function updateCommand(skills, opts) {
15
- if (!opts.json) {
30
+ if (!opts.json)
16
31
  banner();
17
- }
18
32
  if (skills.length === 0 && !opts.all) {
19
33
  if (opts.json) {
20
34
  console.log(JSON.stringify({ error: "Specify a skill name or use --all" }));
@@ -40,13 +54,13 @@ export async function updateCommand(skills, opts) {
40
54
  }
41
55
  const providerName = opts.provider ?? loadConfig().defaultProvider;
42
56
  if (opts.all) {
43
- await updateAll(installDir, providerName, opts.json, opts.dryRun);
57
+ await updateBatch(null, installDir, providerName, opts.json, opts.dryRun);
44
58
  }
45
59
  else if (skills.length === 1) {
46
60
  await updateOne(skills[0], installDir, providerName, opts.json, opts.dryRun);
47
61
  }
48
62
  else {
49
- await updateMultiple(skills, installDir, providerName, opts.json, opts.dryRun);
63
+ await updateBatch(skills, installDir, providerName, opts.json, opts.dryRun);
50
64
  }
51
65
  }
52
66
  async function updateOne(skillName, installDir, providerName, json, dryRun) {
@@ -55,7 +69,12 @@ async function updateOne(skillName, installDir, providerName, json, dryRun) {
55
69
  }
56
70
  catch (err) {
57
71
  if (json) {
58
- console.log(JSON.stringify({ updated: [], upToDate: [], failed: [skillName], error: err instanceof Error ? err.message : "Invalid name" }));
72
+ console.log(JSON.stringify({
73
+ updated: [],
74
+ upToDate: [],
75
+ failed: [skillName],
76
+ error: err instanceof Error ? err.message : "Invalid name",
77
+ }));
59
78
  }
60
79
  else {
61
80
  console.log(ui.error(` ${err instanceof Error ? err.message : "Invalid skill name"}`));
@@ -102,7 +121,10 @@ async function updateOne(skillName, installDir, providerName, json, dryRun) {
102
121
  }
103
122
  if (dryRun) {
104
123
  if (json) {
105
- console.log(JSON.stringify({ dryRun: true, wouldUpdate: [{ name: skillName, from: meta?.version ?? "unknown", to: remote.version }] }));
124
+ console.log(JSON.stringify({
125
+ dryRun: true,
126
+ wouldUpdate: [{ name: skillName, from: meta?.version ?? "unknown", to: remote.version }],
127
+ }));
106
128
  }
107
129
  else {
108
130
  s.info(`${ui.bold(skillName)} would be updated: v${meta?.version ?? "unknown"} -> v${remote.version}`);
@@ -111,15 +133,7 @@ async function updateOne(skillName, installDir, providerName, json, dryRun) {
111
133
  return;
112
134
  }
113
135
  s.text = `Updating ${ui.bold(skillName)}...`;
114
- const files = await provider.fetch(skillName);
115
- installSkill(skillName, files);
116
- writeSkillMeta(skillName, {
117
- version: remote.version,
118
- installedAt: new Date().toISOString(),
119
- source: providerName,
120
- description: remote.description,
121
- fileCount: files.length,
122
- });
136
+ const files = await applyUpdate(skillName, remote, provider);
123
137
  if (json) {
124
138
  console.log(JSON.stringify({ updated: [skillName], upToDate: [], failed: [] }));
125
139
  }
@@ -130,7 +144,12 @@ async function updateOne(skillName, installDir, providerName, json, dryRun) {
130
144
  }
131
145
  catch (err) {
132
146
  if (json) {
133
- console.log(JSON.stringify({ updated: [], upToDate: [], failed: [skillName], error: err instanceof Error ? err.message : "Update failed" }));
147
+ console.log(JSON.stringify({
148
+ updated: [],
149
+ upToDate: [],
150
+ failed: [skillName],
151
+ error: err instanceof Error ? err.message : "Update failed",
152
+ }));
134
153
  }
135
154
  else {
136
155
  s.fail(`Failed to update ${skillName}`);
@@ -141,102 +160,35 @@ async function updateOne(skillName, installDir, providerName, json, dryRun) {
141
160
  process.exit(1);
142
161
  }
143
162
  }
144
- async function updateMultiple(skillNames, installDir, providerName, json, dryRun) {
145
- for (const name of skillNames) {
146
- try {
147
- validateSlug(name, "skill name");
148
- }
149
- catch (err) {
150
- if (json) {
151
- console.log(JSON.stringify({ updated: [], upToDate: [], failed: skillNames, error: err instanceof Error ? err.message : "Invalid name" }));
163
+ /**
164
+ * Batch update. If `skillNames` is null, update all installed skills.
165
+ * If `skillNames` is provided, update only those specific skills.
166
+ */
167
+ async function updateBatch(skillNames, installDir, providerName, json, dryRun) {
168
+ // Validate explicit skill names
169
+ if (skillNames) {
170
+ for (const name of skillNames) {
171
+ try {
172
+ validateSlug(name, "skill name");
152
173
  }
153
- else {
154
- console.log(ui.error(` ${err instanceof Error ? err.message : "Invalid skill name"}`));
155
- console.log();
156
- }
157
- process.exit(1);
158
- }
159
- }
160
- const s = json ? noopSpinner() : spinner(`Checking ${skillNames.length} skills for updates...`);
161
- s.start();
162
- const provider = getProvider(providerName);
163
- const updatedList = [];
164
- const upToDateList = [];
165
- const failedList = [];
166
- const dryRunUpdates = [];
167
- for (let i = 0; i < skillNames.length; i++) {
168
- const skillName = skillNames[i];
169
- const skillDir = join(installDir, skillName);
170
- if (!existsSync(skillDir)) {
171
- failedList.push(skillName);
172
- if (!json)
173
- console.error(ui.dim(` ${skillName} is not installed`));
174
- continue;
175
- }
176
- try {
177
- const remote = await provider.info(skillName);
178
- if (!remote) {
179
- failedList.push(skillName);
180
- continue;
181
- }
182
- const meta = readSkillMeta(skillName);
183
- if (!isNewer(remote.version, meta?.version)) {
184
- upToDateList.push(skillName);
185
- continue;
186
- }
187
- if (dryRun) {
188
- dryRunUpdates.push({ name: skillName, from: meta?.version ?? "unknown", to: remote.version });
189
- continue;
190
- }
191
- s.text = `Updating ${ui.bold(skillName)} (${i + 1}/${skillNames.length})...`;
192
- const files = await provider.fetch(skillName);
193
- installSkill(skillName, files);
194
- writeSkillMeta(skillName, {
195
- version: remote.version,
196
- installedAt: new Date().toISOString(),
197
- source: providerName,
198
- description: remote.description,
199
- fileCount: files.length,
200
- });
201
- updatedList.push(skillName);
202
- }
203
- catch (err) {
204
- failedList.push(skillName);
205
- if (err instanceof Error && !json)
206
- console.error(ui.dim(` Failed to update ${skillName}: ${err.message}`));
207
- }
208
- }
209
- if (dryRun) {
210
- if (json) {
211
- console.log(JSON.stringify({ dryRun: true, wouldUpdate: dryRunUpdates, upToDate: upToDateList, failed: failedList }));
212
- }
213
- else {
214
- s.stop();
215
- if (dryRunUpdates.length === 0) {
216
- console.log(ui.dim(" All skills are up to date."));
217
- }
218
- else {
219
- for (const u of dryRunUpdates) {
220
- console.log(` ${ui.bold(u.name)}: v${u.from} -> v${u.to}`);
174
+ catch (err) {
175
+ if (json) {
176
+ console.log(JSON.stringify({
177
+ updated: [],
178
+ upToDate: [],
179
+ failed: skillNames,
180
+ error: err instanceof Error ? err.message : "Invalid name",
181
+ }));
182
+ }
183
+ else {
184
+ console.log(ui.error(` ${err instanceof Error ? err.message : "Invalid skill name"}`));
185
+ console.log();
221
186
  }
187
+ process.exit(1);
222
188
  }
223
- console.log();
224
189
  }
225
- return;
226
- }
227
- if (json) {
228
- console.log(JSON.stringify({ updated: updatedList, upToDate: upToDateList, failed: failedList }));
229
190
  }
230
- else {
231
- s.succeed(`Update complete`);
232
- console.log(ui.dim(` ${updatedList.length} updated, ${upToDateList.length} up to date${failedList.length > 0 ? `, ${failedList.length} failed` : ""}`));
233
- console.log();
234
- }
235
- if (failedList.length > 0)
236
- process.exit(1);
237
- }
238
- async function updateAll(installDir, providerName, json, dryRun) {
239
- const installed = readdirSync(installDir).filter((d) => statSync(join(installDir, d)).isDirectory());
191
+ const installed = skillNames ?? readdirSync(installDir).filter((d) => statSync(join(installDir, d)).isDirectory());
240
192
  if (installed.length === 0) {
241
193
  if (json) {
242
194
  console.log(JSON.stringify({ updated: [], upToDate: [], failed: [] }));
@@ -254,69 +206,106 @@ async function updateAll(installDir, providerName, json, dryRun) {
254
206
  const failedList = [];
255
207
  const skippedList = [];
256
208
  const dryRunUpdates = [];
257
- const providers = getProviders(providerName === "arcana" ? undefined : providerName);
258
- // Pre-fetch skill lists to avoid N+1 info() calls
209
+ // For --all mode, use all providers and pre-fetch skill lists (avoids N+1 info() calls)
210
+ // For explicit names, use single provider with per-skill info() calls
211
+ const isAllMode = skillNames === null;
212
+ const providers = isAllMode ? getProviders(providerName === "arcana" ? undefined : providerName) : [];
213
+ const singleProvider = isAllMode ? null : getProvider(providerName);
214
+ // Pre-fetch provider skill maps for --all mode
259
215
  const providerSkillMaps = new Map();
260
- for (const provider of providers) {
261
- try {
262
- const skills = await provider.list();
263
- const map = new Map();
264
- for (const skill of skills) {
265
- map.set(skill.name, { version: skill.version, description: skill.description });
216
+ if (isAllMode) {
217
+ for (const provider of providers) {
218
+ try {
219
+ const skills = await provider.list();
220
+ const map = new Map();
221
+ for (const skill of skills) {
222
+ map.set(skill.name, { version: skill.version, description: skill.description });
223
+ }
224
+ providerSkillMaps.set(provider.name, map);
225
+ }
226
+ catch (err) {
227
+ if (err instanceof Error && !json)
228
+ console.error(ui.dim(` Failed to list ${provider.name}: ${err.message}`));
266
229
  }
267
- providerSkillMaps.set(provider.name, map);
268
- }
269
- catch (err) {
270
- if (err instanceof Error && !json)
271
- console.error(ui.dim(` Failed to list ${provider.name}: ${err.message}`));
272
230
  }
273
231
  }
274
232
  const total = installed.length;
275
233
  for (let i = 0; i < total; i++) {
276
- const skillName = installed[i];
277
- let found = false;
234
+ const name = installed[i];
235
+ // Check installation exists (for explicit names)
236
+ if (!isAllMode) {
237
+ const skillDir = join(installDir, name);
238
+ if (!existsSync(skillDir)) {
239
+ failedList.push(name);
240
+ if (!json)
241
+ console.error(ui.dim(` ${name} is not installed`));
242
+ continue;
243
+ }
244
+ }
278
245
  try {
279
- for (const provider of providers) {
280
- const skillMap = providerSkillMaps.get(provider.name);
281
- const remote = skillMap?.get(skillName) ?? null;
282
- if (!remote)
246
+ if (isAllMode) {
247
+ // Find across all providers
248
+ let found = false;
249
+ for (const provider of providers) {
250
+ const skillMap = providerSkillMaps.get(provider.name);
251
+ const remote = skillMap?.get(name) ?? null;
252
+ if (!remote)
253
+ continue;
254
+ found = true;
255
+ const meta = readSkillMeta(name);
256
+ if (!isNewer(remote.version, meta?.version)) {
257
+ upToDateList.push(name);
258
+ break;
259
+ }
260
+ if (dryRun) {
261
+ dryRunUpdates.push({ name, from: meta?.version ?? "unknown", to: remote.version });
262
+ break;
263
+ }
264
+ s.text = `Updating ${ui.bold(name)} (${i + 1}/${total})...`;
265
+ await applyUpdate(name, remote, provider);
266
+ updatedList.push(name);
267
+ break;
268
+ }
269
+ if (!found)
270
+ skippedList.push(name);
271
+ }
272
+ else {
273
+ // Single provider mode
274
+ const remote = await singleProvider.info(name);
275
+ if (!remote) {
276
+ failedList.push(name);
283
277
  continue;
284
- found = true;
285
- const meta = readSkillMeta(skillName);
278
+ }
279
+ const meta = readSkillMeta(name);
286
280
  if (!isNewer(remote.version, meta?.version)) {
287
- upToDateList.push(skillName);
288
- break;
281
+ upToDateList.push(name);
282
+ continue;
289
283
  }
290
284
  if (dryRun) {
291
- dryRunUpdates.push({ name: skillName, from: meta?.version ?? "unknown", to: remote.version });
292
- break;
285
+ dryRunUpdates.push({ name, from: meta?.version ?? "unknown", to: remote.version });
286
+ continue;
293
287
  }
294
- s.text = `Updating ${ui.bold(skillName)} (${i + 1}/${total})...`;
295
- const files = await provider.fetch(skillName);
296
- installSkill(skillName, files);
297
- writeSkillMeta(skillName, {
298
- version: remote.version,
299
- installedAt: new Date().toISOString(),
300
- source: provider.name,
301
- description: remote.description,
302
- fileCount: files.length,
303
- });
304
- updatedList.push(skillName);
305
- break;
288
+ s.text = `Updating ${ui.bold(name)} (${i + 1}/${total})...`;
289
+ await applyUpdate(name, remote, singleProvider);
290
+ updatedList.push(name);
306
291
  }
307
292
  }
308
293
  catch (err) {
309
- failedList.push(skillName);
294
+ failedList.push(name);
310
295
  if (err instanceof Error && !json)
311
- console.error(ui.dim(` Failed to update ${skillName}: ${err.message}`));
312
- continue;
296
+ console.error(ui.dim(` Failed to update ${name}: ${err.message}`));
313
297
  }
314
- if (!found)
315
- skippedList.push(skillName);
316
298
  }
299
+ // Output results
317
300
  if (dryRun) {
318
301
  if (json) {
319
- console.log(JSON.stringify({ dryRun: true, wouldUpdate: dryRunUpdates, upToDate: upToDateList, skipped: skippedList, failed: failedList }));
302
+ console.log(JSON.stringify({
303
+ dryRun: true,
304
+ wouldUpdate: dryRunUpdates,
305
+ upToDate: upToDateList,
306
+ ...(isAllMode ? { skipped: skippedList } : {}),
307
+ failed: failedList,
308
+ }));
320
309
  }
321
310
  else {
322
311
  s.stop();
@@ -324,8 +313,10 @@ async function updateAll(installDir, providerName, json, dryRun) {
324
313
  console.log(ui.dim(" All skills are up to date."));
325
314
  }
326
315
  else {
327
- console.log(ui.bold(` ${dryRunUpdates.length} of ${total} skills have updates available:`));
328
- console.log();
316
+ if (isAllMode) {
317
+ console.log(ui.bold(` ${dryRunUpdates.length} of ${total} skills have updates available:`));
318
+ console.log();
319
+ }
329
320
  for (const u of dryRunUpdates) {
330
321
  console.log(` ${ui.bold(u.name)}: v${u.from} -> v${u.to}`);
331
322
  }
@@ -335,14 +326,23 @@ async function updateAll(installDir, providerName, json, dryRun) {
335
326
  return;
336
327
  }
337
328
  if (json) {
338
- console.log(JSON.stringify({ updated: updatedList, upToDate: upToDateList, skipped: skippedList, failed: failedList }));
329
+ console.log(JSON.stringify({
330
+ updated: updatedList,
331
+ upToDate: upToDateList,
332
+ ...(isAllMode ? { skipped: skippedList } : {}),
333
+ failed: failedList,
334
+ }));
339
335
  }
340
336
  else {
341
337
  s.succeed(`Update complete`);
342
- console.log(ui.dim(` ${updatedList.length} updated, ${upToDateList.length} up to date${skippedList.length > 0 ? `, ${skippedList.length} skipped (not on provider)` : ""}${failedList.length > 0 ? `, ${failedList.length} failed` : ""}`));
338
+ const parts = [`${updatedList.length} updated`, `${upToDateList.length} up to date`];
339
+ if (skippedList.length > 0)
340
+ parts.push(`${skippedList.length} skipped (not on provider)`);
341
+ if (failedList.length > 0)
342
+ parts.push(`${failedList.length} failed`);
343
+ console.log(ui.dim(` ${parts.join(", ")}`));
343
344
  console.log();
344
345
  }
345
346
  if (failedList.length > 0)
346
347
  process.exit(1);
347
348
  }
348
- //# sourceMappingURL=update.js.map
@@ -2,5 +2,7 @@ export declare function validateCommand(skill: string | undefined, opts: {
2
2
  all?: boolean;
3
3
  fix?: boolean;
4
4
  json?: boolean;
5
+ source?: string;
6
+ cross?: boolean;
7
+ minScore?: number;
5
8
  }): Promise<void>;
6
- //# sourceMappingURL=validate.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
2
- import { join } from "node:path";
2
+ import { join, resolve } from "node:path";
3
3
  import { getInstallDir } from "../utils/fs.js";
4
4
  import { validateSkillDir, fixSkillFrontmatter } from "../utils/frontmatter.js";
5
5
  import { atomicWriteSync } from "../utils/atomic.js";
@@ -8,8 +8,8 @@ import { scanSkillContent } from "../utils/scanner.js";
8
8
  export async function validateCommand(skill, opts) {
9
9
  if (!opts.json)
10
10
  banner();
11
- const installDir = getInstallDir();
12
- if (!existsSync(installDir)) {
11
+ const baseDir = opts.source ? resolve(opts.source) : getInstallDir();
12
+ if (!existsSync(baseDir)) {
13
13
  if (opts.json) {
14
14
  console.log(JSON.stringify({ results: [] }));
15
15
  }
@@ -21,7 +21,14 @@ export async function validateCommand(skill, opts) {
21
21
  }
22
22
  let skills;
23
23
  if (opts.all) {
24
- skills = readdirSync(installDir).filter((d) => statSync(join(installDir, d)).isDirectory());
24
+ skills = readdirSync(baseDir).filter((d) => {
25
+ try {
26
+ return statSync(join(baseDir, d)).isDirectory();
27
+ }
28
+ catch {
29
+ return false;
30
+ }
31
+ });
25
32
  }
26
33
  else if (skill) {
27
34
  skills = [skill];
@@ -40,7 +47,7 @@ export async function validateCommand(skill, opts) {
40
47
  }
41
48
  const results = [];
42
49
  for (const name of skills) {
43
- const skillDir = join(installDir, name);
50
+ const skillDir = join(baseDir, name);
44
51
  if (!existsSync(skillDir)) {
45
52
  results.push({ skill: name, valid: false, errors: ["Not installed"], warnings: [], infos: [] });
46
53
  continue;
@@ -81,26 +88,84 @@ export async function validateCommand(skill, opts) {
81
88
  }
82
89
  }
83
90
  }
84
- catch { /* skip if unreadable */ }
91
+ catch {
92
+ /* skip if unreadable */
93
+ }
94
+ }
95
+ // Quality scoring (when --min-score is set)
96
+ const entry = { ...result };
97
+ if (opts.minScore !== undefined) {
98
+ const { auditSkill } = await import("./audit.js");
99
+ const audit = auditSkill(skillDir, name);
100
+ entry.qualityScore = audit.score;
101
+ entry.qualityRating = audit.rating;
102
+ if (audit.score < opts.minScore) {
103
+ entry.valid = false;
104
+ entry.errors.push(`Quality score ${audit.score} below minimum ${opts.minScore} (${audit.rating})`);
105
+ }
106
+ }
107
+ results.push(entry);
108
+ }
109
+ // Cross-validation (when --cross is set)
110
+ let crossIssues = [];
111
+ if (opts.cross) {
112
+ const { crossValidate } = await import("../utils/quality.js");
113
+ const marketplacePaths = opts.source
114
+ ? [
115
+ resolve(opts.source, "..", ".claude-plugin", "marketplace.json"),
116
+ resolve(opts.source, ".claude-plugin", "marketplace.json"),
117
+ ]
118
+ : [resolve(baseDir, "..", ".claude-plugin", "marketplace.json")];
119
+ const marketplacePath = marketplacePaths.find((p) => existsSync(p));
120
+ if (marketplacePath) {
121
+ crossIssues = crossValidate(baseDir, marketplacePath);
122
+ }
123
+ else if (!opts.json) {
124
+ console.log(ui.warn(" Could not find marketplace.json for cross-validation"));
85
125
  }
86
- results.push(result);
87
126
  }
127
+ const hasErrors = results.some((r) => !r.valid);
128
+ const hasCrossErrors = crossIssues.some((i) => i.level === "error");
88
129
  if (opts.json) {
89
- console.log(JSON.stringify({ results: results.map((r) => ({ skill: r.skill, valid: r.valid, errors: r.errors, warnings: r.warnings, infos: r.infos, fixed: r.fixed ?? false })) }, null, 2));
90
- if (results.some((r) => !r.valid))
130
+ const output = {
131
+ results: results.map((r) => ({
132
+ skill: r.skill,
133
+ valid: r.valid,
134
+ errors: r.errors,
135
+ warnings: r.warnings,
136
+ infos: r.infos,
137
+ fixed: r.fixed ?? false,
138
+ ...(r.qualityScore !== undefined && { qualityScore: r.qualityScore, qualityRating: r.qualityRating }),
139
+ })),
140
+ };
141
+ if (opts.cross && crossIssues.length > 0) {
142
+ output.crossValidation = crossIssues;
143
+ }
144
+ const scores = results.filter((r) => r.qualityScore !== undefined).map((r) => r.qualityScore);
145
+ if (scores.length > 0) {
146
+ output.summary = {
147
+ total: results.length,
148
+ passed: results.filter((r) => r.valid).length,
149
+ failed: results.filter((r) => !r.valid).length,
150
+ averageScore: Math.round(scores.reduce((a, b) => a + b, 0) / scores.length),
151
+ belowThreshold: opts.minScore ? results.filter((r) => (r.qualityScore ?? 0) < opts.minScore).length : 0,
152
+ };
153
+ }
154
+ console.log(JSON.stringify(output, null, 2));
155
+ if (hasErrors || hasCrossErrors)
91
156
  process.exit(1);
92
157
  return;
93
158
  }
159
+ // Human-readable output
94
160
  let passed = 0;
95
161
  let warned = 0;
96
162
  let failed = 0;
97
163
  let fixed = 0;
98
164
  for (const r of results) {
99
- const icon = r.valid
100
- ? r.warnings.length > 0 ? ui.warn("[!!]") : ui.success("[OK]")
101
- : ui.error("[XX]");
165
+ const icon = r.valid ? (r.warnings.length > 0 ? ui.warn("[!!]") : ui.success("[OK]")) : ui.error("[XX]");
102
166
  const fixTag = r.fixed ? ui.cyan(" [fixed]") : "";
103
- console.log(` ${icon} ${ui.bold(r.skill)}${fixTag}`);
167
+ const scoreTag = r.qualityScore !== undefined ? ui.dim(` (${r.qualityScore}/100 ${r.qualityRating})`) : "";
168
+ console.log(` ${icon} ${ui.bold(r.skill)}${fixTag}${scoreTag}`);
104
169
  for (const err of r.errors) {
105
170
  console.log(ui.error(` Error: ${err}`));
106
171
  }
@@ -122,6 +187,15 @@ export async function validateCommand(skill, opts) {
122
187
  if (r.fixed)
123
188
  fixed++;
124
189
  }
190
+ // Cross-validation output
191
+ if (opts.cross && crossIssues.length > 0) {
192
+ console.log();
193
+ console.log(ui.bold(" Cross-validation:"));
194
+ for (const issue of crossIssues) {
195
+ const icon = issue.level === "error" ? ui.error("[XX]") : ui.warn("[!!]");
196
+ console.log(` ${icon} ${issue.skill}: ${issue.detail}`);
197
+ }
198
+ }
125
199
  console.log();
126
200
  const parts = [];
127
201
  if (passed > 0)
@@ -132,9 +206,10 @@ export async function validateCommand(skill, opts) {
132
206
  parts.push(ui.error(`${failed} failed`));
133
207
  if (fixed > 0)
134
208
  parts.push(ui.cyan(`${fixed} fixed`));
209
+ if (hasCrossErrors)
210
+ parts.push(ui.error(`${crossIssues.filter((i) => i.level === "error").length} cross-validation errors`));
135
211
  console.log(` ${parts.join(ui.dim(" | "))}`);
136
212
  console.log();
137
- if (failed > 0)
213
+ if (failed > 0 || hasCrossErrors)
138
214
  process.exit(1);
139
215
  }
140
- //# sourceMappingURL=validate.js.map
@@ -0,0 +1,4 @@
1
+ export declare function verifyCommand(skillNames: string[], opts: {
2
+ all?: boolean;
3
+ json?: boolean;
4
+ }): Promise<void>;