claudeup 4.18.0 → 4.22.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 (191) hide show
  1. package/bin/claudeup.js +43 -19
  2. package/package.json +14 -19
  3. package/scripts/build-binaries.ts +77 -0
  4. package/scripts/check-optional-deps.ts +49 -0
  5. package/scripts/test-isolated.ts +43 -0
  6. package/src/__tests__/alias-adopt.test.ts +364 -0
  7. package/src/__tests__/alias-parser.test.ts +92 -0
  8. package/src/__tests__/alias-shell-writer.test.ts +7 -0
  9. package/src/__tests__/alias-store.test.ts +77 -0
  10. package/src/__tests__/cli-router.test.ts +65 -0
  11. package/src/__tests__/conventions-integration.test.ts +745 -0
  12. package/src/__tests__/conventions-manager.test.ts +1172 -0
  13. package/src/__tests__/doctor-bins.test.ts +35 -0
  14. package/src/__tests__/doctor.test.ts +450 -0
  15. package/src/__tests__/dual-write-prevention.test.ts +14 -4
  16. package/src/__tests__/file-locking.test.ts +208 -0
  17. package/src/__tests__/gitignore-prerun.test.ts +1 -0
  18. package/src/__tests__/install-command.test.ts +81 -0
  19. package/src/__tests__/install-plan.test.ts +98 -0
  20. package/src/__tests__/manifest.test.ts +136 -0
  21. package/src/__tests__/marketplace-refresh.test.ts +267 -0
  22. package/src/__tests__/plugin-requires.test.ts +133 -0
  23. package/src/__tests__/plugin-setup.test.ts +118 -0
  24. package/src/__tests__/profile-command.test.ts +116 -0
  25. package/src/__tests__/profile-materializer.test.ts +82 -0
  26. package/src/__tests__/profile-sync.test.ts +136 -0
  27. package/src/__tests__/registry-version-resolution.test.ts +74 -0
  28. package/src/__tests__/resolve-executable.test.ts +42 -0
  29. package/src/__tests__/resolver.test.ts +218 -0
  30. package/src/__tests__/symlink-manager.test.ts +99 -0
  31. package/src/__tests__/toolchain.test.ts +56 -0
  32. package/src/cli/claude.ts +21 -0
  33. package/src/cli/doctor.ts +132 -0
  34. package/src/cli/install.ts +360 -0
  35. package/src/cli/profile.ts +166 -0
  36. package/src/cli/router.ts +107 -0
  37. package/src/cli/update.ts +85 -0
  38. package/src/data/alias-flags.ts +11 -1
  39. package/src/data/cli-tools.ts +7 -7
  40. package/src/data/gitignore-defaults.ts +4 -0
  41. package/src/data/marketplaces.ts +12 -0
  42. package/src/data/predefined-profiles.ts +3 -4
  43. package/src/main.tsx +11 -154
  44. package/src/prerunner/index.ts +62 -27
  45. package/src/services/alias-shell-writer.ts +382 -8
  46. package/src/services/alias-store.ts +60 -0
  47. package/src/services/claude-cli.ts +19 -22
  48. package/src/services/claude-settings.ts +99 -26
  49. package/src/services/conventions-manager.ts +865 -0
  50. package/src/services/doctor-bins.ts +49 -0
  51. package/src/services/doctor.ts +509 -0
  52. package/src/services/install-plan.ts +107 -0
  53. package/src/services/manifest.ts +166 -0
  54. package/src/services/marketplace-refresh.ts +173 -0
  55. package/src/services/plugin-manager.ts +7 -1
  56. package/src/services/plugin-requires.ts +215 -0
  57. package/src/services/plugin-setup.ts +61 -4
  58. package/src/services/plugin-version-check.ts +9 -4
  59. package/src/services/profile-materializer.ts +61 -0
  60. package/src/services/profile-sync.ts +150 -0
  61. package/src/services/resolver.ts +293 -0
  62. package/src/services/symlink-manager.ts +146 -0
  63. package/src/services/toolchain.ts +97 -0
  64. package/src/services/version-check.ts +10 -11
  65. package/src/types/index.ts +138 -31
  66. package/src/ui/App.tsx +16 -11
  67. package/src/ui/components/FlagDetailEditor.tsx +0 -0
  68. package/src/ui/components/modals/ConfirmModal.tsx +1 -1
  69. package/src/ui/screens/AliasScreen.tsx +491 -359
  70. package/src/ui/screens/CliToolsScreen.tsx +11 -3
  71. package/src/ui/screens/PluginsScreen.tsx +3 -1
  72. package/src/ui/screens/index.ts +0 -1
  73. package/src/ui/state/reducer.ts +6 -102
  74. package/src/ui/state/types.ts +8 -35
  75. package/src/utils/command-utils.ts +23 -0
  76. package/src/utils/file-locking.ts +144 -0
  77. package/src/data/alias-flags.js +0 -196
  78. package/src/data/cli-tools.js +0 -123
  79. package/src/data/gitignore-defaults.js +0 -24
  80. package/src/data/gitignore-reasons.js +0 -97
  81. package/src/data/gitignore-templates.js +0 -21
  82. package/src/data/marketplaces.js +0 -138
  83. package/src/data/mcp-servers.js +0 -509
  84. package/src/data/predefined-profiles.js +0 -248
  85. package/src/data/settings-catalog.js +0 -625
  86. package/src/data/skill-repos.js +0 -160
  87. package/src/data/statuslines.js +0 -159
  88. package/src/data/statuslines.ts +0 -188
  89. package/src/index.js +0 -4
  90. package/src/index.ts +0 -5
  91. package/src/main.js +0 -170
  92. package/src/prerunner/index.js +0 -252
  93. package/src/services/alias-settings.js +0 -51
  94. package/src/services/alias-shell-writer.js +0 -764
  95. package/src/services/alias-store.js +0 -77
  96. package/src/services/claude-cli.js +0 -189
  97. package/src/services/claude-runner.js +0 -28
  98. package/src/services/claude-settings.js +0 -1223
  99. package/src/services/gitignore-detector.js +0 -155
  100. package/src/services/gitignore-fixer.js +0 -231
  101. package/src/services/gitignore-prerun.js +0 -46
  102. package/src/services/gitignore-resolver.js +0 -143
  103. package/src/services/gitignore-service.js +0 -117
  104. package/src/services/local-marketplace.js +0 -339
  105. package/src/services/marketplace-fetcher.js +0 -96
  106. package/src/services/marketplace-sync.js +0 -94
  107. package/src/services/mcp-registry.js +0 -87
  108. package/src/services/plugin-manager.js +0 -473
  109. package/src/services/plugin-mcp-config.js +0 -177
  110. package/src/services/plugin-setup.js +0 -444
  111. package/src/services/plugin-version-check.js +0 -262
  112. package/src/services/profiles.js +0 -161
  113. package/src/services/settings-manager.js +0 -243
  114. package/src/services/skills-manager.js +0 -393
  115. package/src/services/skillsmp-client.js +0 -87
  116. package/src/services/update-cache.js +0 -52
  117. package/src/services/version-check.js +0 -99
  118. package/src/types/gitignore.js +0 -6
  119. package/src/types/index.js +0 -1
  120. package/src/ui/App.js +0 -346
  121. package/src/ui/adapters/pluginsAdapter.js +0 -139
  122. package/src/ui/adapters/settingsAdapter.js +0 -111
  123. package/src/ui/adapters/skillsAdapter.js +0 -154
  124. package/src/ui/components/CategoryHeader.js +0 -9
  125. package/src/ui/components/EmptyFilterState.js +0 -4
  126. package/src/ui/components/ScopeIndicator.js +0 -30
  127. package/src/ui/components/ScrollableList.js +0 -36
  128. package/src/ui/components/SearchInput.js +0 -19
  129. package/src/ui/components/StyledText.js +0 -39
  130. package/src/ui/components/TabBar.js +0 -19
  131. package/src/ui/components/layout/FooterHints.js +0 -29
  132. package/src/ui/components/layout/Panel.js +0 -6
  133. package/src/ui/components/layout/ProgressBar.js +0 -14
  134. package/src/ui/components/layout/ScopeTabs.js +0 -6
  135. package/src/ui/components/layout/ScreenLayout.js +0 -16
  136. package/src/ui/components/layout/index.js +0 -5
  137. package/src/ui/components/modals/ConfirmModal.js +0 -14
  138. package/src/ui/components/modals/InputModal.js +0 -5
  139. package/src/ui/components/modals/LoadingModal.js +0 -17
  140. package/src/ui/components/modals/MessageModal.js +0 -16
  141. package/src/ui/components/modals/ModalContainer.js +0 -137
  142. package/src/ui/components/modals/SelectModal.js +0 -18
  143. package/src/ui/components/modals/VersionMismatchModal.js +0 -36
  144. package/src/ui/components/modals/index.js +0 -6
  145. package/src/ui/components/primitives/ActionHints.js +0 -13
  146. package/src/ui/components/primitives/DetailSection.js +0 -7
  147. package/src/ui/components/primitives/KeyValueLine.js +0 -8
  148. package/src/ui/components/primitives/ListCategoryRow.js +0 -8
  149. package/src/ui/components/primitives/MetaText.js +0 -8
  150. package/src/ui/components/primitives/ScopeDetail.js +0 -32
  151. package/src/ui/components/primitives/ScopeSquares.js +0 -11
  152. package/src/ui/components/primitives/SelectableRow.js +0 -5
  153. package/src/ui/components/primitives/index.js +0 -8
  154. package/src/ui/hooks/index.js +0 -4
  155. package/src/ui/hooks/useAsyncData.js +0 -77
  156. package/src/ui/hooks/useGitignoreModal.js +0 -74
  157. package/src/ui/hooks/useKeyboard.js +0 -13
  158. package/src/ui/hooks/useKeyboardHandler.js +0 -39
  159. package/src/ui/hooks/useMismatchModal.js +0 -77
  160. package/src/ui/registry.js +0 -1
  161. package/src/ui/renderers/cliToolRenderers.js +0 -54
  162. package/src/ui/renderers/gitignoreRenderers.js +0 -46
  163. package/src/ui/renderers/mcpRenderers.js +0 -26
  164. package/src/ui/renderers/pluginRenderers.js +0 -124
  165. package/src/ui/renderers/profileRenderers.js +0 -177
  166. package/src/ui/renderers/settingsRenderers.js +0 -73
  167. package/src/ui/renderers/skillRenderers.js +0 -138
  168. package/src/ui/screens/AliasScreen.js +0 -1008
  169. package/src/ui/screens/CliToolsScreen.js +0 -338
  170. package/src/ui/screens/EnvVarsScreen.js +0 -152
  171. package/src/ui/screens/GitignoreScreen.js +0 -328
  172. package/src/ui/screens/McpRegistryScreen.js +0 -238
  173. package/src/ui/screens/McpScreen.js +0 -176
  174. package/src/ui/screens/ModelSelectorScreen.js +0 -296
  175. package/src/ui/screens/ModelSelectorScreen.tsx +0 -444
  176. package/src/ui/screens/PluginsScreen.js +0 -766
  177. package/src/ui/screens/ProfilesScreen.js +0 -298
  178. package/src/ui/screens/SkillsScreen.js +0 -549
  179. package/src/ui/screens/StatusLineScreen.js +0 -206
  180. package/src/ui/screens/StatusLineScreen.tsx +0 -416
  181. package/src/ui/screens/index.js +0 -10
  182. package/src/ui/state/AnimationContext.js +0 -34
  183. package/src/ui/state/AppContext.js +0 -162
  184. package/src/ui/state/DimensionsContext.js +0 -71
  185. package/src/ui/state/reducer.js +0 -543
  186. package/src/ui/state/types.js +0 -1
  187. package/src/ui/theme.js +0 -47
  188. package/src/utils/clipboard.js +0 -56
  189. package/src/utils/command-utils.js +0 -19
  190. package/src/utils/fuzzy-search.js +0 -101
  191. package/src/utils/string-utils.js +0 -62
@@ -1,161 +0,0 @@
1
- import fs from "fs-extra";
2
- import path from "node:path";
3
- import os from "node:os";
4
- const PROFILES_FILE = "profiles.json";
5
- // ─── Path helpers ──────────────────────────────────────────────────────────────
6
- export function getUserProfilesPath() {
7
- return path.join(os.homedir(), ".claude", PROFILES_FILE);
8
- }
9
- export function getProjectProfilesPath(projectPath) {
10
- const base = projectPath ?? process.cwd();
11
- return path.join(base, ".claude", PROFILES_FILE);
12
- }
13
- // ─── Low-level read/write ──────────────────────────────────────────────────────
14
- export async function readProfiles(scope, projectPath) {
15
- const filePath = scope === "user"
16
- ? getUserProfilesPath()
17
- : getProjectProfilesPath(projectPath);
18
- try {
19
- if (await fs.pathExists(filePath)) {
20
- const data = await fs.readJson(filePath);
21
- if (data.version && data.profiles)
22
- return data;
23
- }
24
- }
25
- catch {
26
- // fall through to default
27
- }
28
- return { version: 1, profiles: {} };
29
- }
30
- async function writeProfiles(scope, data, projectPath) {
31
- const filePath = scope === "user"
32
- ? getUserProfilesPath()
33
- : getProjectProfilesPath(projectPath);
34
- await fs.ensureDir(path.dirname(filePath));
35
- await fs.writeJson(filePath, data, { spaces: 2 });
36
- }
37
- // ─── CRUD ──────────────────────────────────────────────────────────────────────
38
- /** Return all profiles from both scopes, merged into a flat list */
39
- export async function listProfiles(projectPath) {
40
- const [user, project] = await Promise.all([
41
- readProfiles("user"),
42
- readProfiles("project", projectPath),
43
- ]);
44
- const entries = [];
45
- for (const [id, p] of Object.entries(user.profiles)) {
46
- entries.push({ id, scope: "user", ...p });
47
- }
48
- for (const [id, p] of Object.entries(project.profiles)) {
49
- entries.push({ id, scope: "project", ...p });
50
- }
51
- // Sort: user profiles first, then project, each by updatedAt desc
52
- entries.sort((a, b) => {
53
- if (a.scope !== b.scope)
54
- return a.scope === "user" ? -1 : 1;
55
- return b.updatedAt.localeCompare(a.updatedAt);
56
- });
57
- return entries;
58
- }
59
- /**
60
- * Save (create or overwrite) a profile.
61
- * Returns the generated profile ID.
62
- */
63
- export async function saveProfile(name, plugins, scope, projectPath) {
64
- const data = await readProfiles(scope, projectPath);
65
- const id = generateUniqueId(name, data.profiles);
66
- const now = new Date().toISOString();
67
- data.profiles[id] = {
68
- name,
69
- plugins,
70
- createdAt: now,
71
- updatedAt: now,
72
- };
73
- await writeProfiles(scope, data, projectPath);
74
- return id;
75
- }
76
- /**
77
- * Apply a profile: replaces enabledPlugins in the target settings scope.
78
- * targetScope is where the settings will be written, independent of where
79
- * the profile is stored.
80
- */
81
- export async function applyProfile(profileId, profileScope, targetScope, projectPath) {
82
- const data = await readProfiles(profileScope, projectPath);
83
- const profile = data.profiles[profileId];
84
- if (!profile)
85
- throw new Error(`Profile "${profileId}" not found`);
86
- // Import settings functions dynamically to avoid circular deps
87
- const { readSettings, writeSettings, readGlobalSettings, writeGlobalSettings, } = await import("./claude-settings.js");
88
- if (targetScope === "user") {
89
- const settings = await readGlobalSettings();
90
- settings.enabledPlugins = { ...profile.plugins };
91
- await writeGlobalSettings(settings);
92
- }
93
- else {
94
- // project or local both write to project settings.json
95
- const settings = await readSettings(projectPath);
96
- settings.enabledPlugins = { ...profile.plugins };
97
- await writeSettings(settings, projectPath);
98
- }
99
- }
100
- /** Rename a profile in-place (preserves id, createdAt) */
101
- export async function renameProfile(profileId, newName, scope, projectPath) {
102
- const data = await readProfiles(scope, projectPath);
103
- if (!data.profiles[profileId]) {
104
- throw new Error(`Profile "${profileId}" not found in ${scope} scope`);
105
- }
106
- data.profiles[profileId].name = newName;
107
- data.profiles[profileId].updatedAt = new Date().toISOString();
108
- await writeProfiles(scope, data, projectPath);
109
- }
110
- export async function deleteProfile(profileId, scope, projectPath) {
111
- const data = await readProfiles(scope, projectPath);
112
- if (!data.profiles[profileId]) {
113
- throw new Error(`Profile "${profileId}" not found in ${scope} scope`);
114
- }
115
- delete data.profiles[profileId];
116
- await writeProfiles(scope, data, projectPath);
117
- }
118
- // ─── Import / Export ───────────────────────────────────────────────────────────
119
- /** Serialize a profile to a compact JSON string for clipboard sharing */
120
- export async function exportProfileToJson(profileId, scope, projectPath) {
121
- const data = await readProfiles(scope, projectPath);
122
- const profile = data.profiles[profileId];
123
- if (!profile)
124
- throw new Error(`Profile "${profileId}" not found`);
125
- const entry = { id: profileId, scope, ...profile };
126
- return JSON.stringify(entry, null, 2);
127
- }
128
- /**
129
- * Parse a clipboard JSON string and save into the specified scope.
130
- * Returns the saved profile id.
131
- */
132
- export async function importProfileFromJson(json, targetScope, projectPath) {
133
- let entry;
134
- try {
135
- entry = JSON.parse(json);
136
- }
137
- catch {
138
- throw new Error("Invalid JSON — could not parse clipboard content");
139
- }
140
- if (!entry.name || typeof entry.plugins !== "object") {
141
- throw new Error("Invalid profile format — expected { name, plugins }");
142
- }
143
- return saveProfile(entry.name, entry.plugins, targetScope, projectPath);
144
- }
145
- // ─── Internal helpers ──────────────────────────────────────────────────────────
146
- function slugify(name) {
147
- return name
148
- .toLowerCase()
149
- .trim()
150
- .replace(/[^a-z0-9]+/g, "-")
151
- .replace(/^-|-$/g, "");
152
- }
153
- function generateUniqueId(name, existing) {
154
- const base = slugify(name) || "profile";
155
- if (!existing[base])
156
- return base;
157
- let n = 2;
158
- while (existing[`${base}-${n}`])
159
- n++;
160
- return `${base}-${n}`;
161
- }
@@ -1,243 +0,0 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- import { readSettings, writeSettings, readGlobalSettings, writeGlobalSettings, } from "./claude-settings.js";
4
- /** Read the current value of a setting from the given scope */
5
- export async function readSettingValue(setting, scope, projectPath) {
6
- const settings = scope === "user"
7
- ? await readGlobalSettings()
8
- : await readSettings(projectPath);
9
- if (setting.storage.type === "attribution") {
10
- // Attribution is an object: { commit: "", pr: "" } means disabled
11
- const attr = settings.attribution;
12
- if (attr && attr.commit === "" && attr.pr === "") {
13
- return "false";
14
- }
15
- return undefined; // default (enabled)
16
- }
17
- else if (setting.storage.type === "attribution-text") {
18
- // Custom attribution text: read from attribution.commit, strip the Co-Authored-By trailer prefix
19
- const attr = settings.attribution;
20
- if (!attr || (attr.commit === "" && attr.pr === "")) {
21
- // Attribution is disabled or not set — no custom text stored
22
- return undefined;
23
- }
24
- const commit = attr.commit;
25
- if (!commit) {
26
- return undefined;
27
- }
28
- // The commit value is "Co-Authored-By: Magus <magus@madappgang.com>\n\n{customText}"
29
- // Strip the trailer prefix if present
30
- const trailerPrefix = "Co-Authored-By: Magus <magus@madappgang.com>\n\n";
31
- if (commit.startsWith(trailerPrefix)) {
32
- const text = commit.slice(trailerPrefix.length);
33
- return text.length > 0 ? text : undefined;
34
- }
35
- // If no trailer prefix, the value is the raw text (or Claude default — return undefined)
36
- return undefined;
37
- }
38
- else if (setting.storage.type === "env") {
39
- const env = settings.env;
40
- return env?.[setting.storage.key];
41
- }
42
- else {
43
- // Direct settings key (supports dot notation like "permissions.defaultMode")
44
- const keys = setting.storage.key.split(".");
45
- let value = settings;
46
- for (const k of keys) {
47
- value = value?.[k];
48
- }
49
- return value !== undefined && value !== null ? String(value) : undefined;
50
- }
51
- }
52
- /** Write a setting value to the given scope */
53
- export async function writeSettingValue(setting, value, scope, projectPath) {
54
- const settings = scope === "user"
55
- ? await readGlobalSettings()
56
- : await readSettings(projectPath);
57
- if (setting.storage.type === "attribution") {
58
- // Boolean toggle: "false" -> write { commit: "", pr: "" }, "true"/undefined -> delete key
59
- if (value === "false") {
60
- settings.attribution = { commit: "", pr: "" };
61
- }
62
- else {
63
- delete settings.attribution;
64
- }
65
- }
66
- else if (setting.storage.type === "attribution-text") {
67
- if (value && value.trim().length > 0) {
68
- // Write custom text: commit gets a Co-Authored-By trailer + the text; pr gets the text
69
- settings.attribution = {
70
- commit: `Co-Authored-By: Magus <magus@madappgang.com>\n\n${value}`,
71
- pr: value,
72
- };
73
- }
74
- else {
75
- // Empty value: remove attribution key entirely (revert to Claude defaults)
76
- delete settings.attribution;
77
- }
78
- }
79
- else if (setting.storage.type === "env") {
80
- // Write to the "env" block in settings.json
81
- settings.env = settings.env || {};
82
- if (value === undefined || value === "" || value === setting.defaultValue) {
83
- delete settings.env[setting.storage.key];
84
- // Clean up empty env block
85
- if (Object.keys(settings.env).length === 0) {
86
- delete settings.env;
87
- }
88
- }
89
- else {
90
- settings.env[setting.storage.key] = value;
91
- }
92
- }
93
- else {
94
- // Direct settings key (supports dot notation)
95
- const keys = setting.storage.key.split(".");
96
- if (keys.length === 1) {
97
- if (value === undefined || value === "") {
98
- delete settings[keys[0]];
99
- }
100
- else {
101
- // Try to preserve type: boolean, number, or string
102
- settings[keys[0]] = parseSettingValue(value, setting.type);
103
- }
104
- }
105
- else {
106
- // Nested key like "permissions.defaultMode"
107
- let obj = settings;
108
- for (let i = 0; i < keys.length - 1; i++) {
109
- obj[keys[i]] = obj[keys[i]] || {};
110
- obj = obj[keys[i]];
111
- }
112
- const lastKey = keys[keys.length - 1];
113
- if (value === undefined || value === "") {
114
- delete obj[lastKey];
115
- }
116
- else {
117
- obj[lastKey] = parseSettingValue(value, setting.type);
118
- }
119
- }
120
- }
121
- if (scope === "user") {
122
- await writeGlobalSettings(settings);
123
- }
124
- else {
125
- await writeSettings(settings, projectPath);
126
- }
127
- }
128
- function parseSettingValue(value, type) {
129
- if (type === "boolean") {
130
- return value === "true" || value === "1";
131
- }
132
- // Try number
133
- const num = Number(value);
134
- if (!Number.isNaN(num) && value.trim() !== "") {
135
- return value; // Keep as string for env vars
136
- }
137
- return value;
138
- }
139
- /** Read all setting values for the catalog */
140
- export async function readAllSettings(catalog, scope, projectPath) {
141
- const values = new Map();
142
- for (const setting of catalog) {
143
- values.set(setting.id, await readSettingValue(setting, scope, projectPath));
144
- }
145
- return values;
146
- }
147
- /** Read all setting values from both scopes simultaneously */
148
- export async function readAllSettingsBothScopes(catalog, projectPath) {
149
- const result = new Map();
150
- const [userValues, projectValues] = await Promise.all([
151
- readAllSettings(catalog, "user", projectPath),
152
- readAllSettings(catalog, "project", projectPath),
153
- ]);
154
- for (const setting of catalog) {
155
- result.set(setting.id, {
156
- user: userValues.get(setting.id),
157
- project: projectValues.get(setting.id),
158
- });
159
- }
160
- return result;
161
- }
162
- /**
163
- * Discover available output styles from installed plugins.
164
- * Scans enabled plugins for outputStyles entries and *-output-style plugin names.
165
- * Returns options suitable for a select setting.
166
- */
167
- export async function discoverOutputStyles(projectPath) {
168
- const styles = [
169
- { label: "Default", value: "" },
170
- ];
171
- const homeDir = process.env.HOME || process.env.USERPROFILE || "";
172
- const cacheDir = path.join(homeDir, ".claude", "plugins", "cache");
173
- // Collect enabled plugins from both scopes
174
- const enabledPlugins = new Set();
175
- try {
176
- const userSettings = await readGlobalSettings();
177
- for (const [k, v] of Object.entries(userSettings.enabledPlugins || {})) {
178
- if (v)
179
- enabledPlugins.add(k);
180
- }
181
- }
182
- catch { }
183
- try {
184
- const projSettings = await readSettings(projectPath);
185
- for (const [k, v] of Object.entries(projSettings.enabledPlugins || {})) {
186
- if (v)
187
- enabledPlugins.add(k);
188
- }
189
- }
190
- catch { }
191
- const seen = new Set();
192
- for (const pluginId of enabledPlugins) {
193
- const [pluginName, marketplace] = pluginId.split("@");
194
- // Check if this is a dedicated output-style plugin (e.g. "explanatory-output-style")
195
- if (pluginName.endsWith("-output-style")) {
196
- const styleName = pluginName
197
- .replace(/-output-style$/, "")
198
- .split("-")
199
- .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
200
- .join(" ");
201
- if (!seen.has(styleName)) {
202
- seen.add(styleName);
203
- styles.push({ label: styleName, value: styleName });
204
- }
205
- continue;
206
- }
207
- // Check plugin.json for outputStyles entries
208
- if (!marketplace)
209
- continue;
210
- const pluginDir = path.join(cacheDir, marketplace, pluginName);
211
- try {
212
- const versions = fs.readdirSync(pluginDir).sort();
213
- const latest = versions[versions.length - 1];
214
- if (!latest)
215
- continue;
216
- const manifestPath = path.join(pluginDir, latest, "plugin.json");
217
- const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
218
- const outputStyles = manifest.outputStyles;
219
- if (!outputStyles?.length)
220
- continue;
221
- for (const stylePath of outputStyles) {
222
- const fullPath = path.join(pluginDir, latest, stylePath);
223
- try {
224
- const content = fs.readFileSync(fullPath, "utf-8");
225
- const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
226
- if (fmMatch) {
227
- const nameMatch = fmMatch[1].match(/^name:\s*(.+)$/m);
228
- if (nameMatch) {
229
- const name = nameMatch[1].trim();
230
- if (!seen.has(name)) {
231
- seen.add(name);
232
- styles.push({ label: name, value: name });
233
- }
234
- }
235
- }
236
- }
237
- catch { }
238
- }
239
- }
240
- catch { }
241
- }
242
- return styles;
243
- }