@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
@@ -0,0 +1,187 @@
1
+ import * as p from "@clack/prompts";
2
+ import chalk from "chalk";
3
+ import semver from "semver";
4
+ import { isSkillInstalled, readSkillMeta, installSkill, writeSkillMeta } from "../utils/fs.js";
5
+ import { getProvider } from "../registry.js";
6
+ import { ui } from "../utils/ui.js";
7
+ import { updateLockEntry } from "../utils/integrity.js";
8
+ import { handleCancel, getInstalledNames } from "./helpers.js";
9
+ import { SKILL_CATEGORIES } from "./categories.js";
10
+ import { skillDetailFlow, doUninstall } from "./skill-detail.js";
11
+ export async function manageInstalled(allSkills, providerName) {
12
+ while (true) {
13
+ const names = getInstalledNames();
14
+ if (names.length === 0) {
15
+ p.log.info("No skills installed.");
16
+ return;
17
+ }
18
+ // Group installed skills by category
19
+ const groups = [];
20
+ const categorized = new Set();
21
+ for (const [cat, catSkills] of Object.entries(SKILL_CATEGORIES)) {
22
+ const installed = catSkills.filter((s) => names.includes(s));
23
+ if (installed.length > 0) {
24
+ groups.push({ cat, skills: installed });
25
+ installed.forEach((s) => categorized.add(s));
26
+ }
27
+ }
28
+ const uncategorized = names.filter((s) => !categorized.has(s));
29
+ if (uncategorized.length > 0) {
30
+ groups.push({ cat: "Other", skills: uncategorized });
31
+ }
32
+ const options = groups.map((g) => ({
33
+ value: g.cat,
34
+ label: g.cat,
35
+ hint: `${g.skills.length} installed`,
36
+ }));
37
+ const picked = await p.select({
38
+ message: `Installed skills (${names.length})`,
39
+ options: [
40
+ ...options,
41
+ { value: "__update", label: chalk.cyan("Check for updates") },
42
+ { value: "__bulk_uninstall", label: "Uninstall multiple..." },
43
+ { value: "__back", label: "Back" },
44
+ ],
45
+ });
46
+ handleCancel(picked);
47
+ if (picked === "__back")
48
+ return;
49
+ if (picked === "__update") {
50
+ await updateAll(providerName);
51
+ continue;
52
+ }
53
+ if (picked === "__bulk_uninstall") {
54
+ await bulkUninstall(names);
55
+ continue;
56
+ }
57
+ const group = groups.find((g) => g.cat === picked);
58
+ if (group) {
59
+ await installedCategoryList(group.cat, group.skills, allSkills, providerName);
60
+ }
61
+ }
62
+ }
63
+ async function installedCategoryList(categoryName, installedNames, allSkills, providerName) {
64
+ while (true) {
65
+ const stillInstalled = installedNames.filter((s) => isSkillInstalled(s));
66
+ if (stillInstalled.length === 0) {
67
+ p.log.info("No skills remaining in this category.");
68
+ return;
69
+ }
70
+ const options = stillInstalled.map((name) => {
71
+ const meta = readSkillMeta(name);
72
+ const ver = meta ? `v${meta.version}` : "";
73
+ const date = meta?.installedAt ? new Date(meta.installedAt).toLocaleDateString() : "";
74
+ return {
75
+ value: name,
76
+ label: name,
77
+ hint: `${ver}${date ? ` ${date}` : ""}`,
78
+ };
79
+ });
80
+ const picked = await p.select({
81
+ message: `${categoryName} (${stillInstalled.length} installed)`,
82
+ options: [...options, { value: "__back", label: "Back" }],
83
+ });
84
+ handleCancel(picked);
85
+ if (picked === "__back")
86
+ return;
87
+ const result = await skillDetailFlow(picked, allSkills, providerName);
88
+ if (result === "menu")
89
+ return;
90
+ }
91
+ }
92
+ async function bulkUninstall(installedNames) {
93
+ const selected = await p.multiselect({
94
+ message: "Select skills to uninstall",
95
+ options: installedNames.map((name) => ({ value: name, label: name })),
96
+ required: false,
97
+ maxItems: 15,
98
+ });
99
+ handleCancel(selected);
100
+ const names = selected;
101
+ if (names.length === 0)
102
+ return;
103
+ const ok = await p.confirm({
104
+ message: `Uninstall ${names.length} skill${names.length > 1 ? "s" : ""}?`,
105
+ });
106
+ handleCancel(ok);
107
+ if (!ok)
108
+ return;
109
+ let removed = 0;
110
+ for (const name of names) {
111
+ if (doUninstall(name))
112
+ removed++;
113
+ }
114
+ p.log.success(`Removed ${removed} skill${removed !== 1 ? "s" : ""}`);
115
+ }
116
+ async function updateAll(providerName) {
117
+ const installed = getInstalledNames();
118
+ if (installed.length === 0) {
119
+ p.log.info("No skills installed.");
120
+ return;
121
+ }
122
+ const s = p.spinner();
123
+ s.start(`Checking ${installed.length} skill${installed.length !== 1 ? "s" : ""} for updates...`);
124
+ const provider = getProvider(providerName);
125
+ let remoteSkills;
126
+ try {
127
+ remoteSkills = await provider.list();
128
+ }
129
+ catch (err) {
130
+ s.stop("Failed to fetch remote skill list");
131
+ if (err instanceof Error)
132
+ p.log.error(ui.dim(err.message));
133
+ return;
134
+ }
135
+ const remoteMap = new Map(remoteSkills.map((rs) => [rs.name, rs]));
136
+ const updates = [];
137
+ for (const name of installed) {
138
+ const remote = remoteMap.get(name);
139
+ if (!remote)
140
+ continue;
141
+ const meta = readSkillMeta(name);
142
+ const localVer = semver.valid(semver.coerce(meta?.version)) ?? "0.0.0";
143
+ const remoteVer = semver.valid(semver.coerce(remote.version)) ?? "0.0.0";
144
+ if (semver.gt(remoteVer, localVer)) {
145
+ updates.push({ name, from: meta?.version ?? "0.0.0", to: remote.version });
146
+ }
147
+ }
148
+ s.stop(`Checked ${installed.length} skills`);
149
+ if (updates.length === 0) {
150
+ p.log.success("All skills are up to date.");
151
+ return;
152
+ }
153
+ p.log.step(`${updates.length} update${updates.length !== 1 ? "s" : ""} available:`);
154
+ for (const u of updates) {
155
+ p.log.info(` ${chalk.bold(u.name)}: v${u.from} -> v${u.to}`);
156
+ }
157
+ const ok = await p.confirm({ message: "Apply updates?" });
158
+ handleCancel(ok);
159
+ if (!ok)
160
+ return;
161
+ const spin = p.spinner();
162
+ spin.start("Updating...");
163
+ let updated = 0;
164
+ for (const u of updates) {
165
+ try {
166
+ spin.message(`Updating ${chalk.bold(u.name)}...`);
167
+ const files = await provider.fetch(u.name);
168
+ installSkill(u.name, files);
169
+ const remote = remoteMap.get(u.name);
170
+ writeSkillMeta(u.name, {
171
+ version: remote.version,
172
+ installedAt: new Date().toISOString(),
173
+ source: providerName,
174
+ description: remote.description,
175
+ fileCount: files.length,
176
+ sizeBytes: files.reduce((s2, f) => s2 + f.content.length, 0),
177
+ });
178
+ updateLockEntry(u.name, remote.version, providerName, files);
179
+ updated++;
180
+ }
181
+ catch (err) {
182
+ if (err instanceof Error)
183
+ p.log.error(`Failed to update ${u.name}: ${err.message}`);
184
+ }
185
+ }
186
+ spin.stop(`Updated ${updated} skill${updated !== 1 ? "s" : ""}`);
187
+ }
@@ -0,0 +1 @@
1
+ export declare function showInteractiveMenu(version: string): Promise<void>;
@@ -0,0 +1,107 @@
1
+ import * as p from "@clack/prompts";
2
+ import chalk from "chalk";
3
+ import { renderBanner } from "../utils/help.js";
4
+ import { loadConfig } from "../utils/config.js";
5
+ import { getProviders, clearProviderCache } from "../registry.js";
6
+ import { getCliReference } from "../command-registry.js";
7
+ import { AMBER, countInstalled, buildMenuOptions } from "./helpers.js";
8
+ import { SKILL_CATEGORIES } from "./categories.js";
9
+ import { browseByCategory } from "./browse.js";
10
+ import { searchFlow } from "./search.js";
11
+ import { quickSetup } from "./setup.js";
12
+ import { manageInstalled } from "./manage.js";
13
+ import { checkHealth } from "./health.js";
14
+ export async function showInteractiveMenu(version) {
15
+ const config = loadConfig();
16
+ const providerName = config.defaultProvider;
17
+ // Fetch skill list once for the session
18
+ const allSkills = [];
19
+ let availableCount = 0;
20
+ try {
21
+ const providers = getProviders();
22
+ for (const provider of providers) {
23
+ const skills = await provider.list();
24
+ allSkills.push(...skills);
25
+ }
26
+ availableCount = allSkills.length;
27
+ }
28
+ catch {
29
+ // Offline mode
30
+ }
31
+ // Banner (shown once)
32
+ const installedOnEntry = countInstalled();
33
+ console.log();
34
+ console.log(renderBanner());
35
+ console.log();
36
+ console.log(` ${AMBER.bold("arcana")} ${chalk.dim(`v${version}`)}`);
37
+ console.log(` ${chalk.dim("Expert skills for AI coding agents. Install what you need.")}`);
38
+ console.log();
39
+ if (availableCount > 0 && installedOnEntry > 0) {
40
+ if (installedOnEntry > availableCount) {
41
+ console.log(` ${chalk.dim(`${installedOnEntry} installed (${availableCount} in marketplace) | provider: ${providerName}`)}`);
42
+ }
43
+ else {
44
+ const pct = Math.round((installedOnEntry / availableCount) * 100);
45
+ console.log(` ${chalk.dim(`${installedOnEntry}/${availableCount} installed (${pct}%) | provider: ${providerName}`)}`);
46
+ }
47
+ }
48
+ else if (availableCount > 0) {
49
+ console.log(` ${chalk.dim(`${availableCount} skills across ${Object.keys(SKILL_CATEGORIES).length} categories`)}`);
50
+ }
51
+ else {
52
+ console.log(` ${chalk.dim(`${installedOnEntry} installed | offline mode`)}`);
53
+ }
54
+ console.log();
55
+ // First-time guided setup
56
+ if (installedOnEntry === 0 && availableCount > 0) {
57
+ const wantsSetup = await p.confirm({
58
+ message: "First time? Let's find the right skills for your project.",
59
+ initialValue: true,
60
+ });
61
+ if (!p.isCancel(wantsSetup) && wantsSetup) {
62
+ await quickSetup(allSkills, providerName);
63
+ }
64
+ }
65
+ // Main loop
66
+ while (true) {
67
+ const installedCount = countInstalled();
68
+ const options = buildMenuOptions(installedCount, availableCount);
69
+ const selected = await p.select({
70
+ message: "What would you like to do?",
71
+ options,
72
+ });
73
+ if (p.isCancel(selected) || selected === "exit") {
74
+ clearProviderCache();
75
+ p.outro(chalk.dim("Until next time."));
76
+ return;
77
+ }
78
+ console.log();
79
+ try {
80
+ switch (selected) {
81
+ case "browse":
82
+ await browseByCategory(allSkills, providerName);
83
+ break;
84
+ case "search":
85
+ await searchFlow(allSkills, providerName);
86
+ break;
87
+ case "setup":
88
+ await quickSetup(allSkills, providerName);
89
+ break;
90
+ case "installed":
91
+ await manageInstalled(allSkills, providerName);
92
+ break;
93
+ case "health":
94
+ await checkHealth();
95
+ break;
96
+ case "ref":
97
+ p.note(getCliReference(), "CLI Reference");
98
+ break;
99
+ }
100
+ }
101
+ catch (err) {
102
+ if (err instanceof Error) {
103
+ p.log.error(err.message);
104
+ }
105
+ }
106
+ }
107
+ }
@@ -0,0 +1,2 @@
1
+ import type { SkillInfo } from "../types.js";
2
+ export declare function searchFlow(allSkills: SkillInfo[], providerName: string): Promise<void>;
@@ -0,0 +1,66 @@
1
+ import * as p from "@clack/prompts";
2
+ import chalk from "chalk";
3
+ import { isSkillInstalled } from "../utils/fs.js";
4
+ import { getProvider } from "../registry.js";
5
+ import { ui } from "../utils/ui.js";
6
+ import { appendHistory } from "../utils/history.js";
7
+ import { handleCancel, truncate } from "./helpers.js";
8
+ import { skillDetailFlow } from "./skill-detail.js";
9
+ export async function searchFlow(allSkills, providerName) {
10
+ while (true) {
11
+ const query = await p.text({
12
+ message: "Search for:",
13
+ placeholder: "e.g. testing, review, golang",
14
+ validate: (v) => (!v || v.trim().length === 0 ? "Enter a search term" : undefined),
15
+ });
16
+ handleCancel(query);
17
+ const provider = getProvider(providerName);
18
+ const s = p.spinner();
19
+ s.start(`Searching "${query}"...`);
20
+ let results;
21
+ try {
22
+ results = await provider.search(query);
23
+ }
24
+ catch (err) {
25
+ s.stop("Search failed");
26
+ if (err instanceof Error)
27
+ p.log.error(ui.dim(err.message));
28
+ return;
29
+ }
30
+ s.stop(`${results.length} result${results.length !== 1 ? "s" : ""}`);
31
+ appendHistory("search", query);
32
+ if (results.length === 0) {
33
+ p.log.info("No skills matched. Try a different query.");
34
+ const again = await p.confirm({ message: "Search again?" });
35
+ handleCancel(again);
36
+ if (!again)
37
+ return;
38
+ continue;
39
+ }
40
+ const nav = await searchResultsPicker(results, allSkills, providerName);
41
+ if (nav === "done")
42
+ return;
43
+ // "search" continues the loop
44
+ }
45
+ }
46
+ async function searchResultsPicker(results, allSkills, providerName) {
47
+ while (true) {
48
+ const options = results.map((skill) => ({
49
+ value: skill.name,
50
+ label: `${skill.name}${isSkillInstalled(skill.name) ? chalk.green(" \u2713") : ""}`,
51
+ hint: truncate(skill.description, 50),
52
+ }));
53
+ const picked = await p.select({
54
+ message: "Pick a skill for details",
55
+ options: [...options, { value: "__search", label: "Search again" }, { value: "__back", label: "Back" }],
56
+ });
57
+ handleCancel(picked);
58
+ if (picked === "__search")
59
+ return "search";
60
+ if (picked === "__back")
61
+ return "done";
62
+ const result = await skillDetailFlow(picked, allSkills, providerName);
63
+ if (result === "menu")
64
+ return "done";
65
+ }
66
+ }
@@ -0,0 +1,2 @@
1
+ import type { SkillInfo } from "../types.js";
2
+ export declare function quickSetup(allSkills: SkillInfo[], providerName: string): Promise<void>;
@@ -0,0 +1,48 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import * as p from "@clack/prompts";
4
+ import chalk from "chalk";
5
+ import { isSkillInstalled } from "../utils/fs.js";
6
+ import { handleCancel } from "./helpers.js";
7
+ import { doBatchInstall } from "./browse.js";
8
+ export async function quickSetup(allSkills, providerName) {
9
+ const { detectProject, SKILL_SUGGESTIONS, SKILL_SUGGESTIONS_DEFAULT } = await import("../commands/init.js");
10
+ const proj = detectProject(process.cwd());
11
+ p.log.step(`Detected: ${chalk.cyan(proj.name)} (${proj.type})`);
12
+ const suggestions = SKILL_SUGGESTIONS[proj.type] ?? SKILL_SUGGESTIONS_DEFAULT;
13
+ const availableNames = new Set(allSkills.map((s) => s.name));
14
+ const validSuggestions = suggestions.filter((s) => availableNames.has(s));
15
+ if (validSuggestions.length === 0) {
16
+ p.log.info("No specific recommendations for this project type.");
17
+ return;
18
+ }
19
+ const notInstalled = validSuggestions.filter((s) => !isSkillInstalled(s));
20
+ if (notInstalled.length === 0) {
21
+ p.log.success("All recommended skills are already installed.");
22
+ return;
23
+ }
24
+ const options = validSuggestions.map((name) => {
25
+ const installed = isSkillInstalled(name);
26
+ return {
27
+ value: name,
28
+ label: `${name}${installed ? chalk.green(" \u2713 installed") : ""}`,
29
+ hint: installed ? "already installed" : "not installed",
30
+ };
31
+ });
32
+ const selected = await p.multiselect({
33
+ message: `Recommended skills for ${proj.type}`,
34
+ options,
35
+ required: false,
36
+ });
37
+ handleCancel(selected);
38
+ const toInstall = selected.filter((s) => !isSkillInstalled(s));
39
+ if (toInstall.length > 0) {
40
+ await doBatchInstall(toInstall, providerName);
41
+ }
42
+ else if (selected.length > 0) {
43
+ p.log.info("All selected skills are already installed.");
44
+ }
45
+ if (!existsSync(join(process.cwd(), "CLAUDE.md"))) {
46
+ p.log.info(`Tip: Run ${chalk.cyan("arcana init")} to create project config files.`);
47
+ }
48
+ }
@@ -0,0 +1,5 @@
1
+ import type { SkillInfo } from "../types.js";
2
+ declare function doInstall(skillName: string, providerName: string): Promise<boolean>;
3
+ declare function doUninstall(skillName: string): boolean;
4
+ export declare function skillDetailFlow(skillName: string, allSkills: SkillInfo[], providerName: string): Promise<"back" | "menu">;
5
+ export { doInstall, doUninstall };
@@ -0,0 +1,126 @@
1
+ import { existsSync, rmSync } from "node:fs";
2
+ import * as p from "@clack/prompts";
3
+ import chalk from "chalk";
4
+ import { isSkillInstalled, readSkillMeta, getSkillDir } from "../utils/fs.js";
5
+ import { getProvider } from "../registry.js";
6
+ import { appendHistory } from "../utils/history.js";
7
+ import { installOneCore } from "../utils/install-core.js";
8
+ import { removeSymlinksFor } from "../commands/uninstall.js";
9
+ import { ui } from "../utils/ui.js";
10
+ import { handleCancel } from "./helpers.js";
11
+ import { getCategoryFor, getRelatedSkills } from "./categories.js";
12
+ async function doInstall(skillName, providerName) {
13
+ const provider = getProvider(providerName);
14
+ const s = p.spinner();
15
+ s.start(`Installing ${chalk.bold(skillName)}...`);
16
+ try {
17
+ const result = await installOneCore(skillName, provider, {});
18
+ if (!result.success) {
19
+ s.stop(`Failed to install ${skillName}`);
20
+ if (result.error)
21
+ p.log.error(ui.dim(result.error));
22
+ return false;
23
+ }
24
+ s.stop(`Installed ${chalk.bold(skillName)} (${result.files?.length ?? 0} files)`);
25
+ appendHistory("install", skillName);
26
+ return true;
27
+ }
28
+ catch (err) {
29
+ s.stop(`Failed to install ${skillName}`);
30
+ if (err instanceof Error)
31
+ p.log.error(ui.dim(err.message));
32
+ return false;
33
+ }
34
+ }
35
+ function doUninstall(skillName) {
36
+ const skillDir = getSkillDir(skillName);
37
+ if (!existsSync(skillDir))
38
+ return false;
39
+ try {
40
+ rmSync(skillDir, { recursive: true, force: true });
41
+ removeSymlinksFor(skillName);
42
+ appendHistory("uninstall", skillName);
43
+ return true;
44
+ }
45
+ catch {
46
+ return false;
47
+ }
48
+ }
49
+ export async function skillDetailFlow(skillName, allSkills, providerName) {
50
+ const info = allSkills.find((s) => s.name === skillName);
51
+ const installed = isSkillInstalled(skillName);
52
+ const meta = installed ? readSkillMeta(skillName) : null;
53
+ // Build info block
54
+ const lines = [];
55
+ lines.push(`${chalk.bold(skillName)} ${info ? `v${info.version}` : ""}`);
56
+ if (info?.description)
57
+ lines.push(info.description);
58
+ lines.push("");
59
+ if (info?.verified)
60
+ lines.push(`Trust: ${chalk.green("Verified")} (official)`);
61
+ else
62
+ lines.push(`Trust: Community`);
63
+ if (info?.author)
64
+ lines.push(`Author: ${info.author}`);
65
+ if (info?.tags && info.tags.length > 0)
66
+ lines.push(`Tags: ${info.tags.join(", ")}`);
67
+ const category = getCategoryFor(skillName);
68
+ if (category)
69
+ lines.push(`Category: ${category}`);
70
+ if (info?.source)
71
+ lines.push(`Source: ${info.source}`);
72
+ if (info?.companions && info.companions.length > 0) {
73
+ lines.push(`Companions: ${info.companions.join(", ")}`);
74
+ }
75
+ if (info?.conflicts && info.conflicts.length > 0) {
76
+ lines.push(`${chalk.red("Conflicts:")} ${info.conflicts.join(", ")}`);
77
+ }
78
+ if (installed && meta) {
79
+ const date = meta.installedAt ? new Date(meta.installedAt).toLocaleDateString() : "";
80
+ lines.push(`Status: ${chalk.green("installed")} (v${meta.version}${date ? `, ${date}` : ""})`);
81
+ }
82
+ else {
83
+ lines.push(`Status: ${chalk.dim("not installed")}`);
84
+ }
85
+ const related = getRelatedSkills(skillName);
86
+ if (related.length > 0) {
87
+ lines.push(`Related: ${related.join(", ")}`);
88
+ }
89
+ p.note(lines.join("\n"), skillName);
90
+ // Action menu
91
+ const actions = [];
92
+ if (installed) {
93
+ actions.push({ value: "reinstall", label: "Reinstall (overwrite)" });
94
+ actions.push({ value: "uninstall", label: "Uninstall (remove files)" });
95
+ }
96
+ else {
97
+ actions.push({ value: "install", label: "Install this skill" });
98
+ }
99
+ actions.push({ value: "back", label: "Back" });
100
+ const action = await p.select({ message: "Action", options: actions });
101
+ handleCancel(action);
102
+ switch (action) {
103
+ case "install":
104
+ case "reinstall": {
105
+ await doInstall(skillName, providerName);
106
+ return "back";
107
+ }
108
+ case "uninstall": {
109
+ const ok = await p.confirm({ message: `Uninstall ${chalk.bold(skillName)}?` });
110
+ handleCancel(ok);
111
+ if (ok) {
112
+ const success = doUninstall(skillName);
113
+ if (success) {
114
+ p.log.success(`Removed ${chalk.bold(skillName)}`);
115
+ }
116
+ else {
117
+ p.log.error(`Failed to remove ${skillName}`);
118
+ }
119
+ }
120
+ return "back";
121
+ }
122
+ default:
123
+ return "back";
124
+ }
125
+ }
126
+ export { doInstall, doUninstall };
@@ -1,2 +1 @@
1
1
  export declare function showInteractiveMenu(version: string): Promise<void>;
2
- //# sourceMappingURL=interactive.d.ts.map