claude-code-workflow 6.2.4 → 6.2.6
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/ccw/dist/core/lite-scanner-complete.d.ts.map +1 -1
- package/ccw/dist/core/lite-scanner-complete.js +4 -1
- package/ccw/dist/core/lite-scanner-complete.js.map +1 -1
- package/ccw/dist/core/lite-scanner.d.ts.map +1 -1
- package/ccw/dist/core/lite-scanner.js +4 -1
- package/ccw/dist/core/lite-scanner.js.map +1 -1
- package/ccw/dist/core/routes/claude-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/claude-routes.js +3 -5
- package/ccw/dist/core/routes/claude-routes.js.map +1 -1
- package/ccw/dist/core/routes/cli-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/cli-routes.js +2 -1
- package/ccw/dist/core/routes/cli-routes.js.map +1 -1
- package/ccw/dist/core/routes/codexlens-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/codexlens-routes.js +31 -6
- package/ccw/dist/core/routes/codexlens-routes.js.map +1 -1
- package/ccw/dist/core/routes/rules-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/rules-routes.js +4 -3
- package/ccw/dist/core/routes/rules-routes.js.map +1 -1
- package/ccw/dist/core/routes/skills-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/skills-routes.js +124 -6
- package/ccw/dist/core/routes/skills-routes.js.map +1 -1
- package/ccw/dist/tools/cli-executor.d.ts +4 -1
- package/ccw/dist/tools/cli-executor.d.ts.map +1 -1
- package/ccw/dist/tools/cli-executor.js +54 -2
- package/ccw/dist/tools/cli-executor.js.map +1 -1
- package/ccw/dist/tools/codex-lens.d.ts +20 -3
- package/ccw/dist/tools/codex-lens.d.ts.map +1 -1
- package/ccw/dist/tools/codex-lens.js +166 -37
- package/ccw/dist/tools/codex-lens.js.map +1 -1
- package/ccw/package.json +1 -1
- package/ccw/src/core/lite-scanner-complete.ts +5 -1
- package/ccw/src/core/lite-scanner.ts +5 -1
- package/ccw/src/core/routes/claude-routes.ts +3 -5
- package/ccw/src/core/routes/cli-routes.ts +2 -1
- package/ccw/src/core/routes/codexlens-routes.ts +34 -6
- package/ccw/src/core/routes/rules-routes.ts +4 -3
- package/ccw/src/core/routes/skills-routes.ts +144 -6
- package/ccw/src/templates/dashboard-js/components/mcp-manager.js +7 -12
- package/ccw/src/templates/dashboard-js/i18n.js +167 -5
- package/ccw/src/templates/dashboard-js/views/claude-manager.js +18 -4
- package/ccw/src/templates/dashboard-js/views/cli-manager.js +5 -3
- package/ccw/src/templates/dashboard-js/views/codexlens-manager.js +790 -25
- package/ccw/src/templates/dashboard-js/views/rules-manager.js +35 -6
- package/ccw/src/templates/dashboard-js/views/skills-manager.js +385 -21
- package/ccw/src/tools/cli-executor.ts +70 -2
- package/ccw/src/tools/codex-lens.ts +183 -35
- package/codex-lens/pyproject.toml +66 -48
- package/codex-lens/src/codexlens/__pycache__/config.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/__pycache__/embedding_manager.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/__pycache__/model_manager.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/embedding_manager.py +3 -3
- package/codex-lens/src/codexlens/cli/model_manager.py +24 -2
- package/codex-lens/src/codexlens/search/__pycache__/hybrid_search.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/hybrid_search.py +313 -313
- package/codex-lens/src/codexlens/semantic/__init__.py +76 -39
- package/codex-lens/src/codexlens/semantic/__pycache__/__init__.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/embedder.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/gpu_support.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/ollama_backend.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/embedder.py +244 -185
- package/codex-lens/src/codexlens/semantic/gpu_support.py +192 -0
- package/package.json +1 -1
|
@@ -638,9 +638,26 @@ function addRulePath() {
|
|
|
638
638
|
|
|
639
639
|
function removeRulePath(index) {
|
|
640
640
|
ruleCreateState.paths.splice(index, 1);
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
641
|
+
|
|
642
|
+
// Re-render paths list without closing modal
|
|
643
|
+
const pathsList = document.getElementById('rulePathsList');
|
|
644
|
+
if (pathsList) {
|
|
645
|
+
pathsList.innerHTML = ruleCreateState.paths.map((path, idx) => `
|
|
646
|
+
<div class="flex gap-2">
|
|
647
|
+
<input type="text" class="rule-path-input flex-1 px-3 py-2 bg-background border border-border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-primary"
|
|
648
|
+
placeholder="src/**/*.ts"
|
|
649
|
+
value="${path}"
|
|
650
|
+
data-index="${idx}">
|
|
651
|
+
${idx > 0 ? `
|
|
652
|
+
<button class="px-3 py-2 text-destructive hover:bg-destructive/10 rounded-lg transition-colors"
|
|
653
|
+
onclick="removeRulePath(${idx})">
|
|
654
|
+
<i data-lucide="x" class="w-4 h-4"></i>
|
|
655
|
+
</button>
|
|
656
|
+
` : ''}
|
|
657
|
+
</div>
|
|
658
|
+
`).join('');
|
|
659
|
+
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
660
|
+
}
|
|
644
661
|
}
|
|
645
662
|
|
|
646
663
|
function switchRuleCreateMode(mode) {
|
|
@@ -674,9 +691,21 @@ function switchRuleCreateMode(mode) {
|
|
|
674
691
|
if (contentSection) contentSection.style.display = 'block';
|
|
675
692
|
}
|
|
676
693
|
|
|
677
|
-
//
|
|
678
|
-
|
|
679
|
-
|
|
694
|
+
// Update mode button styles without re-rendering
|
|
695
|
+
const modeButtons = document.querySelectorAll('#ruleCreateModal .mode-btn');
|
|
696
|
+
modeButtons.forEach(btn => {
|
|
697
|
+
const btnText = btn.querySelector('.font-medium')?.textContent || '';
|
|
698
|
+
const isInput = btnText.includes(t('rules.manualInput'));
|
|
699
|
+
const isCliGenerate = btnText.includes(t('rules.cliGenerate'));
|
|
700
|
+
|
|
701
|
+
if ((isInput && mode === 'input') || (isCliGenerate && mode === 'cli-generate')) {
|
|
702
|
+
btn.classList.remove('border-border', 'hover:border-primary/50');
|
|
703
|
+
btn.classList.add('border-primary', 'bg-primary/10');
|
|
704
|
+
} else {
|
|
705
|
+
btn.classList.remove('border-primary', 'bg-primary/10');
|
|
706
|
+
btn.classList.add('border-border', 'hover:border-primary/50');
|
|
707
|
+
}
|
|
708
|
+
});
|
|
680
709
|
}
|
|
681
710
|
|
|
682
711
|
function switchRuleGenerationType(type) {
|
|
@@ -153,10 +153,11 @@ function renderSkillCard(skill, location) {
|
|
|
153
153
|
const locationIcon = location === 'project' ? 'folder' : 'user';
|
|
154
154
|
const locationClass = location === 'project' ? 'text-primary' : 'text-indigo';
|
|
155
155
|
const locationBg = location === 'project' ? 'bg-primary/10' : 'bg-indigo/10';
|
|
156
|
+
const folderName = skill.folderName || skill.name;
|
|
156
157
|
|
|
157
158
|
return `
|
|
158
159
|
<div class="skill-card bg-card border border-border rounded-lg p-4 hover:shadow-md transition-all cursor-pointer"
|
|
159
|
-
onclick="showSkillDetail('${escapeHtml(
|
|
160
|
+
onclick="showSkillDetail('${escapeHtml(folderName)}', '${location}')">
|
|
160
161
|
<div class="flex items-start justify-between mb-3">
|
|
161
162
|
<div class="flex items-center gap-3">
|
|
162
163
|
<div class="w-10 h-10 ${locationBg} rounded-lg flex items-center justify-center">
|
|
@@ -198,6 +199,7 @@ function renderSkillCard(skill, location) {
|
|
|
198
199
|
function renderSkillDetailPanel(skill) {
|
|
199
200
|
const hasAllowedTools = skill.allowedTools && skill.allowedTools.length > 0;
|
|
200
201
|
const hasSupportingFiles = skill.supportingFiles && skill.supportingFiles.length > 0;
|
|
202
|
+
const folderName = skill.folderName || skill.name;
|
|
201
203
|
|
|
202
204
|
return `
|
|
203
205
|
<div class="skill-detail-panel fixed top-0 right-0 w-1/2 max-w-xl h-full bg-card border-l border-border shadow-lg z-50 flex flex-col">
|
|
@@ -243,20 +245,54 @@ function renderSkillDetailPanel(skill) {
|
|
|
243
245
|
</div>
|
|
244
246
|
` : ''}
|
|
245
247
|
|
|
246
|
-
<!-- Supporting Files -->
|
|
247
|
-
|
|
248
|
-
<
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
248
|
+
<!-- Skill Files (SKILL.md + Supporting Files) -->
|
|
249
|
+
<div>
|
|
250
|
+
<h4 class="text-sm font-semibold text-foreground mb-2">${t('skills.files') || 'Files'}</h4>
|
|
251
|
+
<div class="space-y-2">
|
|
252
|
+
<!-- SKILL.md (main file) -->
|
|
253
|
+
<div class="flex items-center justify-between p-2 bg-primary/5 border border-primary/20 rounded-lg cursor-pointer hover:bg-primary/10 transition-colors"
|
|
254
|
+
onclick="viewSkillFile('${escapeHtml(folderName)}', 'SKILL.md', '${skill.location}')">
|
|
255
|
+
<div class="flex items-center gap-2">
|
|
256
|
+
<i data-lucide="file-text" class="w-4 h-4 text-primary"></i>
|
|
257
|
+
<span class="text-sm font-mono text-foreground font-medium">SKILL.md</span>
|
|
258
|
+
</div>
|
|
259
|
+
<div class="flex items-center gap-1">
|
|
260
|
+
<button class="p-1 text-primary hover:bg-primary/20 rounded transition-colors"
|
|
261
|
+
onclick="event.stopPropagation(); editSkillFile('${escapeHtml(folderName)}', 'SKILL.md', '${skill.location}')"
|
|
262
|
+
title="${t('common.edit')}">
|
|
263
|
+
<i data-lucide="edit-2" class="w-3.5 h-3.5"></i>
|
|
264
|
+
</button>
|
|
265
|
+
</div>
|
|
266
|
+
</div>
|
|
267
|
+
${hasSupportingFiles ? skill.supportingFiles.map(file => {
|
|
268
|
+
const isDir = file.endsWith('/');
|
|
269
|
+
const dirName = isDir ? file.slice(0, -1) : file;
|
|
270
|
+
return `
|
|
271
|
+
<!-- Supporting file: ${escapeHtml(file)} -->
|
|
272
|
+
<div class="skill-file-item" data-path="${escapeHtml(dirName)}">
|
|
273
|
+
<div class="flex items-center justify-between p-2 bg-muted/50 rounded-lg cursor-pointer hover:bg-muted transition-colors"
|
|
274
|
+
onclick="${isDir ? `toggleSkillFolder('${escapeHtml(folderName)}', '${escapeHtml(dirName)}', '${skill.location}', this)` : `viewSkillFile('${escapeHtml(folderName)}', '${escapeHtml(file)}', '${skill.location}')`}">
|
|
275
|
+
<div class="flex items-center gap-2">
|
|
276
|
+
<i data-lucide="${isDir ? 'folder' : 'file-text'}" class="w-4 h-4 text-muted-foreground ${isDir ? 'folder-icon' : ''}"></i>
|
|
277
|
+
<span class="text-sm font-mono text-foreground">${escapeHtml(isDir ? dirName : file)}</span>
|
|
278
|
+
${isDir ? '<i data-lucide="chevron-right" class="w-3 h-3 text-muted-foreground folder-chevron transition-transform"></i>' : ''}
|
|
255
279
|
</div>
|
|
256
|
-
|
|
280
|
+
${!isDir ? `
|
|
281
|
+
<div class="flex items-center gap-1">
|
|
282
|
+
<button class="p-1 text-muted-foreground hover:text-foreground hover:bg-muted rounded transition-colors"
|
|
283
|
+
onclick="event.stopPropagation(); editSkillFile('${escapeHtml(folderName)}', '${escapeHtml(file)}', '${skill.location}')"
|
|
284
|
+
title="${t('common.edit')}">
|
|
285
|
+
<i data-lucide="edit-2" class="w-3.5 h-3.5"></i>
|
|
286
|
+
</button>
|
|
287
|
+
</div>
|
|
288
|
+
` : ''}
|
|
289
|
+
</div>
|
|
290
|
+
<div class="folder-contents hidden ml-4 mt-1 space-y-1"></div>
|
|
257
291
|
</div>
|
|
292
|
+
`;
|
|
293
|
+
}).join('') : ''}
|
|
258
294
|
</div>
|
|
259
|
-
|
|
295
|
+
</div>
|
|
260
296
|
|
|
261
297
|
<!-- Path -->
|
|
262
298
|
<div>
|
|
@@ -269,12 +305,12 @@ function renderSkillDetailPanel(skill) {
|
|
|
269
305
|
<!-- Actions -->
|
|
270
306
|
<div class="px-5 py-4 border-t border-border flex justify-between">
|
|
271
307
|
<button class="px-4 py-2 text-sm text-destructive hover:bg-destructive/10 rounded-lg transition-colors flex items-center gap-2"
|
|
272
|
-
onclick="deleteSkill('${escapeHtml(
|
|
308
|
+
onclick="deleteSkill('${escapeHtml(folderName)}', '${skill.location}')">
|
|
273
309
|
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
|
274
310
|
${t('common.delete')}
|
|
275
311
|
</button>
|
|
276
312
|
<button class="px-4 py-2 text-sm bg-primary text-primary-foreground rounded-lg hover:opacity-90 transition-opacity flex items-center gap-2"
|
|
277
|
-
onclick="editSkill('${escapeHtml(
|
|
313
|
+
onclick="editSkill('${escapeHtml(folderName)}', '${skill.location}')">
|
|
278
314
|
<i data-lucide="edit" class="w-4 h-4"></i>
|
|
279
315
|
${t('common.edit')}
|
|
280
316
|
</button>
|
|
@@ -525,7 +561,7 @@ function openSkillCreateModal() {
|
|
|
525
561
|
</div>
|
|
526
562
|
|
|
527
563
|
<!-- Footer -->
|
|
528
|
-
<div class="flex items-center justify-end gap-3 px-6 py-4 border-t border-border">
|
|
564
|
+
<div id="skillModalFooter" class="flex items-center justify-end gap-3 px-6 py-4 border-t border-border">
|
|
529
565
|
<button class="px-4 py-2 text-sm text-muted-foreground hover:text-foreground transition-colors"
|
|
530
566
|
onclick="closeSkillCreateModal()">
|
|
531
567
|
${t('common.cancel')}
|
|
@@ -588,16 +624,76 @@ function selectSkillLocation(location) {
|
|
|
588
624
|
|
|
589
625
|
function switchSkillCreateMode(mode) {
|
|
590
626
|
skillCreateState.mode = mode;
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
627
|
+
|
|
628
|
+
// Toggle visibility of mode sections
|
|
629
|
+
const importSection = document.getElementById('skillImportMode');
|
|
630
|
+
const cliGenerateSection = document.getElementById('skillCliGenerateMode');
|
|
631
|
+
const footerContainer = document.getElementById('skillModalFooter');
|
|
632
|
+
|
|
633
|
+
if (importSection) importSection.style.display = mode === 'import' ? 'block' : 'none';
|
|
634
|
+
if (cliGenerateSection) cliGenerateSection.style.display = mode === 'cli-generate' ? 'block' : 'none';
|
|
635
|
+
|
|
636
|
+
// Update mode button styles
|
|
637
|
+
const modeButtons = document.querySelectorAll('#skillCreateModal .mode-btn');
|
|
638
|
+
modeButtons.forEach(btn => {
|
|
639
|
+
const btnText = btn.querySelector('.font-medium')?.textContent || '';
|
|
640
|
+
const isImport = btnText.includes(t('skills.importFolder'));
|
|
641
|
+
const isCliGenerate = btnText.includes(t('skills.cliGenerate'));
|
|
642
|
+
|
|
643
|
+
if ((isImport && mode === 'import') || (isCliGenerate && mode === 'cli-generate')) {
|
|
644
|
+
btn.classList.remove('border-border', 'hover:border-primary/50');
|
|
645
|
+
btn.classList.add('border-primary', 'bg-primary/10');
|
|
646
|
+
} else {
|
|
647
|
+
btn.classList.remove('border-primary', 'bg-primary/10');
|
|
648
|
+
btn.classList.add('border-border', 'hover:border-primary/50');
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
// Update footer buttons
|
|
653
|
+
if (footerContainer) {
|
|
654
|
+
if (mode === 'import') {
|
|
655
|
+
footerContainer.innerHTML = `
|
|
656
|
+
<button class="px-4 py-2 text-sm text-muted-foreground hover:text-foreground transition-colors"
|
|
657
|
+
onclick="closeSkillCreateModal()">
|
|
658
|
+
${t('common.cancel')}
|
|
659
|
+
</button>
|
|
660
|
+
<button class="px-4 py-2 text-sm bg-primary/10 text-primary rounded-lg hover:bg-primary/20 transition-colors"
|
|
661
|
+
onclick="validateSkillImport()">
|
|
662
|
+
${t('skills.validate')}
|
|
663
|
+
</button>
|
|
664
|
+
<button class="px-4 py-2 text-sm bg-primary text-primary-foreground rounded-lg hover:opacity-90 transition-opacity"
|
|
665
|
+
onclick="createSkill()">
|
|
666
|
+
${t('skills.import')}
|
|
667
|
+
</button>
|
|
668
|
+
`;
|
|
669
|
+
} else {
|
|
670
|
+
footerContainer.innerHTML = `
|
|
671
|
+
<button class="px-4 py-2 text-sm text-muted-foreground hover:text-foreground transition-colors"
|
|
672
|
+
onclick="closeSkillCreateModal()">
|
|
673
|
+
${t('common.cancel')}
|
|
674
|
+
</button>
|
|
675
|
+
<button class="px-4 py-2 text-sm bg-primary text-primary-foreground rounded-lg hover:opacity-90 transition-opacity flex items-center gap-2"
|
|
676
|
+
onclick="createSkill()">
|
|
677
|
+
<i data-lucide="sparkles" class="w-4 h-4"></i>
|
|
678
|
+
${t('skills.generate')}
|
|
679
|
+
</button>
|
|
680
|
+
`;
|
|
681
|
+
}
|
|
682
|
+
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
683
|
+
}
|
|
594
684
|
}
|
|
595
685
|
|
|
596
686
|
function switchSkillGenerationType(type) {
|
|
597
687
|
skillCreateState.generationType = type;
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
688
|
+
|
|
689
|
+
// Toggle visibility of description area
|
|
690
|
+
const descriptionArea = document.getElementById('skillDescriptionArea');
|
|
691
|
+
if (descriptionArea) {
|
|
692
|
+
descriptionArea.style.display = type === 'description' ? 'block' : 'none';
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// Update generation type button styles (only the description button is active, template is disabled)
|
|
696
|
+
// No need to update button styles since template button is disabled
|
|
601
697
|
}
|
|
602
698
|
|
|
603
699
|
function browseSkillFolder() {
|
|
@@ -817,3 +913,271 @@ async function createSkill() {
|
|
|
817
913
|
}
|
|
818
914
|
}
|
|
819
915
|
}
|
|
916
|
+
|
|
917
|
+
|
|
918
|
+
// ========== Skill File View/Edit Functions ==========
|
|
919
|
+
|
|
920
|
+
var skillFileEditorState = {
|
|
921
|
+
skillName: '',
|
|
922
|
+
fileName: '',
|
|
923
|
+
location: '',
|
|
924
|
+
content: '',
|
|
925
|
+
isEditing: false
|
|
926
|
+
};
|
|
927
|
+
|
|
928
|
+
async function viewSkillFile(skillName, fileName, location) {
|
|
929
|
+
try {
|
|
930
|
+
const response = await fetch(
|
|
931
|
+
'/api/skills/' + encodeURIComponent(skillName) + '/file?filename=' + encodeURIComponent(fileName) +
|
|
932
|
+
'&location=' + location + '&path=' + encodeURIComponent(projectPath)
|
|
933
|
+
);
|
|
934
|
+
|
|
935
|
+
if (!response.ok) {
|
|
936
|
+
const error = await response.json();
|
|
937
|
+
throw new Error(error.error || 'Failed to load file');
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
const data = await response.json();
|
|
941
|
+
|
|
942
|
+
skillFileEditorState = {
|
|
943
|
+
skillName,
|
|
944
|
+
fileName,
|
|
945
|
+
location,
|
|
946
|
+
content: data.content,
|
|
947
|
+
isEditing: false
|
|
948
|
+
};
|
|
949
|
+
|
|
950
|
+
renderSkillFileModal();
|
|
951
|
+
} catch (err) {
|
|
952
|
+
console.error('Failed to load skill file:', err);
|
|
953
|
+
if (window.showToast) {
|
|
954
|
+
showToast(err.message || t('skills.fileLoadError') || 'Failed to load file', 'error');
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
function editSkillFile(skillName, fileName, location) {
|
|
960
|
+
viewSkillFile(skillName, fileName, location).then(() => {
|
|
961
|
+
skillFileEditorState.isEditing = true;
|
|
962
|
+
renderSkillFileModal();
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
function renderSkillFileModal() {
|
|
967
|
+
// Remove existing modal if any
|
|
968
|
+
const existingModal = document.getElementById('skillFileModal');
|
|
969
|
+
if (existingModal) existingModal.remove();
|
|
970
|
+
|
|
971
|
+
const { skillName, fileName, content, isEditing, location } = skillFileEditorState;
|
|
972
|
+
|
|
973
|
+
const modalHtml = `
|
|
974
|
+
<div class="modal-overlay fixed inset-0 bg-black/50 z-[60] flex items-center justify-center" onclick="closeSkillFileModal(event)">
|
|
975
|
+
<div class="modal-dialog bg-card rounded-lg shadow-lg w-full max-w-4xl max-h-[90vh] mx-4 flex flex-col" onclick="event.stopPropagation()">
|
|
976
|
+
<!-- Header -->
|
|
977
|
+
<div class="flex items-center justify-between px-6 py-4 border-b border-border">
|
|
978
|
+
<div class="flex items-center gap-3">
|
|
979
|
+
<i data-lucide="file-text" class="w-5 h-5 text-primary"></i>
|
|
980
|
+
<div>
|
|
981
|
+
<h3 class="text-lg font-semibold text-foreground font-mono">${escapeHtml(fileName)}</h3>
|
|
982
|
+
<p class="text-xs text-muted-foreground">${escapeHtml(skillName)} / ${location}</p>
|
|
983
|
+
</div>
|
|
984
|
+
</div>
|
|
985
|
+
<div class="flex items-center gap-2">
|
|
986
|
+
${!isEditing ? `
|
|
987
|
+
<button class="px-3 py-1.5 text-sm bg-primary/10 text-primary rounded-lg hover:bg-primary/20 transition-colors flex items-center gap-1"
|
|
988
|
+
onclick="toggleSkillFileEdit()">
|
|
989
|
+
<i data-lucide="edit-2" class="w-4 h-4"></i>
|
|
990
|
+
${t('common.edit')}
|
|
991
|
+
</button>
|
|
992
|
+
` : ''}
|
|
993
|
+
<button class="w-8 h-8 flex items-center justify-center text-xl text-muted-foreground hover:text-foreground hover:bg-hover rounded"
|
|
994
|
+
onclick="closeSkillFileModal()">×</button>
|
|
995
|
+
</div>
|
|
996
|
+
</div>
|
|
997
|
+
|
|
998
|
+
<!-- Content -->
|
|
999
|
+
<div class="flex-1 overflow-hidden p-4">
|
|
1000
|
+
${isEditing ? `
|
|
1001
|
+
<textarea id="skillFileContent"
|
|
1002
|
+
class="w-full h-full min-h-[400px] px-4 py-3 bg-background border border-border rounded-lg text-sm font-mono focus:outline-none focus:ring-2 focus:ring-primary resize-none"
|
|
1003
|
+
spellcheck="false">${escapeHtml(content)}</textarea>
|
|
1004
|
+
` : `
|
|
1005
|
+
<div class="w-full h-full min-h-[400px] overflow-auto">
|
|
1006
|
+
<pre class="px-4 py-3 bg-muted/30 rounded-lg text-sm font-mono whitespace-pre-wrap break-words">${escapeHtml(content)}</pre>
|
|
1007
|
+
</div>
|
|
1008
|
+
`}
|
|
1009
|
+
</div>
|
|
1010
|
+
|
|
1011
|
+
<!-- Footer -->
|
|
1012
|
+
${isEditing ? `
|
|
1013
|
+
<div class="flex items-center justify-end gap-3 px-6 py-4 border-t border-border">
|
|
1014
|
+
<button class="px-4 py-2 text-sm text-muted-foreground hover:text-foreground transition-colors"
|
|
1015
|
+
onclick="cancelSkillFileEdit()">
|
|
1016
|
+
${t('common.cancel')}
|
|
1017
|
+
</button>
|
|
1018
|
+
<button class="px-4 py-2 text-sm bg-primary text-primary-foreground rounded-lg hover:opacity-90 transition-opacity flex items-center gap-2"
|
|
1019
|
+
onclick="saveSkillFile()">
|
|
1020
|
+
<i data-lucide="save" class="w-4 h-4"></i>
|
|
1021
|
+
${t('common.save')}
|
|
1022
|
+
</button>
|
|
1023
|
+
</div>
|
|
1024
|
+
` : ''}
|
|
1025
|
+
</div>
|
|
1026
|
+
</div>
|
|
1027
|
+
`;
|
|
1028
|
+
|
|
1029
|
+
const modalContainer = document.createElement('div');
|
|
1030
|
+
modalContainer.id = 'skillFileModal';
|
|
1031
|
+
modalContainer.innerHTML = modalHtml;
|
|
1032
|
+
document.body.appendChild(modalContainer);
|
|
1033
|
+
|
|
1034
|
+
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
function closeSkillFileModal(event) {
|
|
1038
|
+
if (event && event.target !== event.currentTarget) return;
|
|
1039
|
+
const modal = document.getElementById('skillFileModal');
|
|
1040
|
+
if (modal) modal.remove();
|
|
1041
|
+
skillFileEditorState = { skillName: '', fileName: '', location: '', content: '', isEditing: false };
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
function toggleSkillFileEdit() {
|
|
1045
|
+
skillFileEditorState.isEditing = true;
|
|
1046
|
+
renderSkillFileModal();
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
function cancelSkillFileEdit() {
|
|
1050
|
+
skillFileEditorState.isEditing = false;
|
|
1051
|
+
renderSkillFileModal();
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
async function saveSkillFile() {
|
|
1055
|
+
const contentTextarea = document.getElementById('skillFileContent');
|
|
1056
|
+
if (!contentTextarea) return;
|
|
1057
|
+
|
|
1058
|
+
const newContent = contentTextarea.value;
|
|
1059
|
+
const { skillName, fileName, location } = skillFileEditorState;
|
|
1060
|
+
|
|
1061
|
+
try {
|
|
1062
|
+
const response = await fetch('/api/skills/' + encodeURIComponent(skillName) + '/file', {
|
|
1063
|
+
method: 'POST',
|
|
1064
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1065
|
+
body: JSON.stringify({
|
|
1066
|
+
fileName,
|
|
1067
|
+
content: newContent,
|
|
1068
|
+
location,
|
|
1069
|
+
projectPath
|
|
1070
|
+
})
|
|
1071
|
+
});
|
|
1072
|
+
|
|
1073
|
+
if (!response.ok) {
|
|
1074
|
+
const error = await response.json();
|
|
1075
|
+
throw new Error(error.error || 'Failed to save file');
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// Update state and close edit mode
|
|
1079
|
+
skillFileEditorState.content = newContent;
|
|
1080
|
+
skillFileEditorState.isEditing = false;
|
|
1081
|
+
renderSkillFileModal();
|
|
1082
|
+
|
|
1083
|
+
// Refresh skill detail if SKILL.md was edited
|
|
1084
|
+
if (fileName === 'SKILL.md') {
|
|
1085
|
+
await loadSkillsData();
|
|
1086
|
+
// Reload current skill detail
|
|
1087
|
+
if (selectedSkill) {
|
|
1088
|
+
await showSkillDetail(skillName, location);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
if (window.showToast) {
|
|
1093
|
+
showToast(t('skills.fileSaved') || 'File saved successfully', 'success');
|
|
1094
|
+
}
|
|
1095
|
+
} catch (err) {
|
|
1096
|
+
console.error('Failed to save skill file:', err);
|
|
1097
|
+
if (window.showToast) {
|
|
1098
|
+
showToast(err.message || t('skills.fileSaveError') || 'Failed to save file', 'error');
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
|
|
1104
|
+
|
|
1105
|
+
// ========== Skill Folder Expansion Functions ==========
|
|
1106
|
+
|
|
1107
|
+
var expandedFolders = new Set();
|
|
1108
|
+
|
|
1109
|
+
async function toggleSkillFolder(skillName, subPath, location, element) {
|
|
1110
|
+
const fileItem = element.closest('.skill-file-item');
|
|
1111
|
+
if (!fileItem) return;
|
|
1112
|
+
|
|
1113
|
+
const contentsDiv = fileItem.querySelector('.folder-contents');
|
|
1114
|
+
const chevron = element.querySelector('.folder-chevron');
|
|
1115
|
+
const folderIcon = element.querySelector('.folder-icon');
|
|
1116
|
+
const folderKey = `${skillName}:${subPath}:${location}`;
|
|
1117
|
+
|
|
1118
|
+
if (expandedFolders.has(folderKey)) {
|
|
1119
|
+
// Collapse folder
|
|
1120
|
+
expandedFolders.delete(folderKey);
|
|
1121
|
+
contentsDiv.classList.add('hidden');
|
|
1122
|
+
contentsDiv.innerHTML = '';
|
|
1123
|
+
if (chevron) chevron.style.transform = '';
|
|
1124
|
+
if (folderIcon) folderIcon.setAttribute('data-lucide', 'folder');
|
|
1125
|
+
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
1126
|
+
} else {
|
|
1127
|
+
// Expand folder
|
|
1128
|
+
try {
|
|
1129
|
+
const response = await fetch(
|
|
1130
|
+
'/api/skills/' + encodeURIComponent(skillName) + '/dir?subpath=' + encodeURIComponent(subPath) +
|
|
1131
|
+
'&location=' + location + '&path=' + encodeURIComponent(projectPath)
|
|
1132
|
+
);
|
|
1133
|
+
|
|
1134
|
+
if (!response.ok) {
|
|
1135
|
+
const error = await response.json();
|
|
1136
|
+
throw new Error(error.error || 'Failed to load folder');
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
const data = await response.json();
|
|
1140
|
+
|
|
1141
|
+
expandedFolders.add(folderKey);
|
|
1142
|
+
if (chevron) chevron.style.transform = 'rotate(90deg)';
|
|
1143
|
+
if (folderIcon) folderIcon.setAttribute('data-lucide', 'folder-open');
|
|
1144
|
+
|
|
1145
|
+
// Render folder contents
|
|
1146
|
+
contentsDiv.innerHTML = data.files.map(file => {
|
|
1147
|
+
const filePath = file.path;
|
|
1148
|
+
const isDir = file.isDirectory;
|
|
1149
|
+
return `
|
|
1150
|
+
<div class="skill-file-item" data-path="${escapeHtml(filePath)}">
|
|
1151
|
+
<div class="flex items-center justify-between p-2 bg-muted/30 rounded-lg cursor-pointer hover:bg-muted/50 transition-colors"
|
|
1152
|
+
onclick="${isDir ? `toggleSkillFolder('${escapeHtml(skillName)}', '${escapeHtml(filePath)}', '${location}', this)` : `viewSkillFile('${escapeHtml(skillName)}', '${escapeHtml(filePath)}', '${location}')`}">
|
|
1153
|
+
<div class="flex items-center gap-2">
|
|
1154
|
+
<i data-lucide="${isDir ? 'folder' : 'file-text'}" class="w-4 h-4 text-muted-foreground ${isDir ? 'folder-icon' : ''}"></i>
|
|
1155
|
+
<span class="text-sm font-mono text-foreground">${escapeHtml(file.name)}</span>
|
|
1156
|
+
${isDir ? '<i data-lucide="chevron-right" class="w-3 h-3 text-muted-foreground folder-chevron transition-transform"></i>' : ''}
|
|
1157
|
+
</div>
|
|
1158
|
+
${!isDir ? `
|
|
1159
|
+
<div class="flex items-center gap-1">
|
|
1160
|
+
<button class="p-1 text-muted-foreground hover:text-foreground hover:bg-muted rounded transition-colors"
|
|
1161
|
+
onclick="event.stopPropagation(); editSkillFile('${escapeHtml(skillName)}', '${escapeHtml(filePath)}', '${location}')"
|
|
1162
|
+
title="${t('common.edit')}">
|
|
1163
|
+
<i data-lucide="edit-2" class="w-3.5 h-3.5"></i>
|
|
1164
|
+
</button>
|
|
1165
|
+
</div>
|
|
1166
|
+
` : ''}
|
|
1167
|
+
</div>
|
|
1168
|
+
<div class="folder-contents hidden ml-4 mt-1 space-y-1"></div>
|
|
1169
|
+
</div>
|
|
1170
|
+
`;
|
|
1171
|
+
}).join('');
|
|
1172
|
+
|
|
1173
|
+
contentsDiv.classList.remove('hidden');
|
|
1174
|
+
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
1175
|
+
} catch (err) {
|
|
1176
|
+
console.error('Failed to load folder contents:', err);
|
|
1177
|
+
if (window.showToast) {
|
|
1178
|
+
showToast(err.message || 'Failed to load folder', 'error');
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { z } from 'zod';
|
|
7
7
|
import type { ToolSchema, ToolResult } from '../types/tool.js';
|
|
8
|
+
import type { HistoryIndexEntry } from './cli-history-store.js';
|
|
8
9
|
import { spawn, ChildProcess } from 'child_process';
|
|
9
10
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync, readdirSync, statSync } from 'fs';
|
|
10
11
|
import { join, relative } from 'path';
|
|
@@ -1982,6 +1983,7 @@ export async function getEnrichedConversation(baseDir: string, ccwId: string) {
|
|
|
1982
1983
|
|
|
1983
1984
|
/**
|
|
1984
1985
|
* Get history with native session info
|
|
1986
|
+
* Supports recursive querying of child projects
|
|
1985
1987
|
*/
|
|
1986
1988
|
export async function getHistoryWithNativeInfo(baseDir: string, options?: {
|
|
1987
1989
|
limit?: number;
|
|
@@ -1990,9 +1992,75 @@ export async function getHistoryWithNativeInfo(baseDir: string, options?: {
|
|
|
1990
1992
|
status?: string | null;
|
|
1991
1993
|
category?: ExecutionCategory | null;
|
|
1992
1994
|
search?: string | null;
|
|
1995
|
+
recursive?: boolean;
|
|
1993
1996
|
}) {
|
|
1994
|
-
const
|
|
1995
|
-
|
|
1997
|
+
const { limit = 50, recursive = false, ...queryOptions } = options || {};
|
|
1998
|
+
|
|
1999
|
+
// Non-recursive mode: query single project
|
|
2000
|
+
if (!recursive) {
|
|
2001
|
+
const store = await getSqliteStore(baseDir);
|
|
2002
|
+
return store.getHistoryWithNativeInfo({ limit, ...queryOptions });
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
// Recursive mode: aggregate data from parent and all child projects
|
|
2006
|
+
const { scanChildProjectsAsync } = await import('../config/storage-paths.js');
|
|
2007
|
+
const childProjects = await scanChildProjectsAsync(baseDir);
|
|
2008
|
+
|
|
2009
|
+
// Use the same type as store.getHistoryWithNativeInfo returns
|
|
2010
|
+
type ExecutionWithNativeAndSource = HistoryIndexEntry & {
|
|
2011
|
+
hasNativeSession: boolean;
|
|
2012
|
+
nativeSessionId?: string;
|
|
2013
|
+
nativeSessionPath?: string;
|
|
2014
|
+
};
|
|
2015
|
+
|
|
2016
|
+
const allExecutions: ExecutionWithNativeAndSource[] = [];
|
|
2017
|
+
let totalCount = 0;
|
|
2018
|
+
|
|
2019
|
+
// Query parent project
|
|
2020
|
+
try {
|
|
2021
|
+
const parentStore = await getSqliteStore(baseDir);
|
|
2022
|
+
const parentResult = parentStore.getHistoryWithNativeInfo({ limit, ...queryOptions });
|
|
2023
|
+
totalCount += parentResult.total;
|
|
2024
|
+
|
|
2025
|
+
for (const exec of parentResult.executions) {
|
|
2026
|
+
allExecutions.push({ ...exec, sourceDir: baseDir });
|
|
2027
|
+
}
|
|
2028
|
+
} catch (error) {
|
|
2029
|
+
if (process.env.DEBUG) {
|
|
2030
|
+
console.error(`[CLI History] Failed to query parent project ${baseDir}:`, error);
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
// Query all child projects
|
|
2035
|
+
for (const child of childProjects) {
|
|
2036
|
+
try {
|
|
2037
|
+
const childStore = await getSqliteStore(child.projectPath);
|
|
2038
|
+
const childResult = childStore.getHistoryWithNativeInfo({ limit, ...queryOptions });
|
|
2039
|
+
totalCount += childResult.total;
|
|
2040
|
+
|
|
2041
|
+
for (const exec of childResult.executions) {
|
|
2042
|
+
allExecutions.push({ ...exec, sourceDir: child.projectPath });
|
|
2043
|
+
}
|
|
2044
|
+
} catch (error) {
|
|
2045
|
+
if (process.env.DEBUG) {
|
|
2046
|
+
console.error(`[CLI History] Failed to query child project ${child.projectPath}:`, error);
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
// Sort by updated_at descending and apply limit
|
|
2052
|
+
allExecutions.sort((a, b) => {
|
|
2053
|
+
const timeA = a.updated_at ? new Date(a.updated_at).getTime() : new Date(a.timestamp).getTime();
|
|
2054
|
+
const timeB = b.updated_at ? new Date(b.updated_at).getTime() : new Date(b.timestamp).getTime();
|
|
2055
|
+
return timeB - timeA;
|
|
2056
|
+
});
|
|
2057
|
+
const limitedExecutions = allExecutions.slice(0, limit);
|
|
2058
|
+
|
|
2059
|
+
return {
|
|
2060
|
+
total: totalCount,
|
|
2061
|
+
count: limitedExecutions.length,
|
|
2062
|
+
executions: limitedExecutions
|
|
2063
|
+
};
|
|
1996
2064
|
}
|
|
1997
2065
|
|
|
1998
2066
|
// Export types
|