@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,103 @@
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 { installOneCore } from "../utils/install-core.js";
6
+ import { appendHistory } from "../utils/history.js";
7
+ import { ui } from "../utils/ui.js";
8
+ import { handleCancel, truncate } from "./helpers.js";
9
+ import { SKILL_CATEGORIES } from "./categories.js";
10
+ import { skillDetailFlow } from "./skill-detail.js";
11
+ async function doBatchInstall(names, providerName) {
12
+ if (names.length === 0)
13
+ return 0;
14
+ const provider = getProvider(providerName);
15
+ const s = p.spinner();
16
+ s.start(`Installing ${names.length} skill${names.length > 1 ? "s" : ""}...`);
17
+ let installed = 0;
18
+ for (const name of names) {
19
+ try {
20
+ s.message(`Installing ${chalk.bold(name)} (${installed + 1}/${names.length})...`);
21
+ const result = await installOneCore(name, provider, {});
22
+ if (result.success) {
23
+ installed++;
24
+ appendHistory("install", name);
25
+ }
26
+ }
27
+ catch (err) {
28
+ s.stop(`Failed: ${name}`);
29
+ if (err instanceof Error)
30
+ p.log.error(ui.dim(err.message));
31
+ if (installed + 1 < names.length)
32
+ s.start(`Installing next...`);
33
+ }
34
+ }
35
+ s.stop(`Installed ${installed} skill${installed !== 1 ? "s" : ""}`);
36
+ return installed;
37
+ }
38
+ export async function browseByCategory(allSkills, providerName) {
39
+ const availableNames = new Set(allSkills.map((s) => s.name));
40
+ while (true) {
41
+ const categoryOptions = Object.entries(SKILL_CATEGORIES).map(([name, skills]) => {
42
+ const valid = skills.filter((s) => availableNames.has(s));
43
+ const installedCount = valid.filter((s) => isSkillInstalled(s)).length;
44
+ return {
45
+ value: name,
46
+ label: name,
47
+ hint: `${valid.length} skills, ${installedCount} installed`,
48
+ };
49
+ });
50
+ const category = await p.select({
51
+ message: "Browse by category",
52
+ options: [...categoryOptions, { value: "__back", label: "Back" }],
53
+ });
54
+ handleCancel(category);
55
+ if (category === "__back")
56
+ return;
57
+ await categorySkillList(category, SKILL_CATEGORIES[category] ?? [], allSkills, providerName);
58
+ }
59
+ }
60
+ async function categorySkillList(categoryName, skillNames, allSkills, providerName) {
61
+ const availableNames = new Set(allSkills.map((s) => s.name));
62
+ const validSkills = skillNames.filter((s) => availableNames.has(s));
63
+ if (validSkills.length === 0) {
64
+ p.log.warn("No skills found in this category.");
65
+ return;
66
+ }
67
+ while (true) {
68
+ const options = validSkills.map((name) => {
69
+ const info = allSkills.find((s) => s.name === name);
70
+ const installed = isSkillInstalled(name);
71
+ return {
72
+ value: name,
73
+ label: `${name}${installed ? chalk.green(" \u2713") : ""}`,
74
+ hint: truncate(info?.description ?? "", 50),
75
+ };
76
+ });
77
+ const notInstalled = validSkills.filter((s) => !isSkillInstalled(s));
78
+ const extraOptions = [];
79
+ if (notInstalled.length > 0) {
80
+ extraOptions.push({
81
+ value: "__install_all",
82
+ label: `Install all uninstalled`,
83
+ hint: `${notInstalled.length} skill${notInstalled.length > 1 ? "s" : ""}`,
84
+ });
85
+ }
86
+ extraOptions.push({ value: "__back", label: "Back to categories" });
87
+ const picked = await p.select({
88
+ message: `${categoryName} (${validSkills.length} skills)`,
89
+ options: [...options, ...extraOptions],
90
+ });
91
+ handleCancel(picked);
92
+ if (picked === "__back")
93
+ return;
94
+ if (picked === "__install_all") {
95
+ await doBatchInstall(notInstalled, providerName);
96
+ continue;
97
+ }
98
+ const result = await skillDetailFlow(picked, allSkills, providerName);
99
+ if (result === "menu")
100
+ return;
101
+ }
102
+ }
103
+ export { doBatchInstall };
@@ -0,0 +1,4 @@
1
+ declare const SKILL_CATEGORIES: Record<string, string[]>;
2
+ export { SKILL_CATEGORIES };
3
+ export declare function getCategoryFor(skillName: string): string | undefined;
4
+ export declare function getRelatedSkills(skillName: string, limit?: number): string[];
@@ -0,0 +1,87 @@
1
+ import { isSkillInstalled } from "../utils/fs.js";
2
+ // Category map (7 categories, 4-12 skills each, no orphans)
3
+ const SKILL_CATEGORIES = {
4
+ "Code Quality & Review": [
5
+ "code-reviewer",
6
+ "codebase-dissection",
7
+ "testing-strategy",
8
+ "refactoring-patterns",
9
+ "git-workflow",
10
+ "pre-production-review",
11
+ "frontend-code-review",
12
+ "dependency-audit",
13
+ "performance-optimization",
14
+ ],
15
+ "Security & Infrastructure": [
16
+ "security-review",
17
+ "local-security",
18
+ "container-security",
19
+ "docker-kubernetes",
20
+ "ci-cd-pipelines",
21
+ "ci-cd-automation",
22
+ "monitoring-observability",
23
+ "incident-response",
24
+ ],
25
+ "Languages & Frameworks": [
26
+ "golang-pro",
27
+ "go-linter-configuration",
28
+ "typescript",
29
+ "typescript-advanced",
30
+ "python-best-practices",
31
+ "rust-best-practices",
32
+ "frontend-design",
33
+ "fullstack-developer",
34
+ "remotion-best-practices",
35
+ "npm-package",
36
+ ],
37
+ "API, Data & Docs": [
38
+ "api-design",
39
+ "api-testing",
40
+ "programming-architecture",
41
+ "database-design",
42
+ "env-config",
43
+ "cost-optimization",
44
+ "docx",
45
+ "xlsx",
46
+ "doc-generation",
47
+ "update-docs",
48
+ ],
49
+ "Game Design & Production": [
50
+ "game-design-theory",
51
+ "game-engines",
52
+ "game-programming-languages",
53
+ "gameplay-mechanics",
54
+ "level-design",
55
+ "game-tools-workflows",
56
+ "game-servers",
57
+ "networking-servers",
58
+ "synchronization-algorithms",
59
+ "monetization-systems",
60
+ "publishing-platforms",
61
+ "daw-music",
62
+ ],
63
+ "Graphics, Audio & Performance": [
64
+ "graphics-rendering",
65
+ "shader-techniques",
66
+ "particle-systems",
67
+ "audio-systems",
68
+ "asset-optimization",
69
+ "optimization-performance",
70
+ "memory-management",
71
+ ],
72
+ "Skill Development": ["skill-creation-guide", "skill-creator", "find-skills", "project-migration"],
73
+ };
74
+ export { SKILL_CATEGORIES };
75
+ export function getCategoryFor(skillName) {
76
+ for (const [cat, skills] of Object.entries(SKILL_CATEGORIES)) {
77
+ if (skills.includes(skillName))
78
+ return cat;
79
+ }
80
+ return undefined;
81
+ }
82
+ export function getRelatedSkills(skillName, limit = 3) {
83
+ const cat = getCategoryFor(skillName);
84
+ if (!cat)
85
+ return [];
86
+ return (SKILL_CATEGORIES[cat] ?? []).filter((s) => s !== skillName && !isSkillInstalled(s)).slice(0, limit);
87
+ }
@@ -0,0 +1 @@
1
+ export declare function checkHealth(): Promise<void>;
@@ -0,0 +1,57 @@
1
+ import * as p from "@clack/prompts";
2
+ import chalk from "chalk";
3
+ import { runDoctorChecks } from "../commands/doctor.js";
4
+ import { handleCancel } from "./helpers.js";
5
+ export async function checkHealth() {
6
+ const checks = runDoctorChecks();
7
+ p.log.step(chalk.bold("Environment Health Check"));
8
+ for (const check of checks) {
9
+ const icon = check.status === "pass" ? chalk.green("OK") : check.status === "warn" ? chalk.yellow("!!") : chalk.red("XX");
10
+ p.log.info(`${icon} ${chalk.bold(check.name)}: ${check.message}`);
11
+ if (check.fix)
12
+ p.log.info(chalk.dim(` Fix: ${check.fix}`));
13
+ }
14
+ const fails = checks.filter((c) => c.status === "fail").length;
15
+ const warns = checks.filter((c) => c.status === "warn").length;
16
+ if (fails > 0) {
17
+ p.log.error(`${fails} issue${fails > 1 ? "s" : ""} found`);
18
+ }
19
+ else if (warns > 0) {
20
+ p.log.warn(`${warns} warning${warns > 1 ? "s" : ""}`);
21
+ }
22
+ else {
23
+ p.log.success("All checks passed");
24
+ return;
25
+ }
26
+ // Offer fixes once
27
+ const fixChecks = checks.filter((c) => c.fix && c.status !== "pass");
28
+ if (fixChecks.length === 0)
29
+ return;
30
+ const fixOptions = fixChecks.map((c) => {
31
+ const cmd = c.fix.replace(/^Run:\s*/, "");
32
+ return { value: cmd, label: `Run: ${cmd}`, hint: c.name };
33
+ });
34
+ const fixAction = await p.select({
35
+ message: "Run a fix?",
36
+ options: [...fixOptions, { value: "__skip", label: "Skip" }],
37
+ });
38
+ handleCancel(fixAction);
39
+ if (fixAction !== "__skip") {
40
+ const cmd = fixAction;
41
+ const SAFE_PREFIXES = ["arcana ", "git config "];
42
+ if (!SAFE_PREFIXES.some((pre) => cmd.startsWith(pre))) {
43
+ p.log.warn(`Skipped unsafe command: ${cmd}`);
44
+ }
45
+ else {
46
+ p.log.info(chalk.dim(`Running: ${cmd}`));
47
+ try {
48
+ const { execSync } = await import("node:child_process");
49
+ execSync(cmd, { stdio: "inherit" });
50
+ }
51
+ catch {
52
+ // Non-zero exit expected for some commands
53
+ }
54
+ }
55
+ p.log.info(chalk.dim("Run health check again to verify."));
56
+ }
57
+ }
@@ -0,0 +1,11 @@
1
+ export declare const AMBER: import("chalk").ChalkInstance;
2
+ export declare function cancelAndExit(): never;
3
+ export declare function handleCancel(value: unknown): void;
4
+ export declare function countInstalled(): number;
5
+ export declare function truncate(str: string, max: number): string;
6
+ export declare function getInstalledNames(): string[];
7
+ export declare function buildMenuOptions(installedCount: number, _availableCount: number): {
8
+ value: string;
9
+ label: string;
10
+ hint?: string;
11
+ }[];
@@ -0,0 +1,66 @@
1
+ import { existsSync, readdirSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import * as p from "@clack/prompts";
4
+ import chalk from "chalk";
5
+ import { getInstallDir } from "../utils/fs.js";
6
+ import { clearProviderCache } from "../registry.js";
7
+ export const AMBER = chalk.hex("#d4943a");
8
+ export function cancelAndExit() {
9
+ clearProviderCache();
10
+ p.cancel("Goodbye.");
11
+ process.exit(0);
12
+ }
13
+ export function handleCancel(value) {
14
+ if (p.isCancel(value))
15
+ cancelAndExit();
16
+ }
17
+ export function countInstalled() {
18
+ const dir = getInstallDir();
19
+ if (!existsSync(dir))
20
+ return 0;
21
+ return readdirSync(dir).filter((d) => {
22
+ try {
23
+ return statSync(join(dir, d)).isDirectory();
24
+ }
25
+ catch {
26
+ return false;
27
+ }
28
+ }).length;
29
+ }
30
+ export function truncate(str, max) {
31
+ return str.length > max ? str.slice(0, max) + "..." : str;
32
+ }
33
+ export function getInstalledNames() {
34
+ const dir = getInstallDir();
35
+ if (!existsSync(dir))
36
+ return [];
37
+ return readdirSync(dir)
38
+ .filter((d) => {
39
+ try {
40
+ return statSync(join(dir, d)).isDirectory();
41
+ }
42
+ catch {
43
+ return false;
44
+ }
45
+ })
46
+ .sort();
47
+ }
48
+ export function buildMenuOptions(installedCount, _availableCount) {
49
+ const isNew = installedCount === 0;
50
+ const options = [];
51
+ if (isNew) {
52
+ options.push({ value: "setup", label: AMBER("Get Started"), hint: "detect project, install recommended skills" });
53
+ }
54
+ else {
55
+ options.push({ value: "installed", label: "Manage installed skills", hint: `${installedCount} installed` });
56
+ }
57
+ options.push({ value: "browse", label: "Browse skills by category" });
58
+ options.push({ value: "search", label: "Search for a skill" });
59
+ if (!isNew) {
60
+ options.push({ value: "setup", label: "Get Started", hint: "detect project, add more skills" });
61
+ }
62
+ options.push({ value: "health", label: "Check environment health" });
63
+ options.push({ value: "ref", label: "CLI reference" });
64
+ options.push({ value: "exit", label: "Exit" });
65
+ return options;
66
+ }
@@ -0,0 +1 @@
1
+ export { showInteractiveMenu } from "./menu.js";
@@ -0,0 +1 @@
1
+ export { showInteractiveMenu } from "./menu.js";
@@ -0,0 +1,2 @@
1
+ import type { SkillInfo } from "../types.js";
2
+ export declare function manageInstalled(allSkills: SkillInfo[], providerName: string): Promise<void>;
@@ -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>;