claudeup 3.16.0 → 3.17.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/package.json
CHANGED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
export const PREDEFINED_PROFILES = [
|
|
2
|
+
{
|
|
3
|
+
id: "frontend-pro",
|
|
4
|
+
name: "Frontend Pro",
|
|
5
|
+
description: "UI implementation, design fidelity, browser-driven workflows",
|
|
6
|
+
icon: "🎨",
|
|
7
|
+
magusPlugins: [
|
|
8
|
+
"dev",
|
|
9
|
+
"code-analysis",
|
|
10
|
+
"terminal",
|
|
11
|
+
"statusline",
|
|
12
|
+
"designer",
|
|
13
|
+
"browser-use",
|
|
14
|
+
"multimodel",
|
|
15
|
+
],
|
|
16
|
+
anthropicPlugins: [
|
|
17
|
+
"feature-dev",
|
|
18
|
+
"frontend-design",
|
|
19
|
+
"code-simplifier",
|
|
20
|
+
"explanatory-output-style",
|
|
21
|
+
"typescript-lsp",
|
|
22
|
+
],
|
|
23
|
+
skills: [
|
|
24
|
+
"React Best Practices",
|
|
25
|
+
"Web Design Guidelines",
|
|
26
|
+
"shadcn/ui",
|
|
27
|
+
"UI/UX Pro Max",
|
|
28
|
+
"Find Skills",
|
|
29
|
+
],
|
|
30
|
+
settings: {
|
|
31
|
+
effortLevel: "high",
|
|
32
|
+
alwaysThinkingEnabled: true,
|
|
33
|
+
model: "claude-sonnet-4-6",
|
|
34
|
+
outputStyle: "explanatory",
|
|
35
|
+
env: {
|
|
36
|
+
CLAUDE_CODE_ENABLE_TASKS: "true",
|
|
37
|
+
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "true",
|
|
38
|
+
},
|
|
39
|
+
includeGitInstructions: true,
|
|
40
|
+
respectGitignore: true,
|
|
41
|
+
enableAllProjectMcpServers: true,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: "backend-forge",
|
|
46
|
+
name: "Backend Forge",
|
|
47
|
+
description: "API development, debugging, code quality, data workflows",
|
|
48
|
+
icon: "⚙️",
|
|
49
|
+
magusPlugins: [
|
|
50
|
+
"dev",
|
|
51
|
+
"code-analysis",
|
|
52
|
+
"terminal",
|
|
53
|
+
"statusline",
|
|
54
|
+
"conductor",
|
|
55
|
+
"multimodel",
|
|
56
|
+
"gtd",
|
|
57
|
+
],
|
|
58
|
+
anthropicPlugins: [
|
|
59
|
+
"feature-dev",
|
|
60
|
+
"code-review",
|
|
61
|
+
"code-simplifier",
|
|
62
|
+
"commit-commands",
|
|
63
|
+
"security-guidance",
|
|
64
|
+
"agent-sdk-dev",
|
|
65
|
+
],
|
|
66
|
+
skills: ["Systematic Debugging", "Neon Postgres", "Find Skills"],
|
|
67
|
+
settings: {
|
|
68
|
+
effortLevel: "high",
|
|
69
|
+
alwaysThinkingEnabled: true,
|
|
70
|
+
model: "claude-sonnet-4-6",
|
|
71
|
+
outputStyle: "concise",
|
|
72
|
+
env: {
|
|
73
|
+
CLAUDE_CODE_ENABLE_TASKS: "true",
|
|
74
|
+
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "true",
|
|
75
|
+
},
|
|
76
|
+
includeGitInstructions: true,
|
|
77
|
+
respectGitignore: true,
|
|
78
|
+
enableAllProjectMcpServers: true,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: "infra-ops",
|
|
83
|
+
name: "Infra Ops",
|
|
84
|
+
description: "Infrastructure, operational debugging, automation, terminal-first",
|
|
85
|
+
icon: "🔧",
|
|
86
|
+
magusPlugins: [
|
|
87
|
+
"dev",
|
|
88
|
+
"code-analysis",
|
|
89
|
+
"terminal",
|
|
90
|
+
"statusline",
|
|
91
|
+
"multimodel",
|
|
92
|
+
"gtd",
|
|
93
|
+
"browser-use",
|
|
94
|
+
],
|
|
95
|
+
anthropicPlugins: [
|
|
96
|
+
"claude-code-setup",
|
|
97
|
+
"hookify",
|
|
98
|
+
"code-review",
|
|
99
|
+
"commit-commands",
|
|
100
|
+
"security-guidance",
|
|
101
|
+
"plugin-dev",
|
|
102
|
+
],
|
|
103
|
+
skills: ["Systematic Debugging", "Find Skills"],
|
|
104
|
+
settings: {
|
|
105
|
+
effortLevel: "high",
|
|
106
|
+
alwaysThinkingEnabled: true,
|
|
107
|
+
model: "claude-opus-4-6",
|
|
108
|
+
outputStyle: "concise",
|
|
109
|
+
env: {
|
|
110
|
+
CLAUDE_CODE_ENABLE_TASKS: "true",
|
|
111
|
+
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "true",
|
|
112
|
+
},
|
|
113
|
+
includeGitInstructions: true,
|
|
114
|
+
respectGitignore: true,
|
|
115
|
+
enableAllProjectMcpServers: true,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: "growth-marketer",
|
|
120
|
+
name: "Growth Marketer",
|
|
121
|
+
description: "SEO, website audits, content production, marketing automation",
|
|
122
|
+
icon: "📈",
|
|
123
|
+
magusPlugins: [
|
|
124
|
+
"dev",
|
|
125
|
+
"code-analysis",
|
|
126
|
+
"terminal",
|
|
127
|
+
"statusline",
|
|
128
|
+
"seo",
|
|
129
|
+
"browser-use",
|
|
130
|
+
"nanobanana",
|
|
131
|
+
"video-editing",
|
|
132
|
+
],
|
|
133
|
+
anthropicPlugins: [
|
|
134
|
+
"explanatory-output-style",
|
|
135
|
+
"frontend-design",
|
|
136
|
+
"playground",
|
|
137
|
+
],
|
|
138
|
+
skills: [
|
|
139
|
+
"Audit Website",
|
|
140
|
+
"Web Design Guidelines",
|
|
141
|
+
"ElevenLabs TTS",
|
|
142
|
+
"Find Skills",
|
|
143
|
+
],
|
|
144
|
+
settings: {
|
|
145
|
+
effortLevel: "medium",
|
|
146
|
+
alwaysThinkingEnabled: false,
|
|
147
|
+
model: "claude-sonnet-4-6",
|
|
148
|
+
outputStyle: "explanatory",
|
|
149
|
+
env: { CLAUDE_CODE_ENABLE_TASKS: "true" },
|
|
150
|
+
respectGitignore: true,
|
|
151
|
+
enableAllProjectMcpServers: true,
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: "team-lead",
|
|
156
|
+
name: "Team Lead",
|
|
157
|
+
description: "Planning, code review, coordination, and broad repo visibility",
|
|
158
|
+
icon: "👔",
|
|
159
|
+
magusPlugins: [
|
|
160
|
+
"dev",
|
|
161
|
+
"code-analysis",
|
|
162
|
+
"terminal",
|
|
163
|
+
"statusline",
|
|
164
|
+
"multimodel",
|
|
165
|
+
"gtd",
|
|
166
|
+
"agentdev",
|
|
167
|
+
],
|
|
168
|
+
anthropicPlugins: [
|
|
169
|
+
"code-review",
|
|
170
|
+
"pr-review-toolkit",
|
|
171
|
+
"claude-md-management",
|
|
172
|
+
"commit-commands",
|
|
173
|
+
"explanatory-output-style",
|
|
174
|
+
"skill-creator",
|
|
175
|
+
],
|
|
176
|
+
skills: ["Systematic Debugging", "Find Skills", "Audit Website"],
|
|
177
|
+
settings: {
|
|
178
|
+
effortLevel: "medium",
|
|
179
|
+
alwaysThinkingEnabled: true,
|
|
180
|
+
model: "claude-sonnet-4-6",
|
|
181
|
+
outputStyle: "explanatory",
|
|
182
|
+
env: {
|
|
183
|
+
CLAUDE_CODE_ENABLE_TASKS: "true",
|
|
184
|
+
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "true",
|
|
185
|
+
},
|
|
186
|
+
includeGitInstructions: true,
|
|
187
|
+
respectGitignore: true,
|
|
188
|
+
enableAllProjectMcpServers: true,
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
];
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
export interface PredefinedProfile {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
icon: string; // emoji or symbol
|
|
6
|
+
magusPlugins: string[];
|
|
7
|
+
anthropicPlugins: string[];
|
|
8
|
+
skills: string[];
|
|
9
|
+
settings: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const PREDEFINED_PROFILES: PredefinedProfile[] = [
|
|
13
|
+
{
|
|
14
|
+
id: "frontend-pro",
|
|
15
|
+
name: "Frontend Pro",
|
|
16
|
+
description: "UI implementation, design fidelity, browser-driven workflows",
|
|
17
|
+
icon: "🎨",
|
|
18
|
+
magusPlugins: [
|
|
19
|
+
"dev",
|
|
20
|
+
"code-analysis",
|
|
21
|
+
"terminal",
|
|
22
|
+
"statusline",
|
|
23
|
+
"designer",
|
|
24
|
+
"browser-use",
|
|
25
|
+
"multimodel",
|
|
26
|
+
],
|
|
27
|
+
anthropicPlugins: [
|
|
28
|
+
"feature-dev",
|
|
29
|
+
"frontend-design",
|
|
30
|
+
"code-simplifier",
|
|
31
|
+
"explanatory-output-style",
|
|
32
|
+
"typescript-lsp",
|
|
33
|
+
],
|
|
34
|
+
skills: [
|
|
35
|
+
"React Best Practices",
|
|
36
|
+
"Web Design Guidelines",
|
|
37
|
+
"shadcn/ui",
|
|
38
|
+
"UI/UX Pro Max",
|
|
39
|
+
"Find Skills",
|
|
40
|
+
],
|
|
41
|
+
settings: {
|
|
42
|
+
effortLevel: "high",
|
|
43
|
+
alwaysThinkingEnabled: true,
|
|
44
|
+
model: "claude-sonnet-4-6",
|
|
45
|
+
outputStyle: "explanatory",
|
|
46
|
+
env: {
|
|
47
|
+
CLAUDE_CODE_ENABLE_TASKS: "true",
|
|
48
|
+
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "true",
|
|
49
|
+
},
|
|
50
|
+
includeGitInstructions: true,
|
|
51
|
+
respectGitignore: true,
|
|
52
|
+
enableAllProjectMcpServers: true,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: "backend-forge",
|
|
57
|
+
name: "Backend Forge",
|
|
58
|
+
description: "API development, debugging, code quality, data workflows",
|
|
59
|
+
icon: "⚙️",
|
|
60
|
+
magusPlugins: [
|
|
61
|
+
"dev",
|
|
62
|
+
"code-analysis",
|
|
63
|
+
"terminal",
|
|
64
|
+
"statusline",
|
|
65
|
+
"conductor",
|
|
66
|
+
"multimodel",
|
|
67
|
+
"gtd",
|
|
68
|
+
],
|
|
69
|
+
anthropicPlugins: [
|
|
70
|
+
"feature-dev",
|
|
71
|
+
"code-review",
|
|
72
|
+
"code-simplifier",
|
|
73
|
+
"commit-commands",
|
|
74
|
+
"security-guidance",
|
|
75
|
+
"agent-sdk-dev",
|
|
76
|
+
],
|
|
77
|
+
skills: ["Systematic Debugging", "Neon Postgres", "Find Skills"],
|
|
78
|
+
settings: {
|
|
79
|
+
effortLevel: "high",
|
|
80
|
+
alwaysThinkingEnabled: true,
|
|
81
|
+
model: "claude-sonnet-4-6",
|
|
82
|
+
outputStyle: "concise",
|
|
83
|
+
env: {
|
|
84
|
+
CLAUDE_CODE_ENABLE_TASKS: "true",
|
|
85
|
+
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "true",
|
|
86
|
+
},
|
|
87
|
+
includeGitInstructions: true,
|
|
88
|
+
respectGitignore: true,
|
|
89
|
+
enableAllProjectMcpServers: true,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: "infra-ops",
|
|
94
|
+
name: "Infra Ops",
|
|
95
|
+
description:
|
|
96
|
+
"Infrastructure, operational debugging, automation, terminal-first",
|
|
97
|
+
icon: "🔧",
|
|
98
|
+
magusPlugins: [
|
|
99
|
+
"dev",
|
|
100
|
+
"code-analysis",
|
|
101
|
+
"terminal",
|
|
102
|
+
"statusline",
|
|
103
|
+
"multimodel",
|
|
104
|
+
"gtd",
|
|
105
|
+
"browser-use",
|
|
106
|
+
],
|
|
107
|
+
anthropicPlugins: [
|
|
108
|
+
"claude-code-setup",
|
|
109
|
+
"hookify",
|
|
110
|
+
"code-review",
|
|
111
|
+
"commit-commands",
|
|
112
|
+
"security-guidance",
|
|
113
|
+
"plugin-dev",
|
|
114
|
+
],
|
|
115
|
+
skills: ["Systematic Debugging", "Find Skills"],
|
|
116
|
+
settings: {
|
|
117
|
+
effortLevel: "high",
|
|
118
|
+
alwaysThinkingEnabled: true,
|
|
119
|
+
model: "claude-opus-4-6",
|
|
120
|
+
outputStyle: "concise",
|
|
121
|
+
env: {
|
|
122
|
+
CLAUDE_CODE_ENABLE_TASKS: "true",
|
|
123
|
+
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "true",
|
|
124
|
+
},
|
|
125
|
+
includeGitInstructions: true,
|
|
126
|
+
respectGitignore: true,
|
|
127
|
+
enableAllProjectMcpServers: true,
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
id: "growth-marketer",
|
|
132
|
+
name: "Growth Marketer",
|
|
133
|
+
description:
|
|
134
|
+
"SEO, website audits, content production, marketing automation",
|
|
135
|
+
icon: "📈",
|
|
136
|
+
magusPlugins: [
|
|
137
|
+
"dev",
|
|
138
|
+
"code-analysis",
|
|
139
|
+
"terminal",
|
|
140
|
+
"statusline",
|
|
141
|
+
"seo",
|
|
142
|
+
"browser-use",
|
|
143
|
+
"nanobanana",
|
|
144
|
+
"video-editing",
|
|
145
|
+
],
|
|
146
|
+
anthropicPlugins: [
|
|
147
|
+
"explanatory-output-style",
|
|
148
|
+
"frontend-design",
|
|
149
|
+
"playground",
|
|
150
|
+
],
|
|
151
|
+
skills: [
|
|
152
|
+
"Audit Website",
|
|
153
|
+
"Web Design Guidelines",
|
|
154
|
+
"ElevenLabs TTS",
|
|
155
|
+
"Find Skills",
|
|
156
|
+
],
|
|
157
|
+
settings: {
|
|
158
|
+
effortLevel: "medium",
|
|
159
|
+
alwaysThinkingEnabled: false,
|
|
160
|
+
model: "claude-sonnet-4-6",
|
|
161
|
+
outputStyle: "explanatory",
|
|
162
|
+
env: { CLAUDE_CODE_ENABLE_TASKS: "true" },
|
|
163
|
+
respectGitignore: true,
|
|
164
|
+
enableAllProjectMcpServers: true,
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
id: "team-lead",
|
|
169
|
+
name: "Team Lead",
|
|
170
|
+
description:
|
|
171
|
+
"Planning, code review, coordination, and broad repo visibility",
|
|
172
|
+
icon: "👔",
|
|
173
|
+
magusPlugins: [
|
|
174
|
+
"dev",
|
|
175
|
+
"code-analysis",
|
|
176
|
+
"terminal",
|
|
177
|
+
"statusline",
|
|
178
|
+
"multimodel",
|
|
179
|
+
"gtd",
|
|
180
|
+
"agentdev",
|
|
181
|
+
],
|
|
182
|
+
anthropicPlugins: [
|
|
183
|
+
"code-review",
|
|
184
|
+
"pr-review-toolkit",
|
|
185
|
+
"claude-md-management",
|
|
186
|
+
"commit-commands",
|
|
187
|
+
"explanatory-output-style",
|
|
188
|
+
"skill-creator",
|
|
189
|
+
],
|
|
190
|
+
skills: ["Systematic Debugging", "Find Skills", "Audit Website"],
|
|
191
|
+
settings: {
|
|
192
|
+
effortLevel: "medium",
|
|
193
|
+
alwaysThinkingEnabled: true,
|
|
194
|
+
model: "claude-sonnet-4-6",
|
|
195
|
+
outputStyle: "explanatory",
|
|
196
|
+
env: {
|
|
197
|
+
CLAUDE_CODE_ENABLE_TASKS: "true",
|
|
198
|
+
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "true",
|
|
199
|
+
},
|
|
200
|
+
includeGitInstructions: true,
|
|
201
|
+
respectGitignore: true,
|
|
202
|
+
enableAllProjectMcpServers: true,
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
];
|
|
@@ -6,7 +6,21 @@ import { useKeyboard } from "../hooks/useKeyboard.js";
|
|
|
6
6
|
import { ScreenLayout } from "../components/layout/index.js";
|
|
7
7
|
import { ScrollableList } from "../components/ScrollableList.js";
|
|
8
8
|
import { listProfiles, applyProfile, renameProfile, deleteProfile, exportProfileToJson, importProfileFromJson, } from "../../services/profiles.js";
|
|
9
|
+
import { readSettings, writeSettings, } from "../../services/claude-settings.js";
|
|
9
10
|
import { writeClipboard, readClipboard, ClipboardUnavailableError, } from "../../utils/clipboard.js";
|
|
11
|
+
import { PREDEFINED_PROFILES, } from "../../data/predefined-profiles.js";
|
|
12
|
+
function buildListItems(profileList) {
|
|
13
|
+
const predefined = PREDEFINED_PROFILES.map((p) => ({
|
|
14
|
+
kind: "predefined",
|
|
15
|
+
profile: p,
|
|
16
|
+
}));
|
|
17
|
+
const saved = profileList.map((e) => ({
|
|
18
|
+
kind: "saved",
|
|
19
|
+
entry: e,
|
|
20
|
+
}));
|
|
21
|
+
return [...predefined, ...saved];
|
|
22
|
+
}
|
|
23
|
+
// ─── Component ────────────────────────────────────────────────────────────────
|
|
10
24
|
export function ProfilesScreen() {
|
|
11
25
|
const { state, dispatch } = useApp();
|
|
12
26
|
const { profiles: profilesState } = state;
|
|
@@ -32,7 +46,8 @@ export function ProfilesScreen() {
|
|
|
32
46
|
const profileList = profilesState.profiles.status === "success"
|
|
33
47
|
? profilesState.profiles.data
|
|
34
48
|
: [];
|
|
35
|
-
const
|
|
49
|
+
const allItems = buildListItems(profileList);
|
|
50
|
+
const selectedItem = allItems[profilesState.selectedIndex];
|
|
36
51
|
// Keyboard handling
|
|
37
52
|
useKeyboard((event) => {
|
|
38
53
|
if (state.isSearching || state.modal)
|
|
@@ -42,28 +57,88 @@ export function ProfilesScreen() {
|
|
|
42
57
|
dispatch({ type: "PROFILES_SELECT", index: newIndex });
|
|
43
58
|
}
|
|
44
59
|
else if (event.name === "down" || event.name === "j") {
|
|
45
|
-
const newIndex = Math.min(Math.max(0,
|
|
60
|
+
const newIndex = Math.min(Math.max(0, allItems.length - 1), profilesState.selectedIndex + 1);
|
|
46
61
|
dispatch({ type: "PROFILES_SELECT", index: newIndex });
|
|
47
62
|
}
|
|
48
63
|
else if (event.name === "enter" || event.name === "a") {
|
|
49
|
-
|
|
64
|
+
if (selectedItem?.kind === "predefined") {
|
|
65
|
+
void handleApplyPredefined(selectedItem.profile);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
void handleApply();
|
|
69
|
+
}
|
|
50
70
|
}
|
|
51
71
|
else if (event.name === "r") {
|
|
52
|
-
|
|
72
|
+
if (selectedItem?.kind === "saved")
|
|
73
|
+
void handleRename();
|
|
53
74
|
}
|
|
54
75
|
else if (event.name === "d") {
|
|
55
|
-
|
|
76
|
+
if (selectedItem?.kind === "saved")
|
|
77
|
+
void handleDelete();
|
|
56
78
|
}
|
|
57
79
|
else if (event.name === "c") {
|
|
58
|
-
|
|
80
|
+
if (selectedItem?.kind === "saved")
|
|
81
|
+
void handleCopy();
|
|
59
82
|
}
|
|
60
83
|
else if (event.name === "i") {
|
|
61
|
-
handleImport();
|
|
84
|
+
void handleImport();
|
|
62
85
|
}
|
|
63
86
|
});
|
|
87
|
+
// ─── Predefined profile apply ─────────────────────────────────────────────
|
|
88
|
+
const handleApplyPredefined = async (profile) => {
|
|
89
|
+
const allPlugins = [
|
|
90
|
+
...profile.magusPlugins.map((p) => `${p}@magus`),
|
|
91
|
+
...profile.anthropicPlugins.map((p) => `${p}@claude-plugins-official`),
|
|
92
|
+
];
|
|
93
|
+
const settingsCount = Object.keys(profile.settings).length;
|
|
94
|
+
const confirmed = await modal.confirm(`Apply ${profile.name}?`, `This will add ${allPlugins.length} plugins, ${profile.skills.length} skills, and update ${settingsCount} settings.\n\nSettings are merged additively — existing values are kept.`);
|
|
95
|
+
if (!confirmed)
|
|
96
|
+
return;
|
|
97
|
+
modal.loading(`Applying "${profile.name}"...`);
|
|
98
|
+
try {
|
|
99
|
+
const settings = await readSettings(state.projectPath);
|
|
100
|
+
// Merge plugins (additive only)
|
|
101
|
+
settings.enabledPlugins = settings.enabledPlugins ?? {};
|
|
102
|
+
for (const plugin of allPlugins) {
|
|
103
|
+
if (!settings.enabledPlugins[plugin]) {
|
|
104
|
+
settings.enabledPlugins[plugin] = true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Merge top-level settings (additive — only set if not already set)
|
|
108
|
+
for (const [key, value] of Object.entries(profile.settings)) {
|
|
109
|
+
if (key === "env") {
|
|
110
|
+
const envMap = value;
|
|
111
|
+
const existing = settings;
|
|
112
|
+
const existingEnv = existing["env"] ?? {};
|
|
113
|
+
for (const [envKey, envVal] of Object.entries(envMap)) {
|
|
114
|
+
if (!existingEnv[envKey]) {
|
|
115
|
+
existingEnv[envKey] = envVal;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
settings["env"] = existingEnv;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
const settingsMap = settings;
|
|
122
|
+
if (settingsMap[key] === undefined) {
|
|
123
|
+
settingsMap[key] = value;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
await writeSettings(settings, state.projectPath);
|
|
128
|
+
modal.hideModal();
|
|
129
|
+
dispatch({ type: "DATA_REFRESH_COMPLETE" });
|
|
130
|
+
await modal.message("Applied", `Profile "${profile.name}" merged into project settings.`, "success");
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
modal.hideModal();
|
|
134
|
+
await modal.message("Error", `Failed to apply profile: ${error}`, "error");
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
// ─── Saved profile actions ────────────────────────────────────────────────
|
|
64
138
|
const handleApply = async () => {
|
|
65
|
-
if (
|
|
139
|
+
if (selectedItem?.kind !== "saved")
|
|
66
140
|
return;
|
|
141
|
+
const selectedProfile = selectedItem.entry;
|
|
67
142
|
const scopeChoice = await modal.select("Apply Profile", `Apply "${selectedProfile.name}" to which scope?`, [
|
|
68
143
|
{ label: "User — ~/.claude/settings.json (global)", value: "user" },
|
|
69
144
|
{
|
|
@@ -98,8 +173,9 @@ export function ProfilesScreen() {
|
|
|
98
173
|
}
|
|
99
174
|
};
|
|
100
175
|
const handleRename = async () => {
|
|
101
|
-
if (
|
|
176
|
+
if (selectedItem?.kind !== "saved")
|
|
102
177
|
return;
|
|
178
|
+
const selectedProfile = selectedItem.entry;
|
|
103
179
|
const newName = await modal.input("Rename Profile", "New name:", selectedProfile.name);
|
|
104
180
|
if (newName === null || !newName.trim())
|
|
105
181
|
return;
|
|
@@ -116,8 +192,9 @@ export function ProfilesScreen() {
|
|
|
116
192
|
}
|
|
117
193
|
};
|
|
118
194
|
const handleDelete = async () => {
|
|
119
|
-
if (
|
|
195
|
+
if (selectedItem?.kind !== "saved")
|
|
120
196
|
return;
|
|
197
|
+
const selectedProfile = selectedItem.entry;
|
|
121
198
|
const confirmed = await modal.confirm(`Delete "${selectedProfile.name}"?`, "This will permanently remove the profile.");
|
|
122
199
|
if (!confirmed)
|
|
123
200
|
return;
|
|
@@ -126,7 +203,7 @@ export function ProfilesScreen() {
|
|
|
126
203
|
await deleteProfile(selectedProfile.id, selectedProfile.scope, state.projectPath);
|
|
127
204
|
modal.hideModal();
|
|
128
205
|
// Adjust selection if we deleted the last item
|
|
129
|
-
const newIndex = Math.max(0, Math.min(profilesState.selectedIndex,
|
|
206
|
+
const newIndex = Math.max(0, Math.min(profilesState.selectedIndex, allItems.length - 2));
|
|
130
207
|
dispatch({ type: "PROFILES_SELECT", index: newIndex });
|
|
131
208
|
await fetchData();
|
|
132
209
|
await modal.message("Deleted", "Profile deleted.", "success");
|
|
@@ -137,8 +214,9 @@ export function ProfilesScreen() {
|
|
|
137
214
|
}
|
|
138
215
|
};
|
|
139
216
|
const handleCopy = async () => {
|
|
140
|
-
if (
|
|
217
|
+
if (selectedItem?.kind !== "saved")
|
|
141
218
|
return;
|
|
219
|
+
const selectedProfile = selectedItem.entry;
|
|
142
220
|
modal.loading("Exporting...");
|
|
143
221
|
try {
|
|
144
222
|
const json = await exportProfileToJson(selectedProfile.id, selectedProfile.scope, state.projectPath);
|
|
@@ -202,6 +280,7 @@ export function ProfilesScreen() {
|
|
|
202
280
|
await modal.message("Error", `Failed to import: ${error}`, "error");
|
|
203
281
|
}
|
|
204
282
|
};
|
|
283
|
+
// ─── Rendering helpers ────────────────────────────────────────────────────
|
|
205
284
|
const formatDate = (iso) => {
|
|
206
285
|
try {
|
|
207
286
|
const d = new Date(iso);
|
|
@@ -215,7 +294,18 @@ export function ProfilesScreen() {
|
|
|
215
294
|
return iso;
|
|
216
295
|
}
|
|
217
296
|
};
|
|
218
|
-
const renderListItem = (
|
|
297
|
+
const renderListItem = (item, _idx, isSelected) => {
|
|
298
|
+
if (item.kind === "predefined") {
|
|
299
|
+
const { profile } = item;
|
|
300
|
+
const pluginCount = profile.magusPlugins.length + profile.anthropicPlugins.length;
|
|
301
|
+
const skillCount = profile.skills.length;
|
|
302
|
+
if (isSelected) {
|
|
303
|
+
return (_jsxs("text", { bg: "blue", fg: "white", children: [" ", profile.icon, " ", profile.name, " \u2014 ", pluginCount, " plugins \u00B7 ", skillCount, " ", "skill", skillCount !== 1 ? "s" : "", " "] }));
|
|
304
|
+
}
|
|
305
|
+
return (_jsxs("text", { children: [_jsx("span", { fg: "blue", children: "[preset]" }), _jsx("span", { children: " " }), _jsxs("span", { fg: "white", children: [profile.icon, " ", profile.name] }), _jsxs("span", { fg: "gray", children: [" ", "\u2014 ", pluginCount, " plugins \u00B7 ", skillCount, " skill", skillCount !== 1 ? "s" : ""] })] }));
|
|
306
|
+
}
|
|
307
|
+
// Saved profile
|
|
308
|
+
const { entry } = item;
|
|
219
309
|
const pluginCount = Object.keys(entry.plugins).length;
|
|
220
310
|
const dateStr = formatDate(entry.updatedAt);
|
|
221
311
|
const scopeColor = entry.scope === "user" ? "cyan" : "green";
|
|
@@ -232,12 +322,24 @@ export function ProfilesScreen() {
|
|
|
232
322
|
if (profilesState.profiles.status === "error") {
|
|
233
323
|
return (_jsxs("text", { fg: "red", children: ["Error: ", profilesState.profiles.error.message] }));
|
|
234
324
|
}
|
|
235
|
-
if (
|
|
236
|
-
return (_jsxs("box", { flexDirection: "column", children: [_jsx("text", { fg: "gray", children: "No profiles yet." }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "green", children: "Press 's' in the Plugins screen to save the current selection as a profile." }) })] }));
|
|
237
|
-
}
|
|
238
|
-
if (!selectedProfile) {
|
|
325
|
+
if (!selectedItem) {
|
|
239
326
|
return _jsx("text", { fg: "gray", children: "Select a profile to see details" });
|
|
240
327
|
}
|
|
328
|
+
if (selectedItem.kind === "predefined") {
|
|
329
|
+
return renderPredefinedDetail(selectedItem.profile);
|
|
330
|
+
}
|
|
331
|
+
return renderSavedDetail(selectedItem.entry);
|
|
332
|
+
};
|
|
333
|
+
const renderPredefinedDetail = (profile) => {
|
|
334
|
+
const allPlugins = [
|
|
335
|
+
...profile.magusPlugins.map((p) => `${p}@magus`),
|
|
336
|
+
...profile.anthropicPlugins.map((p) => `${p}@claude-plugins-official`),
|
|
337
|
+
];
|
|
338
|
+
return (_jsxs("box", { flexDirection: "column", children: [_jsx("text", { fg: "blue", children: _jsxs("strong", { children: [profile.icon, " ", profile.name] }) }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "gray", children: profile.description }) }), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsxs("text", { fg: "gray", children: ["Magus plugins (", profile.magusPlugins.length, "):"] }), profile.magusPlugins.map((p) => (_jsx("box", { children: _jsxs("text", { fg: "cyan", children: [" ", p, "@magus"] }) }, p)))] }), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsxs("text", { fg: "gray", children: ["Anthropic plugins (", profile.anthropicPlugins.length, "):"] }), profile.anthropicPlugins.map((p) => (_jsx("box", { children: _jsxs("text", { fg: "yellow", children: [" ", p, "@claude-plugins-official"] }) }, p)))] }), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsxs("text", { fg: "gray", children: ["Skills (", profile.skills.length, "):"] }), profile.skills.map((s) => (_jsx("box", { children: _jsxs("text", { fg: "white", children: [" ", s] }) }, s)))] }), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsxs("text", { fg: "gray", children: ["Settings (", Object.keys(profile.settings).length, "):"] }), Object.entries(profile.settings)
|
|
339
|
+
.filter(([k]) => k !== "env")
|
|
340
|
+
.map(([k, v]) => (_jsx("box", { children: _jsxs("text", { fg: "white", children: [" ", k, ": ", String(v)] }) }, k)))] }), _jsx("box", { marginTop: 2, flexDirection: "column", children: _jsxs("box", { children: [_jsxs("text", { bg: "blue", fg: "white", children: [" ", "Enter/a", " "] }), _jsxs("text", { fg: "gray", children: [" ", "Apply (merges ", allPlugins.length, " plugins into project settings)"] })] }) })] }));
|
|
341
|
+
};
|
|
342
|
+
const renderSavedDetail = (selectedProfile) => {
|
|
241
343
|
const plugins = Object.keys(selectedProfile.plugins);
|
|
242
344
|
const scopeColor = selectedProfile.scope === "user" ? "cyan" : "green";
|
|
243
345
|
const scopeLabel = selectedProfile.scope === "user"
|
|
@@ -248,8 +350,7 @@ export function ProfilesScreen() {
|
|
|
248
350
|
const profileCount = profileList.length;
|
|
249
351
|
const userCount = profileList.filter((p) => p.scope === "user").length;
|
|
250
352
|
const projCount = profileList.filter((p) => p.scope === "project").length;
|
|
251
|
-
const statusContent = (_jsxs("text", { children: [_jsx("span", { fg: "gray", children: "
|
|
252
|
-
return (_jsx(ScreenLayout, { title: "claudeup Plugin Profiles", currentScreen: "profiles", statusLine: statusContent, footerHints: "\u2191\u2193:nav \u2502 Enter/a:apply \u2502 r:rename \u2502 d:delete \u2502 c:copy \u2502 i:import", listPanel:
|
|
253
|
-
profilesState.profiles.status !== "loading" ? (_jsxs("box", { flexDirection: "column", children: [_jsx("text", { fg: "gray", children: "No profiles yet." }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "green", children: "Go to Plugins (1) and press 's' to save a profile." }) })] })) : (_jsx(ScrollableList, { items: profileList, selectedIndex: profilesState.selectedIndex, renderItem: renderListItem, maxHeight: dimensions.listPanelHeight })), detailPanel: renderDetail() }));
|
|
353
|
+
const statusContent = (_jsxs("text", { children: [_jsxs("span", { fg: "blue", children: [PREDEFINED_PROFILES.length, " presets"] }), _jsx("span", { fg: "gray", children: " + " }), _jsxs("span", { fg: "cyan", children: [userCount, " user"] }), _jsx("span", { fg: "gray", children: " + " }), _jsxs("span", { fg: "green", children: [projCount, " project"] }), _jsx("span", { fg: "gray", children: " = " }), _jsxs("span", { fg: "white", children: [PREDEFINED_PROFILES.length + profileCount, " total"] })] }));
|
|
354
|
+
return (_jsx(ScreenLayout, { title: "claudeup Plugin Profiles", currentScreen: "profiles", statusLine: statusContent, footerHints: "\u2191\u2193:nav \u2502 Enter/a:apply \u2502 r:rename \u2502 d:delete \u2502 c:copy \u2502 i:import", listPanel: _jsx(ScrollableList, { items: allItems, selectedIndex: profilesState.selectedIndex, renderItem: renderListItem, maxHeight: dimensions.listPanelHeight }), detailPanel: renderDetail() }));
|
|
254
355
|
}
|
|
255
356
|
export default ProfilesScreen;
|
|
@@ -12,12 +12,40 @@ import {
|
|
|
12
12
|
exportProfileToJson,
|
|
13
13
|
importProfileFromJson,
|
|
14
14
|
} from "../../services/profiles.js";
|
|
15
|
+
import {
|
|
16
|
+
readSettings,
|
|
17
|
+
writeSettings,
|
|
18
|
+
} from "../../services/claude-settings.js";
|
|
15
19
|
import {
|
|
16
20
|
writeClipboard,
|
|
17
21
|
readClipboard,
|
|
18
22
|
ClipboardUnavailableError,
|
|
19
23
|
} from "../../utils/clipboard.js";
|
|
20
24
|
import type { ProfileEntry } from "../../types/index.js";
|
|
25
|
+
import {
|
|
26
|
+
PREDEFINED_PROFILES,
|
|
27
|
+
type PredefinedProfile,
|
|
28
|
+
} from "../../data/predefined-profiles.js";
|
|
29
|
+
|
|
30
|
+
// ─── List item discriminated union ───────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
type ListItem =
|
|
33
|
+
| { kind: "predefined"; profile: PredefinedProfile }
|
|
34
|
+
| { kind: "saved"; entry: ProfileEntry };
|
|
35
|
+
|
|
36
|
+
function buildListItems(profileList: ProfileEntry[]): ListItem[] {
|
|
37
|
+
const predefined: ListItem[] = PREDEFINED_PROFILES.map((p) => ({
|
|
38
|
+
kind: "predefined" as const,
|
|
39
|
+
profile: p,
|
|
40
|
+
}));
|
|
41
|
+
const saved: ListItem[] = profileList.map((e) => ({
|
|
42
|
+
kind: "saved" as const,
|
|
43
|
+
entry: e,
|
|
44
|
+
}));
|
|
45
|
+
return [...predefined, ...saved];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ─── Component ────────────────────────────────────────────────────────────────
|
|
21
49
|
|
|
22
50
|
export function ProfilesScreen() {
|
|
23
51
|
const { state, dispatch } = useApp();
|
|
@@ -48,8 +76,8 @@ export function ProfilesScreen() {
|
|
|
48
76
|
? profilesState.profiles.data
|
|
49
77
|
: [];
|
|
50
78
|
|
|
51
|
-
const
|
|
52
|
-
|
|
79
|
+
const allItems = buildListItems(profileList);
|
|
80
|
+
const selectedItem: ListItem | undefined = allItems[profilesState.selectedIndex];
|
|
53
81
|
|
|
54
82
|
// Keyboard handling
|
|
55
83
|
useKeyboard((event) => {
|
|
@@ -60,25 +88,100 @@ export function ProfilesScreen() {
|
|
|
60
88
|
dispatch({ type: "PROFILES_SELECT", index: newIndex });
|
|
61
89
|
} else if (event.name === "down" || event.name === "j") {
|
|
62
90
|
const newIndex = Math.min(
|
|
63
|
-
Math.max(0,
|
|
91
|
+
Math.max(0, allItems.length - 1),
|
|
64
92
|
profilesState.selectedIndex + 1,
|
|
65
93
|
);
|
|
66
94
|
dispatch({ type: "PROFILES_SELECT", index: newIndex });
|
|
67
95
|
} else if (event.name === "enter" || event.name === "a") {
|
|
68
|
-
|
|
96
|
+
if (selectedItem?.kind === "predefined") {
|
|
97
|
+
void handleApplyPredefined(selectedItem.profile);
|
|
98
|
+
} else {
|
|
99
|
+
void handleApply();
|
|
100
|
+
}
|
|
69
101
|
} else if (event.name === "r") {
|
|
70
|
-
handleRename();
|
|
102
|
+
if (selectedItem?.kind === "saved") void handleRename();
|
|
71
103
|
} else if (event.name === "d") {
|
|
72
|
-
handleDelete();
|
|
104
|
+
if (selectedItem?.kind === "saved") void handleDelete();
|
|
73
105
|
} else if (event.name === "c") {
|
|
74
|
-
handleCopy();
|
|
106
|
+
if (selectedItem?.kind === "saved") void handleCopy();
|
|
75
107
|
} else if (event.name === "i") {
|
|
76
|
-
handleImport();
|
|
108
|
+
void handleImport();
|
|
77
109
|
}
|
|
78
110
|
});
|
|
79
111
|
|
|
112
|
+
// ─── Predefined profile apply ─────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
const handleApplyPredefined = async (profile: PredefinedProfile) => {
|
|
115
|
+
const allPlugins = [
|
|
116
|
+
...profile.magusPlugins.map((p) => `${p}@magus`),
|
|
117
|
+
...profile.anthropicPlugins.map(
|
|
118
|
+
(p) => `${p}@claude-plugins-official`,
|
|
119
|
+
),
|
|
120
|
+
];
|
|
121
|
+
const settingsCount = Object.keys(profile.settings).length;
|
|
122
|
+
|
|
123
|
+
const confirmed = await modal.confirm(
|
|
124
|
+
`Apply ${profile.name}?`,
|
|
125
|
+
`This will add ${allPlugins.length} plugins, ${profile.skills.length} skills, and update ${settingsCount} settings.\n\nSettings are merged additively — existing values are kept.`,
|
|
126
|
+
);
|
|
127
|
+
if (!confirmed) return;
|
|
128
|
+
|
|
129
|
+
modal.loading(`Applying "${profile.name}"...`);
|
|
130
|
+
try {
|
|
131
|
+
const settings = await readSettings(state.projectPath);
|
|
132
|
+
|
|
133
|
+
// Merge plugins (additive only)
|
|
134
|
+
settings.enabledPlugins = settings.enabledPlugins ?? {};
|
|
135
|
+
for (const plugin of allPlugins) {
|
|
136
|
+
if (!settings.enabledPlugins[plugin]) {
|
|
137
|
+
settings.enabledPlugins[plugin] = true;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Merge top-level settings (additive — only set if not already set)
|
|
142
|
+
for (const [key, value] of Object.entries(profile.settings)) {
|
|
143
|
+
if (key === "env") {
|
|
144
|
+
const envMap = value as Record<string, string>;
|
|
145
|
+
const existing = settings as Record<string, unknown>;
|
|
146
|
+
const existingEnv =
|
|
147
|
+
(existing["env"] as Record<string, string> | undefined) ?? {};
|
|
148
|
+
for (const [envKey, envVal] of Object.entries(envMap)) {
|
|
149
|
+
if (!existingEnv[envKey]) {
|
|
150
|
+
existingEnv[envKey] = envVal;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
(settings as Record<string, unknown>)["env"] = existingEnv;
|
|
154
|
+
} else {
|
|
155
|
+
const settingsMap = settings as Record<string, unknown>;
|
|
156
|
+
if (settingsMap[key] === undefined) {
|
|
157
|
+
settingsMap[key] = value;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
await writeSettings(settings, state.projectPath);
|
|
163
|
+
modal.hideModal();
|
|
164
|
+
dispatch({ type: "DATA_REFRESH_COMPLETE" });
|
|
165
|
+
await modal.message(
|
|
166
|
+
"Applied",
|
|
167
|
+
`Profile "${profile.name}" merged into project settings.`,
|
|
168
|
+
"success",
|
|
169
|
+
);
|
|
170
|
+
} catch (error) {
|
|
171
|
+
modal.hideModal();
|
|
172
|
+
await modal.message(
|
|
173
|
+
"Error",
|
|
174
|
+
`Failed to apply profile: ${error}`,
|
|
175
|
+
"error",
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// ─── Saved profile actions ────────────────────────────────────────────────
|
|
181
|
+
|
|
80
182
|
const handleApply = async () => {
|
|
81
|
-
if (
|
|
183
|
+
if (selectedItem?.kind !== "saved") return;
|
|
184
|
+
const selectedProfile = selectedItem.entry;
|
|
82
185
|
|
|
83
186
|
const scopeChoice = await modal.select(
|
|
84
187
|
"Apply Profile",
|
|
@@ -138,7 +241,8 @@ export function ProfilesScreen() {
|
|
|
138
241
|
};
|
|
139
242
|
|
|
140
243
|
const handleRename = async () => {
|
|
141
|
-
if (
|
|
244
|
+
if (selectedItem?.kind !== "saved") return;
|
|
245
|
+
const selectedProfile = selectedItem.entry;
|
|
142
246
|
|
|
143
247
|
const newName = await modal.input(
|
|
144
248
|
"Rename Profile",
|
|
@@ -169,7 +273,8 @@ export function ProfilesScreen() {
|
|
|
169
273
|
};
|
|
170
274
|
|
|
171
275
|
const handleDelete = async () => {
|
|
172
|
-
if (
|
|
276
|
+
if (selectedItem?.kind !== "saved") return;
|
|
277
|
+
const selectedProfile = selectedItem.entry;
|
|
173
278
|
|
|
174
279
|
const confirmed = await modal.confirm(
|
|
175
280
|
`Delete "${selectedProfile.name}"?`,
|
|
@@ -188,7 +293,7 @@ export function ProfilesScreen() {
|
|
|
188
293
|
// Adjust selection if we deleted the last item
|
|
189
294
|
const newIndex = Math.max(
|
|
190
295
|
0,
|
|
191
|
-
Math.min(profilesState.selectedIndex,
|
|
296
|
+
Math.min(profilesState.selectedIndex, allItems.length - 2),
|
|
192
297
|
);
|
|
193
298
|
dispatch({ type: "PROFILES_SELECT", index: newIndex });
|
|
194
299
|
await fetchData();
|
|
@@ -200,7 +305,8 @@ export function ProfilesScreen() {
|
|
|
200
305
|
};
|
|
201
306
|
|
|
202
307
|
const handleCopy = async () => {
|
|
203
|
-
if (
|
|
308
|
+
if (selectedItem?.kind !== "saved") return;
|
|
309
|
+
const selectedProfile = selectedItem.entry;
|
|
204
310
|
|
|
205
311
|
modal.loading("Exporting...");
|
|
206
312
|
try {
|
|
@@ -284,6 +390,8 @@ export function ProfilesScreen() {
|
|
|
284
390
|
}
|
|
285
391
|
};
|
|
286
392
|
|
|
393
|
+
// ─── Rendering helpers ────────────────────────────────────────────────────
|
|
394
|
+
|
|
287
395
|
const formatDate = (iso: string): string => {
|
|
288
396
|
try {
|
|
289
397
|
const d = new Date(iso);
|
|
@@ -297,11 +405,41 @@ export function ProfilesScreen() {
|
|
|
297
405
|
}
|
|
298
406
|
};
|
|
299
407
|
|
|
300
|
-
const renderListItem = (
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
408
|
+
const renderListItem = (item: ListItem, _idx: number, isSelected: boolean) => {
|
|
409
|
+
if (item.kind === "predefined") {
|
|
410
|
+
const { profile } = item;
|
|
411
|
+
const pluginCount =
|
|
412
|
+
profile.magusPlugins.length + profile.anthropicPlugins.length;
|
|
413
|
+
const skillCount = profile.skills.length;
|
|
414
|
+
|
|
415
|
+
if (isSelected) {
|
|
416
|
+
return (
|
|
417
|
+
<text bg="blue" fg="white">
|
|
418
|
+
{" "}
|
|
419
|
+
{profile.icon} {profile.name} — {pluginCount} plugins · {skillCount}{" "}
|
|
420
|
+
skill{skillCount !== 1 ? "s" : ""}{" "}
|
|
421
|
+
</text>
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return (
|
|
426
|
+
<text>
|
|
427
|
+
<span fg="blue">[preset]</span>
|
|
428
|
+
<span> </span>
|
|
429
|
+
<span fg="white">
|
|
430
|
+
{profile.icon} {profile.name}
|
|
431
|
+
</span>
|
|
432
|
+
<span fg="gray">
|
|
433
|
+
{" "}
|
|
434
|
+
— {pluginCount} plugins · {skillCount} skill
|
|
435
|
+
{skillCount !== 1 ? "s" : ""}
|
|
436
|
+
</span>
|
|
437
|
+
</text>
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Saved profile
|
|
442
|
+
const { entry } = item;
|
|
305
443
|
const pluginCount = Object.keys(entry.plugins).length;
|
|
306
444
|
const dateStr = formatDate(entry.updatedAt);
|
|
307
445
|
const scopeColor = entry.scope === "user" ? "cyan" : "green";
|
|
@@ -341,24 +479,93 @@ export function ProfilesScreen() {
|
|
|
341
479
|
);
|
|
342
480
|
}
|
|
343
481
|
|
|
344
|
-
if (
|
|
345
|
-
return
|
|
346
|
-
<box flexDirection="column">
|
|
347
|
-
<text fg="gray">No profiles yet.</text>
|
|
348
|
-
<box marginTop={1}>
|
|
349
|
-
<text fg="green">
|
|
350
|
-
Press 's' in the Plugins screen to save the current selection as a
|
|
351
|
-
profile.
|
|
352
|
-
</text>
|
|
353
|
-
</box>
|
|
354
|
-
</box>
|
|
355
|
-
);
|
|
482
|
+
if (!selectedItem) {
|
|
483
|
+
return <text fg="gray">Select a profile to see details</text>;
|
|
356
484
|
}
|
|
357
485
|
|
|
358
|
-
if (
|
|
359
|
-
return
|
|
486
|
+
if (selectedItem.kind === "predefined") {
|
|
487
|
+
return renderPredefinedDetail(selectedItem.profile);
|
|
360
488
|
}
|
|
361
489
|
|
|
490
|
+
return renderSavedDetail(selectedItem.entry);
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
const renderPredefinedDetail = (profile: PredefinedProfile) => {
|
|
494
|
+
const allPlugins = [
|
|
495
|
+
...profile.magusPlugins.map((p) => `${p}@magus`),
|
|
496
|
+
...profile.anthropicPlugins.map((p) => `${p}@claude-plugins-official`),
|
|
497
|
+
];
|
|
498
|
+
|
|
499
|
+
return (
|
|
500
|
+
<box flexDirection="column">
|
|
501
|
+
<text fg="blue">
|
|
502
|
+
<strong>
|
|
503
|
+
{profile.icon} {profile.name}
|
|
504
|
+
</strong>
|
|
505
|
+
</text>
|
|
506
|
+
<box marginTop={1}>
|
|
507
|
+
<text fg="gray">{profile.description}</text>
|
|
508
|
+
</box>
|
|
509
|
+
<box marginTop={1} flexDirection="column">
|
|
510
|
+
<text fg="gray">
|
|
511
|
+
Magus plugins ({profile.magusPlugins.length}):
|
|
512
|
+
</text>
|
|
513
|
+
{profile.magusPlugins.map((p) => (
|
|
514
|
+
<box key={p}>
|
|
515
|
+
<text fg="cyan"> {p}@magus</text>
|
|
516
|
+
</box>
|
|
517
|
+
))}
|
|
518
|
+
</box>
|
|
519
|
+
<box marginTop={1} flexDirection="column">
|
|
520
|
+
<text fg="gray">
|
|
521
|
+
Anthropic plugins ({profile.anthropicPlugins.length}):
|
|
522
|
+
</text>
|
|
523
|
+
{profile.anthropicPlugins.map((p) => (
|
|
524
|
+
<box key={p}>
|
|
525
|
+
<text fg="yellow"> {p}@claude-plugins-official</text>
|
|
526
|
+
</box>
|
|
527
|
+
))}
|
|
528
|
+
</box>
|
|
529
|
+
<box marginTop={1} flexDirection="column">
|
|
530
|
+
<text fg="gray">Skills ({profile.skills.length}):</text>
|
|
531
|
+
{profile.skills.map((s) => (
|
|
532
|
+
<box key={s}>
|
|
533
|
+
<text fg="white"> {s}</text>
|
|
534
|
+
</box>
|
|
535
|
+
))}
|
|
536
|
+
</box>
|
|
537
|
+
<box marginTop={1} flexDirection="column">
|
|
538
|
+
<text fg="gray">
|
|
539
|
+
Settings ({Object.keys(profile.settings).length}):
|
|
540
|
+
</text>
|
|
541
|
+
{Object.entries(profile.settings)
|
|
542
|
+
.filter(([k]) => k !== "env")
|
|
543
|
+
.map(([k, v]) => (
|
|
544
|
+
<box key={k}>
|
|
545
|
+
<text fg="white">
|
|
546
|
+
{" "}
|
|
547
|
+
{k}: {String(v)}
|
|
548
|
+
</text>
|
|
549
|
+
</box>
|
|
550
|
+
))}
|
|
551
|
+
</box>
|
|
552
|
+
<box marginTop={2} flexDirection="column">
|
|
553
|
+
<box>
|
|
554
|
+
<text bg="blue" fg="white">
|
|
555
|
+
{" "}
|
|
556
|
+
Enter/a{" "}
|
|
557
|
+
</text>
|
|
558
|
+
<text fg="gray">
|
|
559
|
+
{" "}
|
|
560
|
+
Apply (merges {allPlugins.length} plugins into project settings)
|
|
561
|
+
</text>
|
|
562
|
+
</box>
|
|
563
|
+
</box>
|
|
564
|
+
</box>
|
|
565
|
+
);
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
const renderSavedDetail = (selectedProfile: ProfileEntry) => {
|
|
362
569
|
const plugins = Object.keys(selectedProfile.plugins);
|
|
363
570
|
const scopeColor = selectedProfile.scope === "user" ? "cyan" : "green";
|
|
364
571
|
const scopeLabel =
|
|
@@ -444,12 +651,13 @@ export function ProfilesScreen() {
|
|
|
444
651
|
|
|
445
652
|
const statusContent = (
|
|
446
653
|
<text>
|
|
447
|
-
<span fg="
|
|
654
|
+
<span fg="blue">{PREDEFINED_PROFILES.length} presets</span>
|
|
655
|
+
<span fg="gray"> + </span>
|
|
448
656
|
<span fg="cyan">{userCount} user</span>
|
|
449
657
|
<span fg="gray"> + </span>
|
|
450
658
|
<span fg="green">{projCount} project</span>
|
|
451
659
|
<span fg="gray"> = </span>
|
|
452
|
-
<span fg="white">{profileCount} total</span>
|
|
660
|
+
<span fg="white">{PREDEFINED_PROFILES.length + profileCount} total</span>
|
|
453
661
|
</text>
|
|
454
662
|
);
|
|
455
663
|
|
|
@@ -460,24 +668,12 @@ export function ProfilesScreen() {
|
|
|
460
668
|
statusLine={statusContent}
|
|
461
669
|
footerHints="↑↓:nav │ Enter/a:apply │ r:rename │ d:delete │ c:copy │ i:import"
|
|
462
670
|
listPanel={
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
Go to Plugins (1) and press 's' to save a profile.
|
|
470
|
-
</text>
|
|
471
|
-
</box>
|
|
472
|
-
</box>
|
|
473
|
-
) : (
|
|
474
|
-
<ScrollableList
|
|
475
|
-
items={profileList}
|
|
476
|
-
selectedIndex={profilesState.selectedIndex}
|
|
477
|
-
renderItem={renderListItem}
|
|
478
|
-
maxHeight={dimensions.listPanelHeight}
|
|
479
|
-
/>
|
|
480
|
-
)
|
|
671
|
+
<ScrollableList
|
|
672
|
+
items={allItems}
|
|
673
|
+
selectedIndex={profilesState.selectedIndex}
|
|
674
|
+
renderItem={renderListItem}
|
|
675
|
+
maxHeight={dimensions.listPanelHeight}
|
|
676
|
+
/>
|
|
481
677
|
}
|
|
482
678
|
detailPanel={renderDetail()}
|
|
483
679
|
/>
|