claude-code-workflow 6.0.5 → 6.1.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/.claude/agents/action-planning-agent.md +1 -1
- package/.claude/agents/cli-execution-agent.md +269 -269
- package/.claude/agents/cli-explore-agent.md +182 -182
- package/.claude/agents/context-search-agent.md +582 -582
- package/.claude/agents/memory-bridge.md +93 -93
- package/.claude/commands/cli/cli-init.md +1 -1
- package/.claude/commands/memory/docs-full-cli.md +471 -471
- package/.claude/commands/memory/docs-related-cli.md +386 -386
- package/.claude/commands/memory/docs.md +615 -615
- package/.claude/commands/memory/load.md +1 -1
- package/.claude/commands/memory/update-full.md +332 -332
- package/.claude/commands/memory/update-related.md +5 -5
- package/.claude/commands/workflow/init.md +1 -1
- package/.claude/commands/workflow/lite-fix.md +621 -621
- package/.claude/commands/workflow/lite-plan.md +592 -592
- package/.claude/commands/workflow/tools/context-gather.md +434 -434
- package/.claude/commands/workflow/ui-design/generate.md +504 -504
- package/.claude/commands/workflow/ui-design/import-from-code.md +537 -537
- package/.claude/scripts/classify-folders.sh +4 -0
- package/.claude/scripts/convert_tokens_to_css.sh +4 -0
- package/.claude/scripts/detect_changed_modules.sh +5 -1
- package/.claude/scripts/discover-design-files.sh +87 -83
- package/.claude/scripts/generate_module_docs.sh +717 -713
- package/.claude/scripts/get_modules_by_depth.sh +5 -1
- package/.claude/scripts/ui-generate-preview.sh +4 -0
- package/.claude/scripts/ui-instantiate-prototypes.sh +4 -0
- package/.claude/scripts/update_module_claude.sh +4 -0
- package/.claude/skills/command-guide/index/all-commands.json +1 -12
- package/.claude/skills/command-guide/index/by-category.json +1 -12
- package/.claude/skills/command-guide/index/by-use-case.json +1 -12
- package/.claude/skills/command-guide/index/essential-commands.json +1 -12
- package/.claude/skills/command-guide/reference/agents/action-planning-agent.md +127 -71
- package/.claude/skills/command-guide/reference/agents/cli-execution-agent.md +269 -269
- package/.claude/skills/command-guide/reference/agents/cli-explore-agent.md +182 -182
- package/.claude/skills/command-guide/reference/agents/conceptual-planning-agent.md +18 -38
- package/.claude/skills/command-guide/reference/agents/context-search-agent.md +582 -577
- package/.claude/skills/command-guide/reference/agents/memory-bridge.md +93 -93
- package/.claude/skills/command-guide/reference/commands/cli/cli-init.md +1 -1
- package/.claude/skills/command-guide/reference/commands/memory/docs-full-cli.md +471 -471
- package/.claude/skills/command-guide/reference/commands/memory/docs-related-cli.md +386 -386
- package/.claude/skills/command-guide/reference/commands/memory/docs.md +615 -610
- package/.claude/skills/command-guide/reference/commands/memory/load.md +1 -1
- package/.claude/skills/command-guide/reference/commands/memory/update-full.md +332 -332
- package/.claude/skills/command-guide/reference/commands/memory/update-related.md +5 -5
- package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/artifacts.md +299 -451
- package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/auto-parallel.md +14 -37
- package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/synthesis.md +252 -350
- package/.claude/skills/command-guide/reference/commands/workflow/init.md +2 -2
- package/.claude/skills/command-guide/reference/commands/workflow/lite-execute.md +52 -0
- package/.claude/skills/command-guide/reference/commands/workflow/lite-fix.md +621 -602
- package/.claude/skills/command-guide/reference/commands/workflow/lite-plan.md +46 -36
- package/.claude/skills/command-guide/reference/commands/workflow/review-fix.md +18 -58
- package/.claude/skills/command-guide/reference/commands/workflow/review-module-cycle.md +22 -52
- package/.claude/skills/command-guide/reference/commands/workflow/review-session-cycle.md +19 -48
- package/.claude/skills/command-guide/reference/commands/workflow/session/start.md +25 -5
- package/.claude/skills/command-guide/reference/commands/workflow/tdd-plan.md +1 -1
- package/.claude/skills/command-guide/reference/commands/workflow/test-fix-gen.md +7 -7
- package/.claude/skills/command-guide/reference/commands/workflow/tools/context-gather.md +434 -434
- package/.claude/skills/command-guide/reference/commands/workflow/tools/task-generate-agent.md +151 -11
- package/.claude/skills/command-guide/reference/commands/workflow/tools/task-generate-tdd.md +4 -4
- package/.claude/skills/command-guide/reference/commands/workflow/tools/test-task-generate.md +1 -1
- package/.claude/skills/command-guide/reference/commands/workflow/ui-design/generate.md +504 -504
- package/.claude/skills/command-guide/reference/commands/workflow/ui-design/import-from-code.md +537 -537
- package/.claude/workflows/context-search-strategy.md +77 -77
- package/.claude/workflows/tool-strategy.md +90 -71
- package/.claude/workflows/workflow-architecture.md +1 -1
- package/ccw/src/cli.js +7 -0
- package/ccw/src/commands/tool.js +181 -0
- package/ccw/src/core/dashboard-generator.js +18 -3
- package/ccw/src/core/lite-scanner.js +35 -11
- package/ccw/src/core/server.js +531 -46
- package/ccw/src/templates/dashboard-css/01-base.css +161 -0
- package/ccw/src/templates/dashboard-css/02-session.css +726 -0
- package/ccw/src/templates/dashboard-css/03-tasks.css +512 -0
- package/ccw/src/templates/dashboard-css/04-lite-tasks.css +843 -0
- package/ccw/src/templates/dashboard-css/05-context.css +2206 -0
- package/ccw/src/templates/dashboard-css/06-cards.css +1570 -0
- package/ccw/src/templates/dashboard-css/07-managers.css +936 -0
- package/ccw/src/templates/dashboard-css/08-review.css +1266 -0
- package/ccw/src/templates/dashboard-css/09-explorer.css +1397 -0
- package/ccw/src/templates/dashboard-js/components/global-notifications.js +219 -0
- package/ccw/src/templates/dashboard-js/components/hook-manager.js +10 -0
- package/ccw/src/templates/dashboard-js/components/mcp-manager.js +11 -1
- package/ccw/src/templates/dashboard-js/components/navigation.js +11 -5
- package/ccw/src/templates/dashboard-js/components/tabs-context.js +20 -20
- package/ccw/src/templates/dashboard-js/components/tabs-other.js +11 -11
- package/ccw/src/templates/dashboard-js/components/theme.js +29 -1
- package/ccw/src/templates/dashboard-js/main.js +4 -0
- package/ccw/src/templates/dashboard-js/state.js +5 -0
- package/ccw/src/templates/dashboard-js/views/explorer.js +852 -0
- package/ccw/src/templates/dashboard-js/views/home.js +13 -9
- package/ccw/src/templates/dashboard-js/views/hook-manager.js +8 -5
- package/ccw/src/templates/dashboard-js/views/lite-tasks.js +21 -16
- package/ccw/src/templates/dashboard-js/views/mcp-manager.js +90 -19
- package/ccw/src/templates/dashboard-js/views/project-overview.js +15 -11
- package/ccw/src/templates/dashboard-js/views/review-session.js +3 -3
- package/ccw/src/templates/dashboard-js/views/session-detail.js +38 -28
- package/ccw/src/templates/dashboard.html +129 -28
- package/ccw/src/tools/classify-folders.js +204 -0
- package/ccw/src/tools/convert-tokens-to-css.js +250 -0
- package/ccw/src/tools/detect-changed-modules.js +288 -0
- package/ccw/src/tools/discover-design-files.js +134 -0
- package/ccw/src/tools/edit-file.js +266 -0
- package/ccw/src/tools/generate-module-docs.js +416 -0
- package/ccw/src/tools/get-modules-by-depth.js +308 -0
- package/ccw/src/tools/index.js +176 -0
- package/ccw/src/tools/ui-generate-preview.js +327 -0
- package/ccw/src/tools/ui-instantiate-prototypes.js +301 -0
- package/ccw/src/tools/update-module-claude.js +380 -0
- package/package.json +1 -1
- package/.claude/skills/command-guide/reference/commands/workflow/status.md +0 -352
- package/ccw/src/core/server.js.bak +0 -385
- package/ccw/src/core/server_original.bak +0 -385
- package/ccw/src/templates/dashboard.css +0 -8187
- package/ccw/src/templates/dashboard_tailwind.html +0 -42
- package/ccw/src/templates/dashboard_test.html +0 -37
- package/ccw/src/templates/tailwind-base.css +0 -212
|
@@ -0,0 +1,852 @@
|
|
|
1
|
+
// ============================================
|
|
2
|
+
// EXPLORER VIEW
|
|
3
|
+
// ============================================
|
|
4
|
+
// File tree browser with .gitignore filtering and CLAUDE.md update support
|
|
5
|
+
// Split-panel layout: file tree (left) + preview (right)
|
|
6
|
+
|
|
7
|
+
// Explorer state
|
|
8
|
+
let explorerCurrentPath = null;
|
|
9
|
+
let explorerSelectedFile = null;
|
|
10
|
+
let explorerExpandedDirs = new Set();
|
|
11
|
+
|
|
12
|
+
// Task queue for CLAUDE.md updates
|
|
13
|
+
let updateTaskQueue = [];
|
|
14
|
+
let isTaskQueueVisible = false;
|
|
15
|
+
let isTaskRunning = false;
|
|
16
|
+
let defaultCliTool = 'gemini'; // Default CLI tool for updates
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Render the Explorer view
|
|
21
|
+
*/
|
|
22
|
+
async function renderExplorer() {
|
|
23
|
+
const container = document.getElementById('mainContent');
|
|
24
|
+
if (!container) return;
|
|
25
|
+
|
|
26
|
+
// Hide stats grid and search
|
|
27
|
+
const statsGrid = document.getElementById('statsGrid');
|
|
28
|
+
const searchInput = document.getElementById('searchInput');
|
|
29
|
+
if (statsGrid) statsGrid.style.display = 'none';
|
|
30
|
+
if (searchInput) searchInput.parentElement.style.display = 'none';
|
|
31
|
+
|
|
32
|
+
// Initialize explorer path to project path
|
|
33
|
+
explorerCurrentPath = projectPath;
|
|
34
|
+
|
|
35
|
+
container.innerHTML = `
|
|
36
|
+
<div class="explorer-container">
|
|
37
|
+
<!-- Left Panel: File Tree -->
|
|
38
|
+
<div class="explorer-tree-panel">
|
|
39
|
+
<div class="explorer-tree-header">
|
|
40
|
+
<div class="explorer-tree-title">
|
|
41
|
+
<i data-lucide="folder-tree" class="explorer-icon"></i>
|
|
42
|
+
<span class="explorer-title-text">Explorer</span>
|
|
43
|
+
</div>
|
|
44
|
+
<button class="explorer-refresh-btn" onclick="refreshExplorerTree()" title="Refresh">
|
|
45
|
+
<i data-lucide="refresh-cw" class="w-4 h-4"></i>
|
|
46
|
+
</button>
|
|
47
|
+
</div>
|
|
48
|
+
<div class="explorer-tree-content" id="explorerTreeContent">
|
|
49
|
+
<div class="explorer-loading">Loading file tree...</div>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<!-- Right Panel: Preview -->
|
|
54
|
+
<div class="explorer-preview-panel">
|
|
55
|
+
<div class="explorer-preview-header" id="explorerPreviewHeader">
|
|
56
|
+
<span class="preview-filename">Select a file to preview</span>
|
|
57
|
+
</div>
|
|
58
|
+
<div class="explorer-preview-content" id="explorerPreviewContent">
|
|
59
|
+
<div class="explorer-preview-empty">
|
|
60
|
+
<div class="preview-empty-icon"><i data-lucide="file-text" class="w-12 h-12"></i></div>
|
|
61
|
+
<div class="preview-empty-text">Select a file from the tree to preview its contents</div>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<!-- Floating Action Button -->
|
|
68
|
+
<div class="explorer-fab" onclick="toggleTaskQueue()" title="Task Queue">
|
|
69
|
+
<span class="fab-icon"><i data-lucide="list-todo" class="w-5 h-5"></i></span>
|
|
70
|
+
<span class="fab-badge" id="fabBadge">0</span>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<!-- Task Queue Panel -->
|
|
74
|
+
<div class="task-queue-panel" id="taskQueuePanel">
|
|
75
|
+
<div class="task-queue-header">
|
|
76
|
+
<span class="task-queue-title"><i data-lucide="clipboard-list" class="w-4 h-4 inline-block mr-1"></i> Update Tasks</span>
|
|
77
|
+
<button class="task-queue-close" onclick="toggleTaskQueue()">×</button>
|
|
78
|
+
</div>
|
|
79
|
+
<div class="task-queue-toolbar">
|
|
80
|
+
<div class="queue-cli-selector">
|
|
81
|
+
<label>CLI:</label>
|
|
82
|
+
<select id="queueCliTool" onchange="updateDefaultCliTool(this.value)">
|
|
83
|
+
<option value="gemini">Gemini</option>
|
|
84
|
+
<option value="qwen">Qwen</option>
|
|
85
|
+
<option value="codex">Codex</option>
|
|
86
|
+
</select>
|
|
87
|
+
</div>
|
|
88
|
+
<div class="task-queue-actions">
|
|
89
|
+
<button class="queue-action-btn" onclick="openAddTaskModal()" title="Add update task">
|
|
90
|
+
<i data-lucide="plus" class="w-4 h-4"></i>
|
|
91
|
+
</button>
|
|
92
|
+
<button class="queue-action-btn queue-start-btn" onclick="startTaskQueue()" id="startQueueBtn" disabled title="Start all tasks">
|
|
93
|
+
<i data-lucide="play" class="w-4 h-4"></i>
|
|
94
|
+
</button>
|
|
95
|
+
<button class="queue-action-btn queue-clear-btn" onclick="clearCompletedTasks()" title="Clear completed">
|
|
96
|
+
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
|
97
|
+
</button>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
<div class="task-queue-list" id="taskQueueList">
|
|
101
|
+
<div class="task-queue-empty">
|
|
102
|
+
<span>No tasks in queue</span>
|
|
103
|
+
<p>Hover folder and click <i data-lucide="file" class="w-3 h-3 inline"></i> or <i data-lucide="folder" class="w-3 h-3 inline"></i> to add tasks</p>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
`;
|
|
108
|
+
|
|
109
|
+
// Load initial file tree
|
|
110
|
+
await loadExplorerTree(explorerCurrentPath);
|
|
111
|
+
|
|
112
|
+
// Initialize Lucide icons for dynamically rendered content
|
|
113
|
+
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Load and render file tree for a directory
|
|
118
|
+
*/
|
|
119
|
+
async function loadExplorerTree(dirPath) {
|
|
120
|
+
const treeContent = document.getElementById('explorerTreeContent');
|
|
121
|
+
if (!treeContent) return;
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
const response = await fetch(`/api/files?path=${encodeURIComponent(dirPath)}`);
|
|
125
|
+
const data = await response.json();
|
|
126
|
+
|
|
127
|
+
if (data.error) {
|
|
128
|
+
treeContent.innerHTML = `<div class="explorer-error">${escapeHtml(data.error)}</div>`;
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Render root level
|
|
133
|
+
treeContent.innerHTML = renderTreeLevel(data.files, dirPath, 0);
|
|
134
|
+
attachTreeEventListeners();
|
|
135
|
+
|
|
136
|
+
// Initialize Lucide icons for tree items
|
|
137
|
+
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
138
|
+
|
|
139
|
+
} catch (error) {
|
|
140
|
+
treeContent.innerHTML = `<div class="explorer-error">Failed to load: ${escapeHtml(error.message)}</div>`;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Render a level of the file tree
|
|
146
|
+
*/
|
|
147
|
+
function renderTreeLevel(files, parentPath, depth) {
|
|
148
|
+
if (!files || files.length === 0) {
|
|
149
|
+
return `<div class="tree-empty" style="padding-left: ${depth * 16 + 8}px">Empty directory</div>`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return files.map(file => {
|
|
153
|
+
const isExpanded = explorerExpandedDirs.has(file.path);
|
|
154
|
+
const isSelected = explorerSelectedFile === file.path;
|
|
155
|
+
|
|
156
|
+
if (file.type === 'directory') {
|
|
157
|
+
const folderIcon = getFolderIcon(file.name, isExpanded, file.hasClaudeMd);
|
|
158
|
+
const chevronIcon = isExpanded ? '<i data-lucide="chevron-down" class="w-3 h-3"></i>' : '<i data-lucide="chevron-right" class="w-3 h-3"></i>';
|
|
159
|
+
return `
|
|
160
|
+
<div class="tree-item tree-folder ${isExpanded ? 'expanded' : ''} ${file.hasClaudeMd ? 'has-claude-md' : ''}" data-path="${escapeHtml(file.path)}" data-type="directory">
|
|
161
|
+
<div class="tree-item-row ${isSelected ? 'selected' : ''}" style="padding-left: ${depth * 16}px">
|
|
162
|
+
<span class="tree-chevron">${chevronIcon}</span>
|
|
163
|
+
<span class="tree-icon">${folderIcon}</span>
|
|
164
|
+
<span class="tree-name">${escapeHtml(file.name)}</span>
|
|
165
|
+
${file.hasClaudeMd ? `
|
|
166
|
+
<span class="claude-md-badge" title="Contains CLAUDE.md documentation">
|
|
167
|
+
<span class="badge-icon"><i data-lucide="file-check" class="w-3 h-3"></i></span>
|
|
168
|
+
<span class="badge-text">DOC</span>
|
|
169
|
+
</span>
|
|
170
|
+
` : ''}
|
|
171
|
+
<div class="tree-folder-actions">
|
|
172
|
+
<button class="tree-update-btn" onclick="event.stopPropagation(); addFolderToQueue('${escapeHtml(file.path)}', 'single-layer')" title="Update CLAUDE.md (current folder only)">
|
|
173
|
+
<span class="update-icon"><i data-lucide="file" class="w-3.5 h-3.5"></i></span>
|
|
174
|
+
</button>
|
|
175
|
+
<button class="tree-update-btn tree-update-multi" onclick="event.stopPropagation(); addFolderToQueue('${escapeHtml(file.path)}', 'multi-layer')" title="Update CLAUDE.md (with subdirectories)">
|
|
176
|
+
<span class="update-icon"><i data-lucide="folder-tree" class="w-3.5 h-3.5"></i></span>
|
|
177
|
+
</button>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
<div class="tree-children ${isExpanded ? 'show' : ''}" id="children-${btoa(file.path).replace(/[^a-zA-Z0-9]/g, '')}">
|
|
181
|
+
${isExpanded ? '' : ''}
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
`;
|
|
185
|
+
} else {
|
|
186
|
+
const ext = file.name.includes('.') ? file.name.split('.').pop().toLowerCase() : '';
|
|
187
|
+
const fileIcon = getFileIcon(ext);
|
|
188
|
+
// Special highlight for CLAUDE.md files
|
|
189
|
+
const isClaudeMd = file.name === 'CLAUDE.md';
|
|
190
|
+
return `
|
|
191
|
+
<div class="tree-item tree-file ${isSelected ? 'selected' : ''} ${isClaudeMd ? 'is-claude-md' : ''}" data-path="${escapeHtml(file.path)}" data-type="file">
|
|
192
|
+
<div class="tree-item-row" style="padding-left: ${depth * 16}px">
|
|
193
|
+
<span class="tree-chevron-spacer"></span>
|
|
194
|
+
<span class="tree-icon">${isClaudeMd ? '<span class="file-icon file-icon-claude"><i data-lucide="bot" class="w-3 h-3"></i></span>' : fileIcon}</span>
|
|
195
|
+
<span class="tree-name">${escapeHtml(file.name)}</span>
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
`;
|
|
199
|
+
}
|
|
200
|
+
}).join('');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Get file icon based on extension - using colored SVG icons for better distinction
|
|
205
|
+
*/
|
|
206
|
+
function getFileIcon(ext) {
|
|
207
|
+
const iconMap = {
|
|
208
|
+
// JavaScript/TypeScript - distinct colors
|
|
209
|
+
'js': '<span class="file-icon file-icon-js">JS</span>',
|
|
210
|
+
'mjs': '<span class="file-icon file-icon-js">JS</span>',
|
|
211
|
+
'cjs': '<span class="file-icon file-icon-js">JS</span>',
|
|
212
|
+
'jsx': '<span class="file-icon file-icon-jsx">JSX</span>',
|
|
213
|
+
'ts': '<span class="file-icon file-icon-ts">TS</span>',
|
|
214
|
+
'tsx': '<span class="file-icon file-icon-tsx">TSX</span>',
|
|
215
|
+
|
|
216
|
+
// Python
|
|
217
|
+
'py': '<span class="file-icon file-icon-py">PY</span>',
|
|
218
|
+
'pyw': '<span class="file-icon file-icon-py">PY</span>',
|
|
219
|
+
|
|
220
|
+
// Other languages
|
|
221
|
+
'go': '<span class="file-icon file-icon-go">GO</span>',
|
|
222
|
+
'rs': '<span class="file-icon file-icon-rs">RS</span>',
|
|
223
|
+
'java': '<span class="file-icon file-icon-java">JV</span>',
|
|
224
|
+
'rb': '<span class="file-icon file-icon-rb">RB</span>',
|
|
225
|
+
'php': '<span class="file-icon file-icon-php">PHP</span>',
|
|
226
|
+
'c': '<span class="file-icon file-icon-c">C</span>',
|
|
227
|
+
'cpp': '<span class="file-icon file-icon-cpp">C++</span>',
|
|
228
|
+
'h': '<span class="file-icon file-icon-h">H</span>',
|
|
229
|
+
'cs': '<span class="file-icon file-icon-cs">C#</span>',
|
|
230
|
+
'swift': '<span class="file-icon file-icon-swift">SW</span>',
|
|
231
|
+
'kt': '<span class="file-icon file-icon-kt">KT</span>',
|
|
232
|
+
|
|
233
|
+
// Web
|
|
234
|
+
'html': '<span class="file-icon file-icon-html">HTML</span>',
|
|
235
|
+
'htm': '<span class="file-icon file-icon-html">HTML</span>',
|
|
236
|
+
'css': '<span class="file-icon file-icon-css">CSS</span>',
|
|
237
|
+
'scss': '<span class="file-icon file-icon-scss">SCSS</span>',
|
|
238
|
+
'sass': '<span class="file-icon file-icon-scss">SASS</span>',
|
|
239
|
+
'less': '<span class="file-icon file-icon-less">LESS</span>',
|
|
240
|
+
'vue': '<span class="file-icon file-icon-vue">VUE</span>',
|
|
241
|
+
'svelte': '<span class="file-icon file-icon-svelte">SV</span>',
|
|
242
|
+
|
|
243
|
+
// Config/Data
|
|
244
|
+
'json': '<span class="file-icon file-icon-json">{}</span>',
|
|
245
|
+
'yaml': '<span class="file-icon file-icon-yaml">YML</span>',
|
|
246
|
+
'yml': '<span class="file-icon file-icon-yaml">YML</span>',
|
|
247
|
+
'xml': '<span class="file-icon file-icon-xml">XML</span>',
|
|
248
|
+
'toml': '<span class="file-icon file-icon-toml">TML</span>',
|
|
249
|
+
'ini': '<span class="file-icon file-icon-ini">INI</span>',
|
|
250
|
+
'env': '<span class="file-icon file-icon-env">ENV</span>',
|
|
251
|
+
|
|
252
|
+
// Documentation
|
|
253
|
+
'md': '<span class="file-icon file-icon-md">MD</span>',
|
|
254
|
+
'markdown': '<span class="file-icon file-icon-md">MD</span>',
|
|
255
|
+
'txt': '<span class="file-icon file-icon-txt">TXT</span>',
|
|
256
|
+
'log': '<span class="file-icon file-icon-log">LOG</span>',
|
|
257
|
+
|
|
258
|
+
// Shell/Scripts
|
|
259
|
+
'sh': '<span class="file-icon file-icon-sh">SH</span>',
|
|
260
|
+
'bash': '<span class="file-icon file-icon-sh">SH</span>',
|
|
261
|
+
'zsh': '<span class="file-icon file-icon-sh">ZSH</span>',
|
|
262
|
+
'ps1': '<span class="file-icon file-icon-ps1">PS1</span>',
|
|
263
|
+
'bat': '<span class="file-icon file-icon-bat">BAT</span>',
|
|
264
|
+
'cmd': '<span class="file-icon file-icon-bat">CMD</span>',
|
|
265
|
+
|
|
266
|
+
// Database
|
|
267
|
+
'sql': '<span class="file-icon file-icon-sql">SQL</span>',
|
|
268
|
+
'db': '<span class="file-icon file-icon-db">DB</span>',
|
|
269
|
+
|
|
270
|
+
// Docker/Container
|
|
271
|
+
'dockerfile': '<span class="file-icon file-icon-docker"><i data-lucide="container" class="w-3 h-3"></i></span>',
|
|
272
|
+
|
|
273
|
+
// Images
|
|
274
|
+
'png': '<span class="file-icon file-icon-img">IMG</span>',
|
|
275
|
+
'jpg': '<span class="file-icon file-icon-img">IMG</span>',
|
|
276
|
+
'jpeg': '<span class="file-icon file-icon-img">IMG</span>',
|
|
277
|
+
'gif': '<span class="file-icon file-icon-img">GIF</span>',
|
|
278
|
+
'svg': '<span class="file-icon file-icon-svg">SVG</span>',
|
|
279
|
+
'ico': '<span class="file-icon file-icon-img">ICO</span>',
|
|
280
|
+
|
|
281
|
+
// Package
|
|
282
|
+
'lock': '<span class="file-icon file-icon-lock"><i data-lucide="lock" class="w-3 h-3"></i></span>'
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
return iconMap[ext] || '<span class="file-icon file-icon-default"><i data-lucide="file" class="w-3 h-3"></i></span>';
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Get folder icon based on folder name and state
|
|
290
|
+
*/
|
|
291
|
+
function getFolderIcon(name, isExpanded, hasClaudeMd) {
|
|
292
|
+
// Only special icon for .workflow folder
|
|
293
|
+
if (name === '.workflow') {
|
|
294
|
+
return '<i data-lucide="zap" class="w-4 h-4 text-warning"></i>';
|
|
295
|
+
}
|
|
296
|
+
return isExpanded
|
|
297
|
+
? '<i data-lucide="folder-open" class="w-4 h-4 text-warning"></i>'
|
|
298
|
+
: '<i data-lucide="folder" class="w-4 h-4 text-warning"></i>';
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Attach event listeners to tree items
|
|
303
|
+
*/
|
|
304
|
+
function attachTreeEventListeners() {
|
|
305
|
+
// Folder click - toggle expand
|
|
306
|
+
document.querySelectorAll('.tree-folder > .tree-item-row').forEach(row => {
|
|
307
|
+
row.addEventListener('click', async (e) => {
|
|
308
|
+
const folder = row.closest('.tree-folder');
|
|
309
|
+
const path = folder.dataset.path;
|
|
310
|
+
await toggleFolderExpand(path, folder);
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// File click - preview
|
|
315
|
+
document.querySelectorAll('.tree-file').forEach(item => {
|
|
316
|
+
item.addEventListener('click', async () => {
|
|
317
|
+
const path = item.dataset.path;
|
|
318
|
+
await previewFile(path);
|
|
319
|
+
|
|
320
|
+
// Update selection
|
|
321
|
+
document.querySelectorAll('.tree-item-row.selected, .tree-file.selected').forEach(el => {
|
|
322
|
+
el.classList.remove('selected');
|
|
323
|
+
});
|
|
324
|
+
item.classList.add('selected');
|
|
325
|
+
explorerSelectedFile = path;
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Toggle folder expand/collapse
|
|
332
|
+
*/
|
|
333
|
+
async function toggleFolderExpand(path, folderElement) {
|
|
334
|
+
const isExpanded = explorerExpandedDirs.has(path);
|
|
335
|
+
const childrenContainer = folderElement.querySelector('.tree-children');
|
|
336
|
+
const chevron = folderElement.querySelector('.tree-chevron');
|
|
337
|
+
const folderIcon = folderElement.querySelector('.tree-icon');
|
|
338
|
+
|
|
339
|
+
if (isExpanded) {
|
|
340
|
+
// Collapse
|
|
341
|
+
explorerExpandedDirs.delete(path);
|
|
342
|
+
folderElement.classList.remove('expanded');
|
|
343
|
+
childrenContainer.classList.remove('show');
|
|
344
|
+
// Update chevron and folder icon
|
|
345
|
+
if (chevron) chevron.innerHTML = '<i data-lucide="chevron-right" class="w-3 h-3"></i>';
|
|
346
|
+
if (folderIcon && !folderElement.querySelector('[data-lucide="zap"]')) {
|
|
347
|
+
folderIcon.innerHTML = '<i data-lucide="folder" class="w-4 h-4 text-warning"></i>';
|
|
348
|
+
}
|
|
349
|
+
} else {
|
|
350
|
+
// Expand - load children if not loaded
|
|
351
|
+
explorerExpandedDirs.add(path);
|
|
352
|
+
folderElement.classList.add('expanded');
|
|
353
|
+
childrenContainer.classList.add('show');
|
|
354
|
+
// Update chevron and folder icon
|
|
355
|
+
if (chevron) chevron.innerHTML = '<i data-lucide="chevron-down" class="w-3 h-3"></i>';
|
|
356
|
+
if (folderIcon && !folderElement.querySelector('[data-lucide="zap"]')) {
|
|
357
|
+
folderIcon.innerHTML = '<i data-lucide="folder-open" class="w-4 h-4 text-warning"></i>';
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (!childrenContainer.innerHTML.trim()) {
|
|
361
|
+
childrenContainer.innerHTML = '<div class="tree-loading">Loading...</div>';
|
|
362
|
+
|
|
363
|
+
try {
|
|
364
|
+
const response = await fetch(`/api/files?path=${encodeURIComponent(path)}`);
|
|
365
|
+
const data = await response.json();
|
|
366
|
+
|
|
367
|
+
const depth = (path.match(/\//g) || []).length - (explorerCurrentPath.match(/\//g) || []).length + 1;
|
|
368
|
+
childrenContainer.innerHTML = renderTreeLevel(data.files, path, depth);
|
|
369
|
+
attachTreeEventListeners();
|
|
370
|
+
} catch (error) {
|
|
371
|
+
childrenContainer.innerHTML = `<div class="tree-error">Failed to load</div>`;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Reinitialize Lucide icons after DOM changes
|
|
377
|
+
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Preview a file in the right panel
|
|
382
|
+
*/
|
|
383
|
+
async function previewFile(filePath) {
|
|
384
|
+
const previewHeader = document.getElementById('explorerPreviewHeader');
|
|
385
|
+
const previewContent = document.getElementById('explorerPreviewContent');
|
|
386
|
+
|
|
387
|
+
const fileName = filePath.split('/').pop();
|
|
388
|
+
const ext = fileName.includes('.') ? fileName.split('.').pop().toLowerCase() : '';
|
|
389
|
+
const isMarkdown = ext === 'md' || ext === 'markdown';
|
|
390
|
+
|
|
391
|
+
// Build header with tabs for markdown files
|
|
392
|
+
previewHeader.innerHTML = `
|
|
393
|
+
<div class="preview-header-left">
|
|
394
|
+
<span class="preview-filename">${escapeHtml(fileName)}</span>
|
|
395
|
+
<span class="preview-path" title="${escapeHtml(filePath)}">${escapeHtml(filePath)}</span>
|
|
396
|
+
</div>
|
|
397
|
+
${isMarkdown ? `
|
|
398
|
+
<div class="preview-header-tabs" id="previewHeaderTabs">
|
|
399
|
+
<button class="preview-tab active" data-tab="rendered" onclick="switchPreviewTab(this, 'rendered')">Preview</button>
|
|
400
|
+
<button class="preview-tab" data-tab="source" onclick="switchPreviewTab(this, 'source')">Source</button>
|
|
401
|
+
</div>
|
|
402
|
+
` : ''}
|
|
403
|
+
`;
|
|
404
|
+
|
|
405
|
+
previewContent.innerHTML = '<div class="explorer-loading">Loading file...</div>';
|
|
406
|
+
|
|
407
|
+
try {
|
|
408
|
+
const response = await fetch(`/api/file-content?path=${encodeURIComponent(filePath)}`);
|
|
409
|
+
const data = await response.json();
|
|
410
|
+
|
|
411
|
+
if (data.error) {
|
|
412
|
+
previewContent.innerHTML = `<div class="explorer-error">${escapeHtml(data.error)}</div>`;
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (data.isMarkdown) {
|
|
417
|
+
// Render markdown with tabs content (tabs are in header)
|
|
418
|
+
const rendered = marked.parse(data.content);
|
|
419
|
+
previewContent.innerHTML = `
|
|
420
|
+
<div class="preview-tab-content rendered show" data-tab="rendered">
|
|
421
|
+
<div class="markdown-preview prose">${rendered}</div>
|
|
422
|
+
</div>
|
|
423
|
+
<div class="preview-tab-content source" data-tab="source">
|
|
424
|
+
<pre><code class="language-markdown">${escapeHtml(data.content)}</code></pre>
|
|
425
|
+
</div>
|
|
426
|
+
`;
|
|
427
|
+
} else {
|
|
428
|
+
// Render code with syntax highlighting
|
|
429
|
+
previewContent.innerHTML = `
|
|
430
|
+
<div class="preview-info">
|
|
431
|
+
<span class="preview-lang">${data.language}</span>
|
|
432
|
+
<span class="preview-lines">${data.lines} lines</span>
|
|
433
|
+
<span class="preview-size">${formatFileSize(data.size)}</span>
|
|
434
|
+
</div>
|
|
435
|
+
<pre class="preview-code"><code class="language-${data.language}">${escapeHtml(data.content)}</code></pre>
|
|
436
|
+
`;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Apply syntax highlighting if hljs is available
|
|
440
|
+
if (typeof hljs !== 'undefined') {
|
|
441
|
+
previewContent.querySelectorAll('pre code').forEach(block => {
|
|
442
|
+
hljs.highlightElement(block);
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
} catch (error) {
|
|
447
|
+
previewContent.innerHTML = `<div class="explorer-error">Failed to load: ${escapeHtml(error.message)}</div>`;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Switch preview tab (for markdown files)
|
|
453
|
+
*/
|
|
454
|
+
function switchPreviewTab(btn, tabName) {
|
|
455
|
+
const previewPanel = btn.closest('.explorer-preview-panel');
|
|
456
|
+
const contentArea = previewPanel.querySelector('.explorer-preview-content');
|
|
457
|
+
|
|
458
|
+
// Update tab buttons in header
|
|
459
|
+
previewPanel.querySelectorAll('.preview-tab').forEach(t => t.classList.remove('active'));
|
|
460
|
+
btn.classList.add('active');
|
|
461
|
+
|
|
462
|
+
// Update tab content
|
|
463
|
+
contentArea.querySelectorAll('.preview-tab-content').forEach(c => c.classList.remove('show'));
|
|
464
|
+
contentArea.querySelector(`[data-tab="${tabName}"]`).classList.add('show');
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Format file size
|
|
469
|
+
*/
|
|
470
|
+
function formatFileSize(bytes) {
|
|
471
|
+
if (bytes < 1024) return bytes + ' B';
|
|
472
|
+
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
|
473
|
+
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Refresh the explorer tree
|
|
478
|
+
*/
|
|
479
|
+
async function refreshExplorerTree() {
|
|
480
|
+
const btn = document.querySelector('.explorer-refresh-btn');
|
|
481
|
+
if (btn) {
|
|
482
|
+
btn.classList.add('refreshing');
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
explorerExpandedDirs.clear();
|
|
486
|
+
await loadExplorerTree(explorerCurrentPath);
|
|
487
|
+
|
|
488
|
+
if (btn) {
|
|
489
|
+
btn.classList.remove('refreshing');
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Open Update CLAUDE.md modal
|
|
495
|
+
*/
|
|
496
|
+
function openUpdateClaudeMdModal(folderPath) {
|
|
497
|
+
const modal = document.getElementById('updateClaudeMdModal');
|
|
498
|
+
if (!modal) return;
|
|
499
|
+
|
|
500
|
+
// Set folder path
|
|
501
|
+
document.getElementById('claudeMdTargetPath').textContent = folderPath;
|
|
502
|
+
document.getElementById('claudeMdTargetPath').dataset.path = folderPath;
|
|
503
|
+
|
|
504
|
+
// Reset form
|
|
505
|
+
document.getElementById('claudeMdTool').value = 'gemini';
|
|
506
|
+
document.getElementById('claudeMdStrategy').value = 'single-layer';
|
|
507
|
+
|
|
508
|
+
// Show modal
|
|
509
|
+
modal.classList.remove('hidden');
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Close Update CLAUDE.md modal
|
|
514
|
+
*/
|
|
515
|
+
function closeUpdateClaudeMdModal() {
|
|
516
|
+
const modal = document.getElementById('updateClaudeMdModal');
|
|
517
|
+
if (modal) {
|
|
518
|
+
modal.classList.add('hidden');
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Execute Update CLAUDE.md
|
|
524
|
+
*/
|
|
525
|
+
async function executeUpdateClaudeMd() {
|
|
526
|
+
const pathEl = document.getElementById('claudeMdTargetPath');
|
|
527
|
+
const toolSelect = document.getElementById('claudeMdTool');
|
|
528
|
+
const strategySelect = document.getElementById('claudeMdStrategy');
|
|
529
|
+
const executeBtn = document.getElementById('claudeMdExecuteBtn');
|
|
530
|
+
const statusEl = document.getElementById('claudeMdStatus');
|
|
531
|
+
|
|
532
|
+
const path = pathEl.dataset.path;
|
|
533
|
+
const tool = toolSelect.value;
|
|
534
|
+
const strategy = strategySelect.value;
|
|
535
|
+
|
|
536
|
+
// Update UI
|
|
537
|
+
executeBtn.disabled = true;
|
|
538
|
+
executeBtn.textContent = 'Updating...';
|
|
539
|
+
statusEl.innerHTML = '<div class="status-running">⏳ Running update...</div>';
|
|
540
|
+
|
|
541
|
+
try {
|
|
542
|
+
const response = await fetch('/api/update-claude-md', {
|
|
543
|
+
method: 'POST',
|
|
544
|
+
headers: { 'Content-Type': 'application/json' },
|
|
545
|
+
body: JSON.stringify({ path, tool, strategy })
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
const result = await response.json();
|
|
549
|
+
|
|
550
|
+
if (result.success) {
|
|
551
|
+
statusEl.innerHTML = `<div class="status-success"><i data-lucide="check-circle" class="w-4 h-4 inline text-success"></i> ${escapeHtml(result.message)}</div>`;
|
|
552
|
+
// Refresh tree to update CLAUDE.md indicators
|
|
553
|
+
await refreshExplorerTree();
|
|
554
|
+
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
555
|
+
} else {
|
|
556
|
+
statusEl.innerHTML = `<div class="status-error"><i data-lucide="x-circle" class="w-4 h-4 inline text-destructive"></i> ${escapeHtml(result.error || 'Update failed')}</div>`;
|
|
557
|
+
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
} catch (error) {
|
|
561
|
+
statusEl.innerHTML = `<div class="status-error"><i data-lucide="x-circle" class="w-4 h-4 inline text-destructive"></i> ${escapeHtml(error.message)}</div>`;
|
|
562
|
+
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
563
|
+
} finally {
|
|
564
|
+
executeBtn.disabled = false;
|
|
565
|
+
executeBtn.textContent = 'Execute';
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// ============================================
|
|
570
|
+
// TASK QUEUE FUNCTIONS
|
|
571
|
+
// ============================================
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Toggle task queue panel visibility
|
|
575
|
+
*/
|
|
576
|
+
function toggleTaskQueue() {
|
|
577
|
+
isTaskQueueVisible = !isTaskQueueVisible;
|
|
578
|
+
const panel = document.getElementById('taskQueuePanel');
|
|
579
|
+
const fab = document.querySelector('.explorer-fab');
|
|
580
|
+
|
|
581
|
+
if (isTaskQueueVisible) {
|
|
582
|
+
panel.classList.add('show');
|
|
583
|
+
fab.classList.add('active');
|
|
584
|
+
} else {
|
|
585
|
+
panel.classList.remove('show');
|
|
586
|
+
fab.classList.remove('active');
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Update default CLI tool
|
|
592
|
+
*/
|
|
593
|
+
function updateDefaultCliTool(tool) {
|
|
594
|
+
defaultCliTool = tool;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Update the FAB badge count
|
|
599
|
+
*/
|
|
600
|
+
function updateFabBadge() {
|
|
601
|
+
const badge = document.getElementById('fabBadge');
|
|
602
|
+
if (badge) {
|
|
603
|
+
const pendingCount = updateTaskQueue.filter(t => t.status === 'pending' || t.status === 'running').length;
|
|
604
|
+
badge.textContent = pendingCount || '';
|
|
605
|
+
badge.style.display = pendingCount > 0 ? 'flex' : 'none';
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Open add task modal
|
|
611
|
+
*/
|
|
612
|
+
function openAddTaskModal() {
|
|
613
|
+
const modal = document.getElementById('updateClaudeMdModal');
|
|
614
|
+
if (!modal) return;
|
|
615
|
+
|
|
616
|
+
// Set default path to current project
|
|
617
|
+
document.getElementById('claudeMdTargetPath').textContent = explorerCurrentPath;
|
|
618
|
+
document.getElementById('claudeMdTargetPath').dataset.path = explorerCurrentPath;
|
|
619
|
+
|
|
620
|
+
// Reset form
|
|
621
|
+
document.getElementById('claudeMdTool').value = 'gemini';
|
|
622
|
+
document.getElementById('claudeMdStrategy').value = 'single-layer';
|
|
623
|
+
document.getElementById('claudeMdStatus').innerHTML = '';
|
|
624
|
+
|
|
625
|
+
// Change button to "Add to Queue"
|
|
626
|
+
const executeBtn = document.getElementById('claudeMdExecuteBtn');
|
|
627
|
+
executeBtn.textContent = 'Add to Queue';
|
|
628
|
+
executeBtn.onclick = addTaskToQueue;
|
|
629
|
+
|
|
630
|
+
modal.classList.remove('hidden');
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Add task to queue from modal
|
|
635
|
+
*/
|
|
636
|
+
function addTaskToQueue() {
|
|
637
|
+
const pathEl = document.getElementById('claudeMdTargetPath');
|
|
638
|
+
const toolSelect = document.getElementById('claudeMdTool');
|
|
639
|
+
const strategySelect = document.getElementById('claudeMdStrategy');
|
|
640
|
+
|
|
641
|
+
const path = pathEl.dataset.path;
|
|
642
|
+
const tool = toolSelect.value;
|
|
643
|
+
const strategy = strategySelect.value;
|
|
644
|
+
|
|
645
|
+
addUpdateTask(path, tool, strategy);
|
|
646
|
+
closeUpdateClaudeMdModal();
|
|
647
|
+
|
|
648
|
+
// Show task queue
|
|
649
|
+
if (!isTaskQueueVisible) {
|
|
650
|
+
toggleTaskQueue();
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Add a task to the update queue
|
|
656
|
+
*/
|
|
657
|
+
function addUpdateTask(path, tool = 'gemini', strategy = 'single-layer') {
|
|
658
|
+
const task = {
|
|
659
|
+
id: Date.now(),
|
|
660
|
+
path,
|
|
661
|
+
tool,
|
|
662
|
+
strategy,
|
|
663
|
+
status: 'pending', // pending, running, completed, failed
|
|
664
|
+
message: '',
|
|
665
|
+
addedAt: new Date().toISOString()
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
updateTaskQueue.push(task);
|
|
669
|
+
renderTaskQueue();
|
|
670
|
+
updateFabBadge();
|
|
671
|
+
|
|
672
|
+
// Enable start button
|
|
673
|
+
document.getElementById('startQueueBtn').disabled = false;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Add task from folder context (right-click or button)
|
|
678
|
+
*/
|
|
679
|
+
function addFolderToQueue(folderPath, strategy = 'single-layer') {
|
|
680
|
+
// Use the selected CLI tool from the queue panel
|
|
681
|
+
addUpdateTask(folderPath, defaultCliTool, strategy);
|
|
682
|
+
|
|
683
|
+
// Show task queue if not visible
|
|
684
|
+
if (!isTaskQueueVisible) {
|
|
685
|
+
toggleTaskQueue();
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Render the task queue list
|
|
691
|
+
*/
|
|
692
|
+
function renderTaskQueue() {
|
|
693
|
+
const listEl = document.getElementById('taskQueueList');
|
|
694
|
+
|
|
695
|
+
if (updateTaskQueue.length === 0) {
|
|
696
|
+
listEl.innerHTML = `
|
|
697
|
+
<div class="task-queue-empty">
|
|
698
|
+
<span>No tasks in queue</span>
|
|
699
|
+
<p>Right-click a folder or click "Add Task" to queue CLAUDE.md updates</p>
|
|
700
|
+
</div>
|
|
701
|
+
`;
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
listEl.innerHTML = updateTaskQueue.map(task => {
|
|
706
|
+
const folderName = task.path.split('/').pop() || task.path;
|
|
707
|
+
const strategyLabel = task.strategy === 'multi-layer'
|
|
708
|
+
? '<i data-lucide="folder-tree" class="w-3 h-3 inline"></i> With subdirs'
|
|
709
|
+
: '<i data-lucide="file" class="w-3 h-3 inline"></i> Current only';
|
|
710
|
+
const statusIcon = {
|
|
711
|
+
'pending': '<i data-lucide="clock" class="w-4 h-4"></i>',
|
|
712
|
+
'running': '<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i>',
|
|
713
|
+
'completed': '<i data-lucide="check-circle" class="w-4 h-4 text-success"></i>',
|
|
714
|
+
'failed': '<i data-lucide="x-circle" class="w-4 h-4 text-destructive"></i>'
|
|
715
|
+
}[task.status];
|
|
716
|
+
|
|
717
|
+
return `
|
|
718
|
+
<div class="task-queue-item status-${task.status}" data-task-id="${task.id}">
|
|
719
|
+
<div class="task-item-header">
|
|
720
|
+
<span class="task-status-icon">${statusIcon}</span>
|
|
721
|
+
<span class="task-folder-name" title="${escapeHtml(task.path)}">${escapeHtml(folderName)}</span>
|
|
722
|
+
${task.status === 'pending' ? `
|
|
723
|
+
<button class="task-remove-btn" onclick="removeTask(${task.id})" title="Remove">×</button>
|
|
724
|
+
` : ''}
|
|
725
|
+
</div>
|
|
726
|
+
<div class="task-item-meta">
|
|
727
|
+
<span class="task-strategy">${strategyLabel}</span>
|
|
728
|
+
<span class="task-tool">${task.tool}</span>
|
|
729
|
+
</div>
|
|
730
|
+
${task.message ? `<div class="task-item-message">${escapeHtml(task.message)}</div>` : ''}
|
|
731
|
+
</div>
|
|
732
|
+
`;
|
|
733
|
+
}).join('');
|
|
734
|
+
|
|
735
|
+
// Reinitialize Lucide icons
|
|
736
|
+
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Remove a task from queue
|
|
741
|
+
*/
|
|
742
|
+
function removeTask(taskId) {
|
|
743
|
+
updateTaskQueue = updateTaskQueue.filter(t => t.id !== taskId);
|
|
744
|
+
renderTaskQueue();
|
|
745
|
+
updateFabBadge();
|
|
746
|
+
|
|
747
|
+
// Disable start button if no pending tasks
|
|
748
|
+
const hasPending = updateTaskQueue.some(t => t.status === 'pending');
|
|
749
|
+
document.getElementById('startQueueBtn').disabled = !hasPending;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* Clear completed/failed tasks
|
|
754
|
+
*/
|
|
755
|
+
function clearCompletedTasks() {
|
|
756
|
+
updateTaskQueue = updateTaskQueue.filter(t => t.status === 'pending' || t.status === 'running');
|
|
757
|
+
renderTaskQueue();
|
|
758
|
+
updateFabBadge();
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* Execute a single task asynchronously
|
|
763
|
+
*/
|
|
764
|
+
async function executeTask(task) {
|
|
765
|
+
const folderName = task.path.split('/').pop() || task.path;
|
|
766
|
+
|
|
767
|
+
// Update status to running
|
|
768
|
+
task.status = 'running';
|
|
769
|
+
task.message = 'Processing...';
|
|
770
|
+
renderTaskQueue();
|
|
771
|
+
|
|
772
|
+
addGlobalNotification('info', `Processing: ${folderName}`, `Strategy: ${task.strategy}, Tool: ${task.tool}`, 'Explorer');
|
|
773
|
+
|
|
774
|
+
try {
|
|
775
|
+
const response = await fetch('/api/update-claude-md', {
|
|
776
|
+
method: 'POST',
|
|
777
|
+
headers: { 'Content-Type': 'application/json' },
|
|
778
|
+
body: JSON.stringify({
|
|
779
|
+
path: task.path,
|
|
780
|
+
tool: task.tool,
|
|
781
|
+
strategy: task.strategy
|
|
782
|
+
})
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
const result = await response.json();
|
|
786
|
+
|
|
787
|
+
if (result.success) {
|
|
788
|
+
task.status = 'completed';
|
|
789
|
+
task.message = 'Updated successfully';
|
|
790
|
+
addGlobalNotification('success', `Completed: ${folderName}`, result.message, 'Explorer');
|
|
791
|
+
return { success: true };
|
|
792
|
+
} else {
|
|
793
|
+
task.status = 'failed';
|
|
794
|
+
task.message = result.error || 'Update failed';
|
|
795
|
+
addGlobalNotification('error', `Failed: ${folderName}`, result.error || 'Unknown error', 'Explorer');
|
|
796
|
+
return { success: false };
|
|
797
|
+
}
|
|
798
|
+
} catch (error) {
|
|
799
|
+
task.status = 'failed';
|
|
800
|
+
task.message = error.message;
|
|
801
|
+
addGlobalNotification('error', `Error: ${folderName}`, error.message, 'Explorer');
|
|
802
|
+
return { success: false };
|
|
803
|
+
} finally {
|
|
804
|
+
renderTaskQueue();
|
|
805
|
+
updateFabBadge();
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* Start processing task queue - executes tasks asynchronously in parallel
|
|
811
|
+
*/
|
|
812
|
+
async function startTaskQueue() {
|
|
813
|
+
if (isTaskRunning) return;
|
|
814
|
+
|
|
815
|
+
const pendingTasks = updateTaskQueue.filter(t => t.status === 'pending');
|
|
816
|
+
if (pendingTasks.length === 0) return;
|
|
817
|
+
|
|
818
|
+
isTaskRunning = true;
|
|
819
|
+
document.getElementById('startQueueBtn').disabled = true;
|
|
820
|
+
|
|
821
|
+
addGlobalNotification('info', `Starting ${pendingTasks.length} task(s) in parallel...`, null, 'Explorer');
|
|
822
|
+
|
|
823
|
+
// Execute all tasks in parallel
|
|
824
|
+
const results = await Promise.all(pendingTasks.map(task => executeTask(task)));
|
|
825
|
+
|
|
826
|
+
const successCount = results.filter(r => r.success).length;
|
|
827
|
+
const failCount = results.filter(r => !r.success).length;
|
|
828
|
+
|
|
829
|
+
isTaskRunning = false;
|
|
830
|
+
|
|
831
|
+
// Summary notification
|
|
832
|
+
addGlobalNotification(
|
|
833
|
+
failCount === 0 ? 'success' : 'warning',
|
|
834
|
+
`Queue completed: ${successCount} succeeded, ${failCount} failed`,
|
|
835
|
+
null,
|
|
836
|
+
'Explorer'
|
|
837
|
+
);
|
|
838
|
+
|
|
839
|
+
// Force refresh notification list to ensure all notifications are displayed
|
|
840
|
+
if (typeof renderGlobalNotifications === 'function') {
|
|
841
|
+
renderGlobalNotifications();
|
|
842
|
+
updateGlobalNotifBadge();
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// Re-enable start button if there are pending tasks
|
|
846
|
+
const hasPending = updateTaskQueue.some(t => t.status === 'pending');
|
|
847
|
+
document.getElementById('startQueueBtn').disabled = !hasPending;
|
|
848
|
+
|
|
849
|
+
// Refresh tree to show updated CLAUDE.md files
|
|
850
|
+
await refreshExplorerTree();
|
|
851
|
+
}
|
|
852
|
+
|