Skip to content

Commit d755484

Browse files
committed
feat: Replace mode configurator UI with mode filter tool
1 parent 1d1b83b commit d755484

File tree

4 files changed

+363
-15
lines changed

4 files changed

+363
-15
lines changed

tools/mode_configurator/index.html

Lines changed: 359 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,361 @@
1-
<!doctype html>
1+
<!DOCTYPE html>
22
<html lang="en">
3-
<head>
4-
<meta charset="UTF-8" />
5-
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
6-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7-
<title>Roo Commander Mode Configurator</title>
8-
<meta name="description" content="A tool to select and configure custom modes for Roo Commander, generating a JSON output for easy integration." />
9-
</head>
10-
<body>
11-
<div id="app"></div>
12-
<script type="module" src="/src/main.js"></script>
13-
</body>
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Roo Code Mode Filter</title>
7+
<style>
8+
body {
9+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
10+
margin: 20px;
11+
line-height: 1.6;
12+
background-color: #f8f9fa;
13+
color: #333;
14+
}
15+
.container {
16+
max-width: 900px;
17+
margin: 0 auto;
18+
background-color: #fff;
19+
padding: 25px;
20+
border-radius: 8px;
21+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
22+
}
23+
h1, h2 {
24+
border-bottom: 1px solid #eee;
25+
padding-bottom: 10px;
26+
margin-bottom: 20px;
27+
color: #007bff;
28+
}
29+
label {
30+
display: block;
31+
margin-bottom: 8px;
32+
font-weight: bold;
33+
}
34+
textarea {
35+
width: 95%; /* Adjusted width */
36+
padding: 10px;
37+
margin-bottom: 15px;
38+
border: 1px solid #ccc;
39+
border-radius: 4px;
40+
font-family: monospace;
41+
font-size: 0.9em;
42+
min-height: 150px;
43+
resize: vertical;
44+
background-color: #fff; /* Ensure background is white */
45+
color: #333; /* Ensure text color is dark */
46+
}
47+
button, .file-upload-label {
48+
background-color: #007bff;
49+
color: white;
50+
border: none;
51+
padding: 10px 18px;
52+
border-radius: 4px;
53+
cursor: pointer;
54+
font-size: 1em;
55+
margin-right: 10px;
56+
transition: background-color 0.2s ease;
57+
display: inline-block; /* Needed for label styling */
58+
vertical-align: middle; /* Align with other buttons */
59+
margin-bottom: 10px; /* Spacing */
60+
}
61+
button:hover, .file-upload-label:hover {
62+
background-color: #0056b3;
63+
}
64+
button:disabled {
65+
background-color: #cccccc;
66+
cursor: not-allowed;
67+
}
68+
#fileInput {
69+
display: none; /* Hide the actual file input */
70+
}
71+
.input-area {
72+
border: 1px solid #eee;
73+
padding: 15px;
74+
margin-bottom: 20px;
75+
border-radius: 5px;
76+
}
77+
#modeListContainer {
78+
margin-top: 20px;
79+
}
80+
#modeList {
81+
margin-top: 10px;
82+
padding: 15px;
83+
border: 1px dashed #ddd;
84+
border-radius: 4px;
85+
max-height: 300px;
86+
overflow-y: auto;
87+
background-color: #fdfdfd;
88+
}
89+
#modeList div {
90+
margin-bottom: 8px;
91+
display: flex;
92+
align-items: center;
93+
}
94+
#modeList input[type="checkbox"] {
95+
margin-right: 10px;
96+
cursor: pointer;
97+
transform: scale(1.1); /* Slightly larger checkboxes */
98+
}
99+
#modeList label {
100+
font-weight: normal;
101+
margin-bottom: 0;
102+
cursor: pointer;
103+
flex-grow: 1; /* Allow label to take space */
104+
}
105+
#errorDisplay, #fileInfo {
106+
color: #dc3545;
107+
margin-top: 15px;
108+
font-weight: bold;
109+
min-height: 1.2em; /* Reserve space */
110+
}
111+
#fileInfo {
112+
color: #28a745; /* Green for success */
113+
}
114+
pre {
115+
background-color: #e9ecef;
116+
padding: 15px;
117+
border-radius: 4px;
118+
overflow-x: auto;
119+
white-space: pre-wrap; /* Wrap long lines */
120+
word-wrap: break-word; /* Break words if necessary */
121+
}
122+
code {
123+
font-family: monospace;
124+
font-size: 0.9em;
125+
color: #333;
126+
}
127+
.button-group button {
128+
margin-right: 5px;
129+
background-color: #6c757d;
130+
}
131+
.button-group button:hover {
132+
background-color: #5a6268;
133+
}
134+
135+
</style>
136+
</head>
137+
<body>
138+
139+
<div class="container">
140+
<h1>Roo Code Mode Filter</h1>
141+
142+
<p>Paste the full JSON content of a Roo Code custom_modes.json file into the input area below or select the file from your computer, click load the modes, select the ones you want, and generate the filtered JSON. For detailed explanations of each mode and how they work together, please refer to the main README.md file in the <a href="https://github.com/jezweb/roo-commander" target="_blank" rel="noopener noreferrer">repository</a>.</p>
143+
144+
<div class="input-area">
145+
<h2>Input Method</h2>
146+
<div>
147+
<label for="jsonInput">Option 1: Paste JSON</label>
148+
<textarea id="jsonInput" placeholder="Paste your Roo Code JSON here..."></textarea>
149+
</div>
150+
<div>
151+
<label for="fileInput" class="file-upload-label">Option 2: Upload JSON/TXT File</label>
152+
<input type="file" id="fileInput" accept=".json,.txt">
153+
<div id="fileInfo"></div>
154+
</div>
155+
<button id="loadButton">Load Modes from Input</button>
156+
</div>
157+
158+
<div id="errorDisplay"></div>
159+
160+
<div id="modeListContainer" style="display: none;">
161+
<h2>Select Modes:</h2>
162+
<div class="button-group">
163+
<button id="selectAllButton">Select All</button>
164+
<button id="selectNoneButton">Select None</button>
165+
</div>
166+
<div id="modeList">
167+
<!-- Mode checkboxes will be populated here -->
168+
<p>Load JSON first to see modes.</p>
169+
</div>
170+
<button id="generateButton" style="margin-top: 20px;" disabled>Generate Filtered JSON</button>
171+
</div>
172+
173+
174+
<div id="outputContainer" style="display: none;">
175+
<h2>Filtered JSON Output:</h2>
176+
<label for="jsonOutput">Result:</label>
177+
<textarea id="jsonOutput" readonly placeholder="Filtered JSON will appear here..."></textarea>
178+
</div>
179+
180+
</div>
181+
182+
<script>
183+
const jsonInput = document.getElementById('jsonInput');
184+
const fileInput = document.getElementById('fileInput');
185+
const fileInfo = document.getElementById('fileInfo');
186+
const loadButton = document.getElementById('loadButton');
187+
const modeListDiv = document.getElementById('modeList');
188+
const modeListContainer = document.getElementById('modeListContainer');
189+
const generateButton = document.getElementById('generateButton');
190+
const jsonOutput = document.getElementById('jsonOutput');
191+
const outputContainer = document.getElementById('outputContainer');
192+
const errorDisplay = document.getElementById('errorDisplay');
193+
const selectAllButton = document.getElementById('selectAllButton');
194+
const selectNoneButton = document.getElementById('selectNoneButton');
195+
196+
let allModesData = []; // To store the original mode objects
197+
198+
// --- File Upload Handling ---
199+
fileInput.addEventListener('change', (event) => {
200+
const file = event.target.files[0];
201+
if (!file) {
202+
fileInfo.textContent = '';
203+
return;
204+
}
205+
206+
// Basic type check (though 'accept' attribute is primary)
207+
if (!file.type.includes('json') && file.type !== 'text/plain') {
208+
errorDisplay.textContent = 'Warning: Selected file is not JSON or TXT. Attempting to read anyway.';
209+
} else {
210+
errorDisplay.textContent = ''; // Clear previous warnings/errors
211+
}
212+
213+
214+
const reader = new FileReader();
215+
216+
reader.onload = (e) => {
217+
try {
218+
jsonInput.value = e.target.result; // Put file content into textarea
219+
fileInfo.textContent = `File "${file.name}" loaded into textarea. Click 'Load Modes'.`;
220+
errorDisplay.textContent = ''; // Clear errors on successful load
221+
} catch (err) {
222+
console.error("Error setting textarea value:", err);
223+
errorDisplay.textContent = "Error displaying file content.";
224+
fileInfo.textContent = '';
225+
}
226+
};
227+
228+
reader.onerror = (e) => {
229+
console.error("FileReader Error:", e);
230+
errorDisplay.textContent = `Error reading file: ${file.name}`;
231+
fileInfo.textContent = '';
232+
jsonInput.value = ''; // Clear textarea on read error
233+
};
234+
235+
reader.readAsText(file); // Read the file as text
236+
});
237+
238+
239+
// --- Load Modes Button ---
240+
loadButton.addEventListener('click', () => {
241+
modeListDiv.innerHTML = ''; // Clear previous list
242+
jsonOutput.value = ''; // Clear previous output
243+
outputContainer.style.display = 'none'; // Hide output
244+
errorDisplay.textContent = ''; // Clear errors
245+
generateButton.disabled = true; // Disable generate button
246+
selectAllButton.disabled = true;
247+
selectNoneButton.disabled = true;
248+
modeListContainer.style.display = 'none'; // Hide mode list initially
249+
allModesData = [];
250+
251+
const jsonString = jsonInput.value.trim();
252+
if (!jsonString) {
253+
errorDisplay.textContent = 'Input JSON (from paste or file) cannot be empty.';
254+
return;
255+
}
256+
257+
fileInfo.textContent = ''; // Clear file loaded message
258+
259+
try {
260+
const parsedJson = JSON.parse(jsonString);
261+
262+
if (!parsedJson.customModes || !Array.isArray(parsedJson.customModes)) {
263+
errorDisplay.textContent = 'Invalid JSON format: Missing or invalid "customModes" array.';
264+
modeListDiv.innerHTML = '<p>Invalid JSON format.</p>';
265+
modeListContainer.style.display = 'block'; // Show container even on error
266+
return;
267+
}
268+
269+
allModesData = parsedJson.customModes; // Store the data
270+
271+
if (allModesData.length === 0) {
272+
modeListDiv.innerHTML = '<p>No modes found in the provided JSON.</p>';
273+
modeListContainer.style.display = 'block'; // Show container
274+
return;
275+
}
276+
277+
// Sort modes alphabetically by name for better display
278+
allModesData.sort((a, b) => (a.name || '').localeCompare(b.name || ''));
279+
280+
allModesData.forEach(mode => {
281+
// Basic validation of mode object
282+
if (mode && mode.slug && mode.name) {
283+
const div = document.createElement('div');
284+
const checkbox = document.createElement('input');
285+
checkbox.type = 'checkbox';
286+
checkbox.id = `mode-${mode.slug}`;
287+
checkbox.value = mode.slug; // Use slug as the identifier
288+
checkbox.checked = true; // Default to checked
289+
checkbox.dataset.name = mode.name; // Store name for potential future use
290+
291+
const label = document.createElement('label');
292+
label.htmlFor = `mode-${mode.slug}`;
293+
label.textContent = mode.name; // Display the mode name
294+
295+
div.appendChild(checkbox);
296+
div.appendChild(label);
297+
modeListDiv.appendChild(div);
298+
} else {
299+
console.warn("Skipping invalid mode object:", mode);
300+
}
301+
});
302+
303+
modeListContainer.style.display = 'block'; // Show the list container
304+
generateButton.disabled = false; // Enable generate button
305+
selectAllButton.disabled = false;
306+
selectNoneButton.disabled = false;
307+
308+
} catch (error) {
309+
console.error("JSON Parsing Error:", error);
310+
errorDisplay.textContent = `Error parsing JSON: ${error.message}`;
311+
modeListDiv.innerHTML = '<p>Error loading modes. Check JSON format.</p>';
312+
modeListContainer.style.display = 'block'; // Show container even on error
313+
}
314+
});
315+
316+
// --- Select All / None Buttons ---
317+
selectAllButton.addEventListener('click', () => {
318+
const checkboxes = modeListDiv.querySelectorAll('input[type="checkbox"]');
319+
checkboxes.forEach(checkbox => checkbox.checked = true);
320+
});
321+
322+
selectNoneButton.addEventListener('click', () => {
323+
const checkboxes = modeListDiv.querySelectorAll('input[type="checkbox"]');
324+
checkboxes.forEach(checkbox => checkbox.checked = false);
325+
});
326+
327+
328+
// --- Generate Button ---
329+
generateButton.addEventListener('click', () => {
330+
const selectedSlugs = [];
331+
const checkboxes = modeListDiv.querySelectorAll('input[type="checkbox"]:checked');
332+
333+
checkboxes.forEach(checkbox => {
334+
selectedSlugs.push(checkbox.value);
335+
});
336+
337+
// Filter the original allModesData based on selected slugs
338+
// Maintain original order if needed, or keep sorted order
339+
// Let's filter the already sorted allModesData
340+
const filteredModes = allModesData.filter(mode => mode && mode.slug && selectedSlugs.includes(mode.slug));
341+
342+
// Create the output structure
343+
const outputJson = {
344+
customModes: filteredModes
345+
};
346+
347+
// Display the filtered JSON beautifully formatted
348+
jsonOutput.value = JSON.stringify(outputJson, null, 2); // Pretty print with 2 spaces
349+
outputContainer.style.display = 'block'; // Show output area
350+
errorDisplay.textContent = ''; // Clear any previous errors
351+
});
352+
353+
// Initial state
354+
selectAllButton.disabled = true;
355+
selectNoneButton.disabled = true;
356+
357+
358+
</script>
359+
360+
</body>
14361
</html>

tools/mode_configurator/public/mode_versions.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"date": "2025-04-02",
1212
"summary": "Introduce Secretary mode for note-taking delegation.",
1313
"path": "modes/v2.2.0",
14-
"status": "beta"
14+
"status": "development"
1515
},
1616
{
1717
"version": "v2.1.3",

tools/mode_configurator/public/modes/latest/secretary.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
"slug": "secretary",
33
"name": "📝 Secretary",
44
"roleDefinition": "You are Roo Secretary, responsible for accurately recording information, decisions, and notes into the project journal system based on instructions from other modes.",
5-
"customInstructions": "Your sole focus is to write or append content to specified Markdown files within the `project_journal` directory structure.\\n\\n**Workflow:**\\n\\n1. **Receive Task:** You will receive a task from another mode containing:\\n * The full, correct file path within the `project_journal` structure (e.g., `project_journal/[project_slug]/technical_notes/[delegating_mode_slug]/YYYY-MM-DD_HH-MM-SS_topic.md`).\\n * The complete Markdown content to be written.\\n * (Optional) An instruction to 'append' instead of 'overwrite'. If not specified, default to overwrite.\\n2. **Validate Path:** Briefly check if the path starts with `project_journal/` and ends with `.md`. If not, report an error.\\n3. **Write/Append File:**\\n * If appending, use the appropriate tool/method to append the provided content to the end of the specified file. Ensure a newline is added before the appended content if the file is not empty.\\n * If overwriting (or default), use the `write_to_file` tool with the specified path and the complete provided content.\\n * The `write_to_file` tool will automatically create necessary parent directories.\\n4. **Confirm Completion:** Use `attempt_completion` to report success or failure back to the delegating mode. Example success: \\\"Successfully wrote content to [file_path].\\\" Example failure: \\\"Error: Invalid file path provided: [file_path]. Path must be within project_journal and end with .md.\\\"\\n\\n**Important:**\\n- Do **not** analyze or modify the content you are given.\\n- Do **not** engage in conversation or ask clarifying questions about the content.\\n- Your only tool is `write_to_file` (or equivalent for appending if available/needed later). Focus on executing the write/append operation accurately.\\n- Ensure you use the exact file path provided by the delegating mode.",
5+
"customInstructions": "Your sole focus is to write or append content to specified Markdown files within the `project_journal` directory structure.\\n\\n**Workflow:**\\n\\n1. **Receive Task:** You will receive a task from another mode containing:\\n * The full, correct file path within the `project_journal` structure (e.g., `project_journal/[project_slug]/technical_notes/[delegating_mode_slug]/YYYY-MM-DD_HH-MM-SS_topic.md`).\\n * The complete Markdown content to be written.\\n * (Optional) An instruction to 'append' instead of 'overwrite'. If not specified, default to overwrite.\\n2. **Validate Path:** Briefly check if the path starts with `project_journal/` and ends with `.md`. If not, report an error.\\n3. **Write/Append File:**\\n * Use the `write_to_file` tool with the specified path and the complete provided content. This tool handles both creating new files and overwriting existing ones.\\n * **Crucially, the `write_to_file` tool will automatically create any necessary parent directories if they do not exist. Proceed with the write operation.**\\n * (Note: If specifically asked to append, and an append tool is available, use that. Otherwise, `write_to_file` will overwrite.)\\n4. **Confirm Completion:** Use `attempt_completion` to report success or failure back to the delegating mode. Example success: \\\"Successfully wrote content to [file_path].\\\" Example failure: \\\"Error: Invalid file path provided: [file_path]. Path must be within project_journal and end with .md.\\\"\\n\\n**Important:**\\n- Do **not** analyze or modify the content you are given.\\n- Do **not** engage in conversation or ask clarifying questions about the content.\\n- Your primary tool is `write_to_file`. Focus on executing the write operation accurately.\\n- Ensure you use the exact file path provided by the delegating mode.",
66
"groups": [
7+
"write_to_file",
78
[
89
"edit",
910
{

0 commit comments

Comments
 (0)