@detergent-software/atk 0.12.1-dev.2

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 (314) hide show
  1. package/build/cli.d.ts +3 -0
  2. package/build/cli.d.ts.map +1 -0
  3. package/build/cli.js +35 -0
  4. package/build/cli.js.map +1 -0
  5. package/build/commands/_app.d.ts +3 -0
  6. package/build/commands/_app.d.ts.map +1 -0
  7. package/build/commands/_app.js +12 -0
  8. package/build/commands/_app.js.map +1 -0
  9. package/build/commands/audit.d.ts +12 -0
  10. package/build/commands/audit.d.ts.map +1 -0
  11. package/build/commands/audit.js +78 -0
  12. package/build/commands/audit.js.map +1 -0
  13. package/build/commands/browse.d.ts +15 -0
  14. package/build/commands/browse.d.ts.map +1 -0
  15. package/build/commands/browse.js +284 -0
  16. package/build/commands/browse.js.map +1 -0
  17. package/build/commands/cache.d.ts +13 -0
  18. package/build/commands/cache.d.ts.map +1 -0
  19. package/build/commands/cache.js +67 -0
  20. package/build/commands/cache.js.map +1 -0
  21. package/build/commands/config.d.ts +13 -0
  22. package/build/commands/config.d.ts.map +1 -0
  23. package/build/commands/config.js +62 -0
  24. package/build/commands/config.js.map +1 -0
  25. package/build/commands/diff.d.ts +16 -0
  26. package/build/commands/diff.d.ts.map +1 -0
  27. package/build/commands/diff.js +158 -0
  28. package/build/commands/diff.js.map +1 -0
  29. package/build/commands/doctor.d.ts +11 -0
  30. package/build/commands/doctor.d.ts.map +1 -0
  31. package/build/commands/doctor.js +30 -0
  32. package/build/commands/doctor.js.map +1 -0
  33. package/build/commands/index.d.ts +3 -0
  34. package/build/commands/index.d.ts.map +1 -0
  35. package/build/commands/index.js +30 -0
  36. package/build/commands/index.js.map +1 -0
  37. package/build/commands/info.d.ts +25 -0
  38. package/build/commands/info.d.ts.map +1 -0
  39. package/build/commands/info.js +198 -0
  40. package/build/commands/info.js.map +1 -0
  41. package/build/commands/init.d.ts +17 -0
  42. package/build/commands/init.d.ts.map +1 -0
  43. package/build/commands/init.js +504 -0
  44. package/build/commands/init.js.map +1 -0
  45. package/build/commands/install.d.ts +18 -0
  46. package/build/commands/install.d.ts.map +1 -0
  47. package/build/commands/install.js +186 -0
  48. package/build/commands/install.js.map +1 -0
  49. package/build/commands/list.d.ts +20 -0
  50. package/build/commands/list.d.ts.map +1 -0
  51. package/build/commands/list.js +61 -0
  52. package/build/commands/list.js.map +1 -0
  53. package/build/commands/outdated.d.ts +21 -0
  54. package/build/commands/outdated.d.ts.map +1 -0
  55. package/build/commands/outdated.js +81 -0
  56. package/build/commands/outdated.js.map +1 -0
  57. package/build/commands/pin.d.ts +22 -0
  58. package/build/commands/pin.d.ts.map +1 -0
  59. package/build/commands/pin.js +94 -0
  60. package/build/commands/pin.js.map +1 -0
  61. package/build/commands/prune.d.ts +14 -0
  62. package/build/commands/prune.d.ts.map +1 -0
  63. package/build/commands/prune.js +203 -0
  64. package/build/commands/prune.js.map +1 -0
  65. package/build/commands/publish.d.ts +14 -0
  66. package/build/commands/publish.d.ts.map +1 -0
  67. package/build/commands/publish.js +345 -0
  68. package/build/commands/publish.js.map +1 -0
  69. package/build/commands/search.d.ts +16 -0
  70. package/build/commands/search.d.ts.map +1 -0
  71. package/build/commands/search.js +80 -0
  72. package/build/commands/search.js.map +1 -0
  73. package/build/commands/sync.d.ts +15 -0
  74. package/build/commands/sync.d.ts.map +1 -0
  75. package/build/commands/sync.js +209 -0
  76. package/build/commands/sync.js.map +1 -0
  77. package/build/commands/uninstall.d.ts +18 -0
  78. package/build/commands/uninstall.d.ts.map +1 -0
  79. package/build/commands/uninstall.js +304 -0
  80. package/build/commands/uninstall.js.map +1 -0
  81. package/build/commands/unpin.d.ts +22 -0
  82. package/build/commands/unpin.d.ts.map +1 -0
  83. package/build/commands/unpin.js +79 -0
  84. package/build/commands/unpin.js.map +1 -0
  85. package/build/commands/update.d.ts +17 -0
  86. package/build/commands/update.d.ts.map +1 -0
  87. package/build/commands/update.js +151 -0
  88. package/build/commands/update.js.map +1 -0
  89. package/build/commands/why.d.ts +23 -0
  90. package/build/commands/why.d.ts.map +1 -0
  91. package/build/commands/why.js +108 -0
  92. package/build/commands/why.js.map +1 -0
  93. package/build/components/AssetDetail.d.ts +32 -0
  94. package/build/components/AssetDetail.d.ts.map +1 -0
  95. package/build/components/AssetDetail.js +45 -0
  96. package/build/components/AssetDetail.js.map +1 -0
  97. package/build/components/AssetTable.d.ts +25 -0
  98. package/build/components/AssetTable.d.ts.map +1 -0
  99. package/build/components/AssetTable.js +96 -0
  100. package/build/components/AssetTable.js.map +1 -0
  101. package/build/components/BrowseExpandedBundle.d.ts +26 -0
  102. package/build/components/BrowseExpandedBundle.d.ts.map +1 -0
  103. package/build/components/BrowseExpandedBundle.js +59 -0
  104. package/build/components/BrowseExpandedBundle.js.map +1 -0
  105. package/build/components/BrowseExpandedRow.d.ts +14 -0
  106. package/build/components/BrowseExpandedRow.d.ts.map +1 -0
  107. package/build/components/BrowseExpandedRow.js +9 -0
  108. package/build/components/BrowseExpandedRow.js.map +1 -0
  109. package/build/components/BrowseList.d.ts +26 -0
  110. package/build/components/BrowseList.d.ts.map +1 -0
  111. package/build/components/BrowseList.js +101 -0
  112. package/build/components/BrowseList.js.map +1 -0
  113. package/build/components/BundleDetail.d.ts +7 -0
  114. package/build/components/BundleDetail.d.ts.map +1 -0
  115. package/build/components/BundleDetail.js +12 -0
  116. package/build/components/BundleDetail.js.map +1 -0
  117. package/build/components/DependencyTree.d.ts +19 -0
  118. package/build/components/DependencyTree.d.ts.map +1 -0
  119. package/build/components/DependencyTree.js +21 -0
  120. package/build/components/DependencyTree.js.map +1 -0
  121. package/build/components/DiagnosticList.d.ts +19 -0
  122. package/build/components/DiagnosticList.d.ts.map +1 -0
  123. package/build/components/DiagnosticList.js +17 -0
  124. package/build/components/DiagnosticList.js.map +1 -0
  125. package/build/components/DiffView.d.ts +30 -0
  126. package/build/components/DiffView.d.ts.map +1 -0
  127. package/build/components/DiffView.js +32 -0
  128. package/build/components/DiffView.js.map +1 -0
  129. package/build/components/DryRunBanner.d.ts +11 -0
  130. package/build/components/DryRunBanner.d.ts.map +1 -0
  131. package/build/components/DryRunBanner.js +6 -0
  132. package/build/components/DryRunBanner.js.map +1 -0
  133. package/build/components/Field.d.ts +12 -0
  134. package/build/components/Field.d.ts.map +1 -0
  135. package/build/components/Field.js +9 -0
  136. package/build/components/Field.js.map +1 -0
  137. package/build/components/FilterBar.d.ts +18 -0
  138. package/build/components/FilterBar.d.ts.map +1 -0
  139. package/build/components/FilterBar.js +21 -0
  140. package/build/components/FilterBar.js.map +1 -0
  141. package/build/components/Header.d.ts +13 -0
  142. package/build/components/Header.d.ts.map +1 -0
  143. package/build/components/Header.js +20 -0
  144. package/build/components/Header.js.map +1 -0
  145. package/build/components/HelpBar.d.ts +11 -0
  146. package/build/components/HelpBar.d.ts.map +1 -0
  147. package/build/components/HelpBar.js +9 -0
  148. package/build/components/HelpBar.js.map +1 -0
  149. package/build/components/InstallConfirmBar.d.ts +9 -0
  150. package/build/components/InstallConfirmBar.d.ts.map +1 -0
  151. package/build/components/InstallConfirmBar.js +23 -0
  152. package/build/components/InstallConfirmBar.js.map +1 -0
  153. package/build/components/InstallSummary.d.ts +29 -0
  154. package/build/components/InstallSummary.d.ts.map +1 -0
  155. package/build/components/InstallSummary.js +12 -0
  156. package/build/components/InstallSummary.js.map +1 -0
  157. package/build/components/SectionDivider.d.ts +5 -0
  158. package/build/components/SectionDivider.d.ts.map +1 -0
  159. package/build/components/SectionDivider.js +11 -0
  160. package/build/components/SectionDivider.js.map +1 -0
  161. package/build/components/Spinner.d.ts +13 -0
  162. package/build/components/Spinner.d.ts.map +1 -0
  163. package/build/components/Spinner.js +11 -0
  164. package/build/components/Spinner.js.map +1 -0
  165. package/build/components/StatusBadge.d.ts +23 -0
  166. package/build/components/StatusBadge.d.ts.map +1 -0
  167. package/build/components/StatusBadge.js +43 -0
  168. package/build/components/StatusBadge.js.map +1 -0
  169. package/build/components/index.d.ts +20 -0
  170. package/build/components/index.d.ts.map +1 -0
  171. package/build/components/index.js +20 -0
  172. package/build/components/index.js.map +1 -0
  173. package/build/hooks/useBrowseState.d.ts +122 -0
  174. package/build/hooks/useBrowseState.d.ts.map +1 -0
  175. package/build/hooks/useBrowseState.js +315 -0
  176. package/build/hooks/useBrowseState.js.map +1 -0
  177. package/build/hooks/useCommand.d.ts +23 -0
  178. package/build/hooks/useCommand.d.ts.map +1 -0
  179. package/build/hooks/useCommand.js +43 -0
  180. package/build/hooks/useCommand.js.map +1 -0
  181. package/build/lib/adapter.d.ts +40 -0
  182. package/build/lib/adapter.d.ts.map +1 -0
  183. package/build/lib/adapter.js +112 -0
  184. package/build/lib/adapter.js.map +1 -0
  185. package/build/lib/auth.d.ts +31 -0
  186. package/build/lib/auth.d.ts.map +1 -0
  187. package/build/lib/auth.js +101 -0
  188. package/build/lib/auth.js.map +1 -0
  189. package/build/lib/breakpoints.d.ts +3 -0
  190. package/build/lib/breakpoints.d.ts.map +1 -0
  191. package/build/lib/breakpoints.js +8 -0
  192. package/build/lib/breakpoints.js.map +1 -0
  193. package/build/lib/checksum.d.ts +31 -0
  194. package/build/lib/checksum.d.ts.map +1 -0
  195. package/build/lib/checksum.js +78 -0
  196. package/build/lib/checksum.js.map +1 -0
  197. package/build/lib/config.d.ts +42 -0
  198. package/build/lib/config.d.ts.map +1 -0
  199. package/build/lib/config.js +123 -0
  200. package/build/lib/config.js.map +1 -0
  201. package/build/lib/detector.d.ts +35 -0
  202. package/build/lib/detector.d.ts.map +1 -0
  203. package/build/lib/detector.js +125 -0
  204. package/build/lib/detector.js.map +1 -0
  205. package/build/lib/diagnostics.d.ts +10 -0
  206. package/build/lib/diagnostics.d.ts.map +1 -0
  207. package/build/lib/diagnostics.js +195 -0
  208. package/build/lib/diagnostics.js.map +1 -0
  209. package/build/lib/diff.d.ts +34 -0
  210. package/build/lib/diff.d.ts.map +1 -0
  211. package/build/lib/diff.js +78 -0
  212. package/build/lib/diff.js.map +1 -0
  213. package/build/lib/fetch-retry.d.ts +43 -0
  214. package/build/lib/fetch-retry.d.ts.map +1 -0
  215. package/build/lib/fetch-retry.js +137 -0
  216. package/build/lib/fetch-retry.js.map +1 -0
  217. package/build/lib/github.d.ts +57 -0
  218. package/build/lib/github.d.ts.map +1 -0
  219. package/build/lib/github.js +96 -0
  220. package/build/lib/github.js.map +1 -0
  221. package/build/lib/gitignore.d.ts +37 -0
  222. package/build/lib/gitignore.d.ts.map +1 -0
  223. package/build/lib/gitignore.js +142 -0
  224. package/build/lib/gitignore.js.map +1 -0
  225. package/build/lib/init.d.ts +100 -0
  226. package/build/lib/init.d.ts.map +1 -0
  227. package/build/lib/init.js +295 -0
  228. package/build/lib/init.js.map +1 -0
  229. package/build/lib/installer.d.ts +50 -0
  230. package/build/lib/installer.d.ts.map +1 -0
  231. package/build/lib/installer.js +435 -0
  232. package/build/lib/installer.js.map +1 -0
  233. package/build/lib/lockfile.d.ts +170 -0
  234. package/build/lib/lockfile.d.ts.map +1 -0
  235. package/build/lib/lockfile.js +437 -0
  236. package/build/lib/lockfile.js.map +1 -0
  237. package/build/lib/markdown.d.ts +9 -0
  238. package/build/lib/markdown.d.ts.map +1 -0
  239. package/build/lib/markdown.js +67 -0
  240. package/build/lib/markdown.js.map +1 -0
  241. package/build/lib/parse-json.d.ts +34 -0
  242. package/build/lib/parse-json.d.ts.map +1 -0
  243. package/build/lib/parse-json.js +52 -0
  244. package/build/lib/parse-json.js.map +1 -0
  245. package/build/lib/paths.d.ts +28 -0
  246. package/build/lib/paths.d.ts.map +1 -0
  247. package/build/lib/paths.js +68 -0
  248. package/build/lib/paths.js.map +1 -0
  249. package/build/lib/publisher.d.ts +49 -0
  250. package/build/lib/publisher.d.ts.map +1 -0
  251. package/build/lib/publisher.js +388 -0
  252. package/build/lib/publisher.js.map +1 -0
  253. package/build/lib/registry.d.ts +97 -0
  254. package/build/lib/registry.d.ts.map +1 -0
  255. package/build/lib/registry.js +248 -0
  256. package/build/lib/registry.js.map +1 -0
  257. package/build/lib/resolver.d.ts +50 -0
  258. package/build/lib/resolver.d.ts.map +1 -0
  259. package/build/lib/resolver.js +110 -0
  260. package/build/lib/resolver.js.map +1 -0
  261. package/build/lib/schemas/adapter.d.ts +33 -0
  262. package/build/lib/schemas/adapter.d.ts.map +1 -0
  263. package/build/lib/schemas/adapter.js +27 -0
  264. package/build/lib/schemas/adapter.js.map +1 -0
  265. package/build/lib/schemas/bundle.d.ts +38 -0
  266. package/build/lib/schemas/bundle.d.ts.map +1 -0
  267. package/build/lib/schemas/bundle.js +19 -0
  268. package/build/lib/schemas/bundle.js.map +1 -0
  269. package/build/lib/schemas/config.d.ts +12 -0
  270. package/build/lib/schemas/config.d.ts.map +1 -0
  271. package/build/lib/schemas/config.js +14 -0
  272. package/build/lib/schemas/config.js.map +1 -0
  273. package/build/lib/schemas/index.d.ts +7 -0
  274. package/build/lib/schemas/index.d.ts.map +1 -0
  275. package/build/lib/schemas/index.js +7 -0
  276. package/build/lib/schemas/index.js.map +1 -0
  277. package/build/lib/schemas/lockfile.d.ts +102 -0
  278. package/build/lib/schemas/lockfile.d.ts.map +1 -0
  279. package/build/lib/schemas/lockfile.js +50 -0
  280. package/build/lib/schemas/lockfile.js.map +1 -0
  281. package/build/lib/schemas/manifest.d.ts +91 -0
  282. package/build/lib/schemas/manifest.d.ts.map +1 -0
  283. package/build/lib/schemas/manifest.js +42 -0
  284. package/build/lib/schemas/manifest.js.map +1 -0
  285. package/build/lib/schemas/registry.d.ts +225 -0
  286. package/build/lib/schemas/registry.d.ts.map +1 -0
  287. package/build/lib/schemas/registry.js +59 -0
  288. package/build/lib/schemas/registry.js.map +1 -0
  289. package/build/lib/search.d.ts +23 -0
  290. package/build/lib/search.d.ts.map +1 -0
  291. package/build/lib/search.js +139 -0
  292. package/build/lib/search.js.map +1 -0
  293. package/build/lib/tool-resolver.d.ts +12 -0
  294. package/build/lib/tool-resolver.d.ts.map +1 -0
  295. package/build/lib/tool-resolver.js +24 -0
  296. package/build/lib/tool-resolver.js.map +1 -0
  297. package/build/lib/uninstaller.d.ts +109 -0
  298. package/build/lib/uninstaller.d.ts.map +1 -0
  299. package/build/lib/uninstaller.js +368 -0
  300. package/build/lib/uninstaller.js.map +1 -0
  301. package/build/lib/updater.d.ts +68 -0
  302. package/build/lib/updater.d.ts.map +1 -0
  303. package/build/lib/updater.js +230 -0
  304. package/build/lib/updater.js.map +1 -0
  305. package/build/lib/version.d.ts +13 -0
  306. package/build/lib/version.d.ts.map +1 -0
  307. package/build/lib/version.js +30 -0
  308. package/build/lib/version.js.map +1 -0
  309. package/build/windows-esm-loader.d.ts +15 -0
  310. package/build/windows-esm-loader.d.ts.map +1 -0
  311. package/build/windows-esm-loader.js +11 -0
  312. package/build/windows-esm-loader.js.map +1 -0
  313. package/package.json +67 -0
  314. package/tool-adapters/claude-code.json +34 -0
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Search the registry for assets and bundles matching a query.
3
+ * Returns results sorted by relevance score (highest first).
4
+ */
5
+ export function searchRegistry(registry, options) {
6
+ const { query, tag, tool, type } = options;
7
+ const queryLower = query.toLowerCase();
8
+ const queryTerms = queryLower.split(/\s+/).filter(Boolean);
9
+ const results = [];
10
+ // Search assets (skip when filtering to bundles only)
11
+ if (type !== 'bundle') {
12
+ for (const asset of registry.assets) {
13
+ if (type && asset.type !== type)
14
+ continue;
15
+ if (tag && !asset.tags.some((t) => t.toLowerCase() === tag.toLowerCase()))
16
+ continue;
17
+ const latestVersion = asset.versions[asset.latest];
18
+ if (tool && !(latestVersion?.tools.includes(tool) ?? false))
19
+ continue;
20
+ const score = scoreAsset(asset, queryTerms, queryLower);
21
+ if (score > 0) {
22
+ results.push({ item: asset, kind: 'asset', score });
23
+ }
24
+ }
25
+ }
26
+ // Search bundles (skip when filtering to a specific asset type, or when tool filter is active)
27
+ if (type === undefined || type === 'bundle') {
28
+ if (!tool) {
29
+ for (const bundle of registry.bundles ?? []) {
30
+ if (tag && !bundle.tags.some((t) => t.toLowerCase() === tag.toLowerCase()))
31
+ continue;
32
+ const score = scoreBundle(bundle, queryTerms, queryLower);
33
+ if (score > 0) {
34
+ results.push({ item: bundle, kind: 'bundle', score });
35
+ }
36
+ }
37
+ }
38
+ }
39
+ // Sort by score descending, then by name alphabetically for ties
40
+ results.sort((a, b) => {
41
+ if (b.score !== a.score)
42
+ return b.score - a.score;
43
+ return a.item.name.localeCompare(b.item.name);
44
+ });
45
+ return results;
46
+ }
47
+ /**
48
+ * Score an asset against search terms.
49
+ * Higher score = better match.
50
+ */
51
+ function scoreAsset(asset, terms, fullQuery) {
52
+ let score = 0;
53
+ const nameLower = asset.name.toLowerCase();
54
+ const latestVersion = asset.versions[asset.latest];
55
+ const descLower = (latestVersion?.description ?? '').toLowerCase();
56
+ const authorLower = (latestVersion?.author ?? '').toLowerCase();
57
+ // Exact name match — highest weight
58
+ if (nameLower === fullQuery) {
59
+ score += 100;
60
+ }
61
+ // Name contains the full query
62
+ else if (nameLower.includes(fullQuery)) {
63
+ score += 50;
64
+ }
65
+ // Description contains the full query
66
+ if (descLower.includes(fullQuery)) {
67
+ score += 20;
68
+ }
69
+ // Per-term matching
70
+ for (const term of terms) {
71
+ // Name contains term
72
+ if (nameLower.includes(term)) {
73
+ score += 10;
74
+ }
75
+ // Description contains term
76
+ if (descLower.includes(term)) {
77
+ score += 5;
78
+ }
79
+ // Tag exact match
80
+ if (asset.tags.some((t) => t.toLowerCase() === term)) {
81
+ score += 8;
82
+ }
83
+ // Author matches
84
+ if (authorLower.includes(term)) {
85
+ score += 3;
86
+ }
87
+ }
88
+ // Trust level bonus — only applied when the asset already has relevance
89
+ if (score > 0) {
90
+ const trustLevel = latestVersion?.trustLevel;
91
+ if (trustLevel === 'vetted') {
92
+ score += 2;
93
+ }
94
+ else if (trustLevel === 'community') {
95
+ score += 1;
96
+ }
97
+ }
98
+ return score;
99
+ }
100
+ /**
101
+ * Score a bundle against search terms.
102
+ * Mirrors scoreAsset() but operates on flat RegistryBundle fields.
103
+ * No trust level bonus (bundles have no trust level).
104
+ */
105
+ function scoreBundle(bundle, terms, fullQuery) {
106
+ let score = 0;
107
+ const nameLower = bundle.name.toLowerCase();
108
+ const descLower = bundle.description.toLowerCase();
109
+ const authorLower = bundle.author.toLowerCase();
110
+ // Exact name match — highest weight
111
+ if (nameLower === fullQuery) {
112
+ score += 100;
113
+ }
114
+ // Name contains the full query
115
+ else if (nameLower.includes(fullQuery)) {
116
+ score += 50;
117
+ }
118
+ // Description contains the full query
119
+ if (descLower.includes(fullQuery)) {
120
+ score += 20;
121
+ }
122
+ // Per-term matching
123
+ for (const term of terms) {
124
+ if (nameLower.includes(term)) {
125
+ score += 10;
126
+ }
127
+ if (descLower.includes(term)) {
128
+ score += 5;
129
+ }
130
+ if (bundle.tags.some((t) => t.toLowerCase() === term)) {
131
+ score += 8;
132
+ }
133
+ if (authorLower.includes(term)) {
134
+ score += 3;
135
+ }
136
+ }
137
+ return score;
138
+ }
139
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/lib/search.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { Config } from './schemas/config.js';
2
+ /**
3
+ * Resolve which tool to target, returning both the resolved tool name and the loaded config.
4
+ *
5
+ * Priority: explicit `--tool` option > config `defaultTool` > auto-detect from project directory.
6
+ * Throws if no tool can be determined (caught automatically by `useCommand`).
7
+ */
8
+ export declare function resolveTool(toolOption: string | undefined, projectDir?: string): Promise<{
9
+ config: Config;
10
+ tool: string;
11
+ }>;
12
+ //# sourceMappingURL=tool-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-resolver.d.ts","sourceRoot":"","sources":["../../src/lib/tool-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAMlD;;;;;GAKG;AACH,wBAAsB,WAAW,CAC/B,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAe3C"}
@@ -0,0 +1,24 @@
1
+ import { loadConfig } from './config.js';
2
+ import { detectPrimaryTool } from './detector.js';
3
+ import { findProjectRoot } from './paths.js';
4
+ /**
5
+ * Resolve which tool to target, returning both the resolved tool name and the loaded config.
6
+ *
7
+ * Priority: explicit `--tool` option > config `defaultTool` > auto-detect from project directory.
8
+ * Throws if no tool can be determined (caught automatically by `useCommand`).
9
+ */
10
+ export async function resolveTool(toolOption, projectDir) {
11
+ const config = await loadConfig();
12
+ let tool = toolOption ?? config.defaultTool;
13
+ if (!toolOption && !config.defaultTool) {
14
+ const detected = await detectPrimaryTool(projectDir ?? findProjectRoot());
15
+ if (detected) {
16
+ tool = detected.tool;
17
+ }
18
+ else {
19
+ throw new Error('Could not detect tool. Use --tool to specify one.');
20
+ }
21
+ }
22
+ return { config, tool: tool ?? 'claude-code' };
23
+ }
24
+ //# sourceMappingURL=tool-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-resolver.js","sourceRoot":"","sources":["../../src/lib/tool-resolver.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,UAA8B,EAC9B,UAAmB;IAEnB,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;IAElC,IAAI,IAAI,GAAG,UAAU,IAAI,MAAM,CAAC,WAAW,CAAC;IAE5C,IAAI,CAAC,UAAU,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QACvC,MAAM,QAAQ,GAAG,MAAM,iBAAiB,CAAC,UAAU,IAAI,eAAe,EAAE,CAAC,CAAC;QAC1E,IAAI,QAAQ,EAAE,CAAC;YACb,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC;QACvB,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;QACvE,CAAC;IACH,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,IAAI,aAAa,EAAE,CAAC;AACjD,CAAC"}
@@ -0,0 +1,109 @@
1
+ import type { InstalledAsset, Lockfile } from './schemas/lockfile.js';
2
+ export interface BundleUninstallPlan {
3
+ /** Assets from the bundle to remove (in dependency order — dependents first). */
4
+ assetsToRemove: InstalledAsset[];
5
+ /** Assets skipped because non-bundle assets depend on them. */
6
+ assetsToSkip: Array<InstalledAsset & {
7
+ reason: string;
8
+ }>;
9
+ bundleName: string;
10
+ bundleVersion: string;
11
+ /** Transitive dependencies that would become orphaned. */
12
+ orphansToRemove: InstalledAsset[];
13
+ }
14
+ export interface BundleUninstallResult {
15
+ errors: Array<{
16
+ asset: string;
17
+ error: string;
18
+ }>;
19
+ removedAssets: Array<{
20
+ name: string;
21
+ type: string;
22
+ version: string;
23
+ }>;
24
+ removedOrphans: Array<{
25
+ name: string;
26
+ type: string;
27
+ version: string;
28
+ }>;
29
+ skippedAssets: Array<{
30
+ name: string;
31
+ reason: string;
32
+ type: string;
33
+ version: string;
34
+ }>;
35
+ }
36
+ export interface UninstallOptions {
37
+ /** Whether to cascade-remove orphaned dependencies. Default: true. */
38
+ cascade?: boolean;
39
+ /** Pre-computed lockfile to avoid re-reading from disk. */
40
+ lockfile?: Lockfile;
41
+ /** Callback for progress updates. */
42
+ onProgress?: (message: string) => void;
43
+ /** Pre-computed uninstall plan to avoid re-computing. */
44
+ plan?: UninstallPlan;
45
+ /** Project root directory. */
46
+ projectDir: string;
47
+ /** Tool identifier (e.g., claude-code). */
48
+ tool: string;
49
+ }
50
+ export interface UninstallPlan {
51
+ /** Other installed assets that depend on the asset being removed. */
52
+ dependents: InstalledAsset[];
53
+ /** Dependencies that would become orphaned after removal. */
54
+ orphanedDependencies: InstalledAsset[];
55
+ /** The primary asset being uninstalled. */
56
+ primary: InstalledAsset;
57
+ }
58
+ export interface UninstallResult {
59
+ configEntriesCleaned: Array<{
60
+ file: string;
61
+ key: string;
62
+ }>;
63
+ filesRemoved: string[];
64
+ name: string;
65
+ /** Whether this was the primary uninstall target or a cascade-removed orphan. */
66
+ reason: 'direct' | 'orphan';
67
+ type: string;
68
+ version: string;
69
+ }
70
+ export declare class UninstallerError extends Error {
71
+ constructor(message: string);
72
+ }
73
+ /**
74
+ * Execute a bundle uninstall plan, removing assets and orphaned dependencies.
75
+ * Errors are collected per-asset rather than aborting the entire operation.
76
+ */
77
+ export declare function executeBundleUninstall(plan: BundleUninstallPlan, options: UninstallOptions): Promise<BundleUninstallResult>;
78
+ /**
79
+ * Compute a bundle uninstall plan.
80
+ * Identifies which bundle assets can be removed, which must be skipped (because
81
+ * non-bundle assets depend on them), and which transitive dependencies would
82
+ * become orphaned.
83
+ */
84
+ export declare function planBundleUninstall(lockfile: Lockfile, bundleName: string, bundleVersion: string): BundleUninstallPlan;
85
+ /**
86
+ * Compute the uninstall plan before removing anything.
87
+ * Identifies dependents (for warnings) and orphaned dependencies (for cascade removal).
88
+ * Handles transitive orphans via BFS.
89
+ */
90
+ export declare function planUninstall(lockfile: Lockfile, assetName: string): UninstallPlan;
91
+ /**
92
+ * Remove a single asset's files and config entries from disk.
93
+ * Does NOT touch the lockfile — caller is responsible for lockfile updates.
94
+ *
95
+ * Lockfile paths are relative to the project root; this function resolves them
96
+ * to absolute paths via `projectDir` before performing any filesystem operations.
97
+ */
98
+ export declare function removeSingleAsset(installed: InstalledAsset, projectDir: string, onProgress?: (message: string) => void): Promise<{
99
+ configEntriesCleaned: Array<{
100
+ file: string;
101
+ key: string;
102
+ }>;
103
+ filesRemoved: string[];
104
+ }>;
105
+ /**
106
+ * Uninstall a single asset by name, with optional cascade removal of orphaned dependencies.
107
+ */
108
+ export declare function uninstallAsset(assetName: string, options: UninstallOptions): Promise<UninstallResult[]>;
109
+ //# sourceMappingURL=uninstaller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uninstaller.d.ts","sourceRoot":"","sources":["../../src/lib/uninstaller.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,368 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { readdir, readFile, rmdir, unlink, writeFile } from 'node:fs/promises';
3
+ import { dirname, join } from 'node:path';
4
+ import { findDependents, findInstalledAsset, findOrphanedDependencies, readLockfile, removeAssetFromLockfile, withLockfileLock, writeLockfile, } from './lockfile.js';
5
+ export class UninstallerError extends Error {
6
+ constructor(message) {
7
+ super(message);
8
+ this.name = 'UninstallerError';
9
+ }
10
+ }
11
+ /**
12
+ * Execute a bundle uninstall plan, removing assets and orphaned dependencies.
13
+ * Errors are collected per-asset rather than aborting the entire operation.
14
+ */
15
+ export async function executeBundleUninstall(plan, options) {
16
+ const { onProgress, projectDir, tool } = options;
17
+ return withLockfileLock(projectDir, async () => {
18
+ let lockfile = await readLockfile(projectDir, tool);
19
+ const removedAssets = [];
20
+ const removedOrphans = [];
21
+ const skippedAssets = plan.assetsToSkip.map((a) => ({
22
+ name: a.name,
23
+ reason: a.reason,
24
+ type: a.type,
25
+ version: a.version,
26
+ }));
27
+ const errors = [];
28
+ // Remove bundle assets in dependency order (dependents first)
29
+ for (const asset of plan.assetsToRemove) {
30
+ try {
31
+ onProgress?.(`Removing ${asset.name}...`);
32
+ await removeSingleAsset(asset, projectDir, onProgress);
33
+ lockfile = removeAssetFromLockfile(lockfile, asset.name, asset.type);
34
+ removedAssets.push({ name: asset.name, type: asset.type, version: asset.version });
35
+ }
36
+ catch (err) {
37
+ errors.push({
38
+ asset: asset.name,
39
+ error: err instanceof Error ? err.message : String(err),
40
+ });
41
+ }
42
+ }
43
+ // Remove orphaned transitive dependencies
44
+ for (const orphan of plan.orphansToRemove) {
45
+ try {
46
+ onProgress?.(`Removing orphaned dependency ${orphan.name}...`);
47
+ await removeSingleAsset(orphan, projectDir, onProgress);
48
+ lockfile = removeAssetFromLockfile(lockfile, orphan.name, orphan.type);
49
+ removedOrphans.push({ name: orphan.name, type: orphan.type, version: orphan.version });
50
+ }
51
+ catch (err) {
52
+ errors.push({
53
+ asset: orphan.name,
54
+ error: err instanceof Error ? err.message : String(err),
55
+ });
56
+ }
57
+ }
58
+ // Write lockfile once at the end
59
+ onProgress?.(`Updating lockfile...`);
60
+ await writeLockfile(projectDir, lockfile);
61
+ return { errors, removedAssets, removedOrphans, skippedAssets };
62
+ });
63
+ }
64
+ /**
65
+ * Compute a bundle uninstall plan.
66
+ * Identifies which bundle assets can be removed, which must be skipped (because
67
+ * non-bundle assets depend on them), and which transitive dependencies would
68
+ * become orphaned.
69
+ */
70
+ export function planBundleUninstall(lockfile, bundleName, bundleVersion) {
71
+ const emptyPlan = {
72
+ assetsToRemove: [],
73
+ assetsToSkip: [],
74
+ bundleName,
75
+ bundleVersion,
76
+ orphansToRemove: [],
77
+ };
78
+ // Find all assets belonging to this bundle
79
+ const bundleAssets = (lockfile.assets ?? []).filter((a) => a.bundleOrigin?.name === bundleName);
80
+ if (bundleAssets.length === 0) {
81
+ return emptyPlan;
82
+ }
83
+ const bundleAssetNames = new Set(bundleAssets.map((a) => a.name));
84
+ const assetsToRemove = [];
85
+ const assetsToSkip = [];
86
+ // Determine which assets can be removed and which must be skipped
87
+ for (const asset of bundleAssets) {
88
+ const dependents = findDependents(lockfile, asset.name, asset.type);
89
+ // A non-bundle dependent is one with a different bundleOrigin or no bundleOrigin at all
90
+ const nonBundleDependents = dependents.filter((d) => d.bundleOrigin?.name !== bundleName);
91
+ if (nonBundleDependents.length > 0) {
92
+ const dependentNames = nonBundleDependents.map((d) => d.name);
93
+ assetsToSkip.push({
94
+ ...asset,
95
+ reason: `Required by ${dependentNames.join(', ')}`,
96
+ });
97
+ }
98
+ else {
99
+ assetsToRemove.push(asset);
100
+ }
101
+ }
102
+ // Order removable assets so dependents are removed before their dependencies
103
+ const ordered = topologicalSortForRemoval(assetsToRemove);
104
+ // Compute orphaned transitive dependencies
105
+ const namesBeingRemoved = new Set(ordered.map((a) => a.name));
106
+ const orphanSet = new Map();
107
+ for (const asset of ordered) {
108
+ const orphans = findOrphanedDependencies(lockfile, asset, [...namesBeingRemoved, ...orphanSet.keys()]);
109
+ for (const orphan of orphans) {
110
+ // Only include orphans that are NOT themselves bundle assets
111
+ if (!bundleAssetNames.has(orphan.name) && !orphanSet.has(orphan.name)) {
112
+ orphanSet.set(orphan.name, orphan);
113
+ }
114
+ }
115
+ }
116
+ return {
117
+ assetsToRemove: ordered,
118
+ assetsToSkip,
119
+ bundleName,
120
+ bundleVersion,
121
+ orphansToRemove: [...orphanSet.values()],
122
+ };
123
+ }
124
+ /**
125
+ * Compute the uninstall plan before removing anything.
126
+ * Identifies dependents (for warnings) and orphaned dependencies (for cascade removal).
127
+ * Handles transitive orphans via BFS.
128
+ */
129
+ export function planUninstall(lockfile, assetName) {
130
+ const primary = findInstalledAsset(lockfile, assetName);
131
+ if (!primary) {
132
+ throw new UninstallerError(`Asset "${assetName}" is not installed.`);
133
+ }
134
+ const dependents = findDependents(lockfile, assetName, primary.type);
135
+ // Collect orphaned dependencies using BFS for transitive orphans
136
+ const allOrphans = [];
137
+ const visited = new Set();
138
+ const queue = [primary];
139
+ const allBeingRemoved = [assetName];
140
+ visited.add(primary.name);
141
+ while (queue.length > 0) {
142
+ const current = queue.shift();
143
+ const orphans = findOrphanedDependencies(lockfile, current, allBeingRemoved);
144
+ for (const orphan of orphans) {
145
+ if (!visited.has(orphan.name)) {
146
+ visited.add(orphan.name);
147
+ allOrphans.push(orphan);
148
+ allBeingRemoved.push(orphan.name);
149
+ queue.push(orphan);
150
+ }
151
+ }
152
+ }
153
+ return {
154
+ dependents,
155
+ orphanedDependencies: allOrphans,
156
+ primary,
157
+ };
158
+ }
159
+ /**
160
+ * Remove a single asset's files and config entries from disk.
161
+ * Does NOT touch the lockfile — caller is responsible for lockfile updates.
162
+ *
163
+ * Lockfile paths are relative to the project root; this function resolves them
164
+ * to absolute paths via `projectDir` before performing any filesystem operations.
165
+ */
166
+ export async function removeSingleAsset(installed, projectDir, onProgress) {
167
+ onProgress?.(`Removing files for ${installed.name}...`);
168
+ // Delete placed files (resolve relative lockfile paths to absolute)
169
+ const filesRemoved = [];
170
+ for (const file of installed.files) {
171
+ const absolutePath = join(projectDir, file.installedPath);
172
+ if (existsSync(absolutePath)) {
173
+ await unlink(absolutePath);
174
+ filesRemoved.push(absolutePath);
175
+ // Clean up empty parent directories
176
+ await cleanupEmptyDirectories(dirname(absolutePath), onProgress);
177
+ }
178
+ }
179
+ // Clean up config entries (resolve relative lockfile paths to absolute)
180
+ const configEntriesCleaned = [];
181
+ for (const entry of installed.configEntries ?? []) {
182
+ const absoluteConfigPath = join(projectDir, entry.file);
183
+ // Handle memory-template section removal
184
+ if (entry.key.startsWith('memory-template.')) {
185
+ const removed = await removeMemoryTemplateSection(absoluteConfigPath, installed.name, onProgress);
186
+ if (removed) {
187
+ configEntriesCleaned.push(entry);
188
+ }
189
+ }
190
+ else {
191
+ const cleaned = await removeConfigEntry(absoluteConfigPath, entry.key, installed.name, onProgress);
192
+ if (cleaned) {
193
+ configEntriesCleaned.push(entry);
194
+ }
195
+ }
196
+ }
197
+ return { configEntriesCleaned, filesRemoved };
198
+ }
199
+ /**
200
+ * Uninstall a single asset by name, with optional cascade removal of orphaned dependencies.
201
+ */
202
+ export async function uninstallAsset(assetName, options) {
203
+ const { cascade = true, lockfile: precomputedLockfile, onProgress, plan: precomputedPlan, projectDir, tool, } = options;
204
+ onProgress?.(`Reading lockfile...`);
205
+ let lockfile = precomputedLockfile ?? (await readLockfile(projectDir, tool));
206
+ // Compute the plan before removing anything
207
+ const plan = precomputedPlan ?? planUninstall(lockfile, assetName);
208
+ // Remove primary asset
209
+ const primaryRemoval = await removeSingleAsset(plan.primary, projectDir, onProgress);
210
+ lockfile = removeAssetFromLockfile(lockfile, assetName);
211
+ const results = [
212
+ {
213
+ ...primaryRemoval,
214
+ name: plan.primary.name,
215
+ reason: 'direct',
216
+ type: plan.primary.type,
217
+ version: plan.primary.version,
218
+ },
219
+ ];
220
+ // Cascade-remove orphaned dependencies
221
+ if (cascade && plan.orphanedDependencies.length > 0) {
222
+ for (const orphan of plan.orphanedDependencies) {
223
+ onProgress?.(`Removing orphaned dependency ${orphan.name}...`);
224
+ const orphanRemoval = await removeSingleAsset(orphan, projectDir, onProgress);
225
+ lockfile = removeAssetFromLockfile(lockfile, orphan.name, orphan.type);
226
+ results.push({
227
+ ...orphanRemoval,
228
+ name: orphan.name,
229
+ reason: 'orphan',
230
+ type: orphan.type,
231
+ version: orphan.version,
232
+ });
233
+ }
234
+ }
235
+ // Write lockfile once at the end
236
+ onProgress?.(`Updating lockfile...`);
237
+ await writeLockfile(projectDir, lockfile);
238
+ onProgress?.(`Uninstalled ${assetName}`);
239
+ return results;
240
+ }
241
+ /**
242
+ * Remove empty parent directories up the tree.
243
+ * Stops when it encounters a non-empty directory or the .claude directory itself.
244
+ */
245
+ async function cleanupEmptyDirectories(dirPath, onProgress) {
246
+ try {
247
+ // Don't remove well-known root directories
248
+ const dirName = dirPath.replace(/\\/g, '/');
249
+ if (dirName.endsWith('/.claude') ||
250
+ dirName.endsWith('/.claude/agents') ||
251
+ dirName.endsWith('/.claude/hooks') ||
252
+ dirName.endsWith('/.claude/rules') ||
253
+ dirName.endsWith('/.claude/skills')) {
254
+ return;
255
+ }
256
+ const entries = await readdir(dirPath);
257
+ if (entries.length === 0) {
258
+ await rmdir(dirPath);
259
+ // Recurse up to clean parent if also empty
260
+ await cleanupEmptyDirectories(dirname(dirPath), onProgress);
261
+ }
262
+ }
263
+ catch (error) {
264
+ // Directory doesn't exist or can't be read — log for debugging
265
+ onProgress?.(`Warning: Failed to clean up directory ${dirPath} — ${error instanceof Error ? error.message : String(error)}`);
266
+ }
267
+ }
268
+ /**
269
+ * Remove an asset's entry from a config file (e.g., settings.json).
270
+ * Reads the JSON config, removes the key associated with the asset, and writes back.
271
+ */
272
+ async function removeConfigEntry(configFilePath, configKeyPath, assetName, onProgress) {
273
+ try {
274
+ if (!existsSync(configFilePath)) {
275
+ return false;
276
+ }
277
+ const raw = await readFile(configFilePath, 'utf-8');
278
+ const config = JSON.parse(raw);
279
+ // configKeyPath is in the form "section.assetName"
280
+ // We need to find the section and remove the asset key from it
281
+ const parts = configKeyPath.split('.');
282
+ const sectionKey = parts[0];
283
+ const section = config[sectionKey];
284
+ if (!section || typeof section !== 'object') {
285
+ return false;
286
+ }
287
+ const sectionObj = section;
288
+ if (!(assetName in sectionObj)) {
289
+ return false;
290
+ }
291
+ delete sectionObj[assetName];
292
+ // If the section is now empty, remove it too
293
+ if (Object.keys(sectionObj).length === 0) {
294
+ delete config[sectionKey];
295
+ }
296
+ await writeFile(configFilePath, JSON.stringify(config, null, '\t') + '\n', 'utf-8');
297
+ return true;
298
+ }
299
+ catch (error) {
300
+ // Non-fatal — log but don't fail the uninstall
301
+ onProgress?.(`Warning: Failed to remove config entry from ${configFilePath} — ${error instanceof Error ? error.message : String(error)}`);
302
+ return false;
303
+ }
304
+ }
305
+ /**
306
+ * Remove a memory-template section from a shared file (e.g., CLAUDE.md).
307
+ * Sections are delimited by <!-- ATK: {name} --> ... <!-- /ATK: {name} -->.
308
+ */
309
+ async function removeMemoryTemplateSection(filePath, assetName, onProgress) {
310
+ try {
311
+ if (!existsSync(filePath)) {
312
+ return false;
313
+ }
314
+ const content = await readFile(filePath, 'utf-8');
315
+ const startTag = `<!-- ATK: ${assetName} -->`;
316
+ const endTag = `<!-- /ATK: ${assetName} -->`;
317
+ const startIdx = content.indexOf(startTag);
318
+ const endIdx = content.indexOf(endTag);
319
+ if (startIdx === -1 || endIdx === -1) {
320
+ return false;
321
+ }
322
+ const before = content.slice(0, startIdx);
323
+ const after = content.slice(endIdx + endTag.length);
324
+ // Clean up extra whitespace between sections
325
+ const cleaned = (before.trimEnd() + after.trimStart()).trim();
326
+ if (cleaned.length === 0) {
327
+ // File is empty after removal — delete it
328
+ await unlink(filePath);
329
+ }
330
+ else {
331
+ await writeFile(filePath, cleaned + '\n', 'utf-8');
332
+ }
333
+ return true;
334
+ }
335
+ catch (error) {
336
+ onProgress?.(`Warning: Failed to remove memory-template section from ${filePath} — ${error instanceof Error ? error.message : String(error)}`);
337
+ return false;
338
+ }
339
+ }
340
+ /**
341
+ * Sort assets so that dependents are removed before their dependencies.
342
+ * Uses a topological sort — if asset A depends on asset B, A appears before B
343
+ * in the result (reverse dependency order).
344
+ */
345
+ function topologicalSortForRemoval(assets) {
346
+ const nameSet = new Set(assets.map((a) => a.name));
347
+ const visited = new Set();
348
+ const result = [];
349
+ function visit(asset) {
350
+ if (visited.has(asset.name))
351
+ return;
352
+ visited.add(asset.name);
353
+ // Visit all assets that depend on this one first (they must be removed first)
354
+ for (const other of assets) {
355
+ if (other.name !== asset.name &&
356
+ (other.dependencies ?? []).some((d) => d.name === asset.name) &&
357
+ nameSet.has(other.name)) {
358
+ visit(other);
359
+ }
360
+ }
361
+ result.push(asset);
362
+ }
363
+ for (const asset of assets) {
364
+ visit(asset);
365
+ }
366
+ return result;
367
+ }
368
+ //# sourceMappingURL=uninstaller.js.map