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.
- package/bin/claudeup.js +43 -19
- package/package.json +14 -19
- package/scripts/build-binaries.ts +77 -0
- package/scripts/check-optional-deps.ts +49 -0
- package/scripts/test-isolated.ts +43 -0
- package/src/__tests__/alias-adopt.test.ts +364 -0
- package/src/__tests__/alias-parser.test.ts +92 -0
- package/src/__tests__/alias-shell-writer.test.ts +7 -0
- package/src/__tests__/alias-store.test.ts +77 -0
- package/src/__tests__/cli-router.test.ts +65 -0
- package/src/__tests__/conventions-integration.test.ts +745 -0
- package/src/__tests__/conventions-manager.test.ts +1172 -0
- package/src/__tests__/doctor-bins.test.ts +35 -0
- package/src/__tests__/doctor.test.ts +450 -0
- package/src/__tests__/dual-write-prevention.test.ts +14 -4
- package/src/__tests__/file-locking.test.ts +208 -0
- package/src/__tests__/gitignore-prerun.test.ts +1 -0
- package/src/__tests__/install-command.test.ts +81 -0
- package/src/__tests__/install-plan.test.ts +98 -0
- package/src/__tests__/manifest.test.ts +136 -0
- package/src/__tests__/marketplace-refresh.test.ts +267 -0
- package/src/__tests__/plugin-requires.test.ts +133 -0
- package/src/__tests__/plugin-setup.test.ts +118 -0
- package/src/__tests__/profile-command.test.ts +116 -0
- package/src/__tests__/profile-materializer.test.ts +82 -0
- package/src/__tests__/profile-sync.test.ts +136 -0
- package/src/__tests__/registry-version-resolution.test.ts +74 -0
- package/src/__tests__/resolve-executable.test.ts +42 -0
- package/src/__tests__/resolver.test.ts +218 -0
- package/src/__tests__/symlink-manager.test.ts +99 -0
- package/src/__tests__/toolchain.test.ts +56 -0
- package/src/cli/claude.ts +21 -0
- package/src/cli/doctor.ts +132 -0
- package/src/cli/install.ts +360 -0
- package/src/cli/profile.ts +166 -0
- package/src/cli/router.ts +107 -0
- package/src/cli/update.ts +85 -0
- package/src/data/alias-flags.ts +11 -1
- package/src/data/cli-tools.ts +7 -7
- package/src/data/gitignore-defaults.ts +4 -0
- package/src/data/marketplaces.ts +12 -0
- package/src/data/predefined-profiles.ts +3 -4
- package/src/main.tsx +11 -154
- package/src/prerunner/index.ts +62 -27
- package/src/services/alias-shell-writer.ts +382 -8
- package/src/services/alias-store.ts +60 -0
- package/src/services/claude-cli.ts +19 -22
- package/src/services/claude-settings.ts +99 -26
- package/src/services/conventions-manager.ts +865 -0
- package/src/services/doctor-bins.ts +49 -0
- package/src/services/doctor.ts +509 -0
- package/src/services/install-plan.ts +107 -0
- package/src/services/manifest.ts +166 -0
- package/src/services/marketplace-refresh.ts +173 -0
- package/src/services/plugin-manager.ts +7 -1
- package/src/services/plugin-requires.ts +215 -0
- package/src/services/plugin-setup.ts +61 -4
- package/src/services/plugin-version-check.ts +9 -4
- package/src/services/profile-materializer.ts +61 -0
- package/src/services/profile-sync.ts +150 -0
- package/src/services/resolver.ts +293 -0
- package/src/services/symlink-manager.ts +146 -0
- package/src/services/toolchain.ts +97 -0
- package/src/services/version-check.ts +10 -11
- package/src/types/index.ts +138 -31
- package/src/ui/App.tsx +16 -11
- package/src/ui/components/FlagDetailEditor.tsx +0 -0
- package/src/ui/components/modals/ConfirmModal.tsx +1 -1
- package/src/ui/screens/AliasScreen.tsx +491 -359
- package/src/ui/screens/CliToolsScreen.tsx +11 -3
- package/src/ui/screens/PluginsScreen.tsx +3 -1
- package/src/ui/screens/index.ts +0 -1
- package/src/ui/state/reducer.ts +6 -102
- package/src/ui/state/types.ts +8 -35
- package/src/utils/command-utils.ts +23 -0
- package/src/utils/file-locking.ts +144 -0
- package/src/data/alias-flags.js +0 -196
- package/src/data/cli-tools.js +0 -123
- package/src/data/gitignore-defaults.js +0 -24
- package/src/data/gitignore-reasons.js +0 -97
- package/src/data/gitignore-templates.js +0 -21
- package/src/data/marketplaces.js +0 -138
- package/src/data/mcp-servers.js +0 -509
- package/src/data/predefined-profiles.js +0 -248
- package/src/data/settings-catalog.js +0 -625
- package/src/data/skill-repos.js +0 -160
- package/src/data/statuslines.js +0 -159
- package/src/data/statuslines.ts +0 -188
- package/src/index.js +0 -4
- package/src/index.ts +0 -5
- package/src/main.js +0 -170
- package/src/prerunner/index.js +0 -252
- package/src/services/alias-settings.js +0 -51
- package/src/services/alias-shell-writer.js +0 -764
- package/src/services/alias-store.js +0 -77
- package/src/services/claude-cli.js +0 -189
- package/src/services/claude-runner.js +0 -28
- package/src/services/claude-settings.js +0 -1223
- package/src/services/gitignore-detector.js +0 -155
- package/src/services/gitignore-fixer.js +0 -231
- package/src/services/gitignore-prerun.js +0 -46
- package/src/services/gitignore-resolver.js +0 -143
- package/src/services/gitignore-service.js +0 -117
- package/src/services/local-marketplace.js +0 -339
- package/src/services/marketplace-fetcher.js +0 -96
- package/src/services/marketplace-sync.js +0 -94
- package/src/services/mcp-registry.js +0 -87
- package/src/services/plugin-manager.js +0 -473
- package/src/services/plugin-mcp-config.js +0 -177
- package/src/services/plugin-setup.js +0 -444
- package/src/services/plugin-version-check.js +0 -262
- package/src/services/profiles.js +0 -161
- package/src/services/settings-manager.js +0 -243
- package/src/services/skills-manager.js +0 -393
- package/src/services/skillsmp-client.js +0 -87
- package/src/services/update-cache.js +0 -52
- package/src/services/version-check.js +0 -99
- package/src/types/gitignore.js +0 -6
- package/src/types/index.js +0 -1
- package/src/ui/App.js +0 -346
- package/src/ui/adapters/pluginsAdapter.js +0 -139
- package/src/ui/adapters/settingsAdapter.js +0 -111
- package/src/ui/adapters/skillsAdapter.js +0 -154
- package/src/ui/components/CategoryHeader.js +0 -9
- package/src/ui/components/EmptyFilterState.js +0 -4
- package/src/ui/components/ScopeIndicator.js +0 -30
- package/src/ui/components/ScrollableList.js +0 -36
- package/src/ui/components/SearchInput.js +0 -19
- package/src/ui/components/StyledText.js +0 -39
- package/src/ui/components/TabBar.js +0 -19
- package/src/ui/components/layout/FooterHints.js +0 -29
- package/src/ui/components/layout/Panel.js +0 -6
- package/src/ui/components/layout/ProgressBar.js +0 -14
- package/src/ui/components/layout/ScopeTabs.js +0 -6
- package/src/ui/components/layout/ScreenLayout.js +0 -16
- package/src/ui/components/layout/index.js +0 -5
- package/src/ui/components/modals/ConfirmModal.js +0 -14
- package/src/ui/components/modals/InputModal.js +0 -5
- package/src/ui/components/modals/LoadingModal.js +0 -17
- package/src/ui/components/modals/MessageModal.js +0 -16
- package/src/ui/components/modals/ModalContainer.js +0 -137
- package/src/ui/components/modals/SelectModal.js +0 -18
- package/src/ui/components/modals/VersionMismatchModal.js +0 -36
- package/src/ui/components/modals/index.js +0 -6
- package/src/ui/components/primitives/ActionHints.js +0 -13
- package/src/ui/components/primitives/DetailSection.js +0 -7
- package/src/ui/components/primitives/KeyValueLine.js +0 -8
- package/src/ui/components/primitives/ListCategoryRow.js +0 -8
- package/src/ui/components/primitives/MetaText.js +0 -8
- package/src/ui/components/primitives/ScopeDetail.js +0 -32
- package/src/ui/components/primitives/ScopeSquares.js +0 -11
- package/src/ui/components/primitives/SelectableRow.js +0 -5
- package/src/ui/components/primitives/index.js +0 -8
- package/src/ui/hooks/index.js +0 -4
- package/src/ui/hooks/useAsyncData.js +0 -77
- package/src/ui/hooks/useGitignoreModal.js +0 -74
- package/src/ui/hooks/useKeyboard.js +0 -13
- package/src/ui/hooks/useKeyboardHandler.js +0 -39
- package/src/ui/hooks/useMismatchModal.js +0 -77
- package/src/ui/registry.js +0 -1
- package/src/ui/renderers/cliToolRenderers.js +0 -54
- package/src/ui/renderers/gitignoreRenderers.js +0 -46
- package/src/ui/renderers/mcpRenderers.js +0 -26
- package/src/ui/renderers/pluginRenderers.js +0 -124
- package/src/ui/renderers/profileRenderers.js +0 -177
- package/src/ui/renderers/settingsRenderers.js +0 -73
- package/src/ui/renderers/skillRenderers.js +0 -138
- package/src/ui/screens/AliasScreen.js +0 -1008
- package/src/ui/screens/CliToolsScreen.js +0 -338
- package/src/ui/screens/EnvVarsScreen.js +0 -152
- package/src/ui/screens/GitignoreScreen.js +0 -328
- package/src/ui/screens/McpRegistryScreen.js +0 -238
- package/src/ui/screens/McpScreen.js +0 -176
- package/src/ui/screens/ModelSelectorScreen.js +0 -296
- package/src/ui/screens/ModelSelectorScreen.tsx +0 -444
- package/src/ui/screens/PluginsScreen.js +0 -766
- package/src/ui/screens/ProfilesScreen.js +0 -298
- package/src/ui/screens/SkillsScreen.js +0 -549
- package/src/ui/screens/StatusLineScreen.js +0 -206
- package/src/ui/screens/StatusLineScreen.tsx +0 -416
- package/src/ui/screens/index.js +0 -10
- package/src/ui/state/AnimationContext.js +0 -34
- package/src/ui/state/AppContext.js +0 -162
- package/src/ui/state/DimensionsContext.js +0 -71
- package/src/ui/state/reducer.js +0 -543
- package/src/ui/state/types.js +0 -1
- package/src/ui/theme.js +0 -47
- package/src/utils/clipboard.js +0 -56
- package/src/utils/command-utils.js +0 -19
- package/src/utils/fuzzy-search.js +0 -101
- package/src/utils/string-utils.js +0 -62
package/src/services/profiles.js
DELETED
|
@@ -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
|
-
}
|