feat(workflow): add multi-CLI collaborative planning command

- Introduced a new command `/workflow:multi-cli-plan` for collaborative planning using ACE semantic search and iterative analysis with Claude and Codex.
- Implemented a structured execution flow with phases for context gathering, multi-tool analysis, user decision points, and final plan generation.
- Added detailed documentation outlining the command's usage, execution phases, and key features.
- Included error handling and configuration options for enhanced user experience.
This commit is contained in:
catlog22
2026-01-13 23:23:09 +08:00
parent 2f1c56285a
commit c3da637849
7 changed files with 2669 additions and 196 deletions

View File

@@ -268,6 +268,12 @@ const i18n = {
'cli.envFilePlaceholder': 'Path to .env file (e.g., ~/.gemini-env or C:/Users/xxx/.env)',
'cli.envFileHint': 'Load environment variables (e.g., API keys) before CLI execution. Supports ~ for home directory.',
'cli.envFileBrowse': 'Browse',
'cli.fileBrowser': 'File Browser',
'cli.fileBrowserSelect': 'Select',
'cli.fileBrowserCancel': 'Cancel',
'cli.fileBrowserUp': 'Parent Directory',
'cli.fileBrowserHome': 'Home',
'cli.fileBrowserShowHidden': 'Show hidden files',
// CodexLens Configuration
'codexlens.config': 'CodexLens Configuration',
@@ -2442,6 +2448,12 @@ const i18n = {
'cli.envFilePlaceholder': '.env 文件路径(如 ~/.gemini-env 或 C:/Users/xxx/.env',
'cli.envFileHint': '在 CLI 执行前加载环境变量(如 API 密钥)。支持 ~ 表示用户目录。',
'cli.envFileBrowse': '浏览',
'cli.fileBrowser': '文件浏览器',
'cli.fileBrowserSelect': '选择',
'cli.fileBrowserCancel': '取消',
'cli.fileBrowserUp': '上级目录',
'cli.fileBrowserHome': '主目录',
'cli.fileBrowserShowHidden': '显示隐藏文件',
// CodexLens 配置
'codexlens.config': 'CodexLens 配置',

View File

@@ -554,6 +554,241 @@ function buildToolConfigModalContent(tool, config, models, status) {
'</div>';
}
// ========== File Browser Modal ==========
var fileBrowserState = {
currentPath: '',
showHidden: false,
onSelect: null
};
function showFileBrowserModal(onSelect) {
fileBrowserState.onSelect = onSelect;
fileBrowserState.showHidden = false;
// Create modal overlay
var overlay = document.createElement('div');
overlay.id = 'fileBrowserOverlay';
overlay.className = 'modal-overlay';
overlay.innerHTML = buildFileBrowserModalContent();
document.body.appendChild(overlay);
// Load initial directory (home)
loadFileBrowserDirectory('');
// Initialize events
initFileBrowserEvents();
// Initialize icons
if (window.lucide) lucide.createIcons();
}
function buildFileBrowserModalContent() {
return '<div class="modal-content file-browser-modal">' +
'<div class="modal-header">' +
'<h3><i data-lucide="folder-open" class="w-4 h-4"></i> ' + t('cli.fileBrowser') + '</h3>' +
'<button class="modal-close" id="fileBrowserCloseBtn">&times;</button>' +
'</div>' +
'<div class="modal-body">' +
'<div class="file-browser-toolbar">' +
'<button class="btn-sm btn-outline" id="fileBrowserUpBtn" title="' + t('cli.fileBrowserUp') + '">' +
'<i data-lucide="arrow-up" class="w-3.5 h-3.5"></i>' +
'</button>' +
'<button class="btn-sm btn-outline" id="fileBrowserHomeBtn" title="' + t('cli.fileBrowserHome') + '">' +
'<i data-lucide="home" class="w-3.5 h-3.5"></i>' +
'</button>' +
'<input type="text" id="fileBrowserPathInput" class="file-browser-path" placeholder="/" readonly />' +
'<label class="file-browser-hidden-toggle">' +
'<input type="checkbox" id="fileBrowserShowHidden" />' +
'<span>' + t('cli.fileBrowserShowHidden') + '</span>' +
'</label>' +
'</div>' +
'<div class="file-browser-list" id="fileBrowserList">' +
'<div class="file-browser-loading"><i data-lucide="loader-2" class="w-5 h-5 animate-spin"></i></div>' +
'</div>' +
'</div>' +
'<div class="modal-footer">' +
'<button class="btn btn-outline" id="fileBrowserCancelBtn">' + t('cli.fileBrowserCancel') + '</button>' +
'<button class="btn btn-primary" id="fileBrowserSelectBtn" disabled>' +
'<i data-lucide="check" class="w-3.5 h-3.5"></i> ' + t('cli.fileBrowserSelect') +
'</button>' +
'</div>' +
'</div>';
}
async function loadFileBrowserDirectory(path) {
var listContainer = document.getElementById('fileBrowserList');
var pathInput = document.getElementById('fileBrowserPathInput');
if (listContainer) {
listContainer.innerHTML = '<div class="file-browser-loading"><i data-lucide="loader-2" class="w-5 h-5 animate-spin"></i></div>';
if (window.lucide) lucide.createIcons();
}
try {
var response = await fetch('/api/dialog/browse', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path: path, showHidden: fileBrowserState.showHidden })
});
if (!response.ok) {
throw new Error('Failed to load directory');
}
var data = await response.json();
fileBrowserState.currentPath = data.currentPath;
if (pathInput) {
pathInput.value = data.currentPath;
}
renderFileBrowserItems(data.items);
} catch (err) {
console.error('Failed to load directory:', err);
if (listContainer) {
listContainer.innerHTML = '<div class="file-browser-error">Failed to load directory</div>';
}
}
}
function renderFileBrowserItems(items) {
var listContainer = document.getElementById('fileBrowserList');
if (!listContainer) return;
if (!items || items.length === 0) {
listContainer.innerHTML = '<div class="file-browser-empty">Empty directory</div>';
return;
}
var html = items.map(function(item) {
var icon = item.isDirectory ? 'folder' : 'file';
var itemClass = 'file-browser-item' + (item.isDirectory ? ' is-directory' : ' is-file');
return '<div class="' + itemClass + '" data-path="' + escapeHtml(item.path) + '" data-is-dir="' + item.isDirectory + '">' +
'<i data-lucide="' + icon + '" class="w-4 h-4"></i>' +
'<span class="file-browser-item-name">' + escapeHtml(item.name) + '</span>' +
'</div>';
}).join('');
listContainer.innerHTML = html;
// Initialize icons
if (window.lucide) lucide.createIcons();
// Add click handlers
listContainer.querySelectorAll('.file-browser-item').forEach(function(el) {
el.onclick = function() {
var isDir = el.getAttribute('data-is-dir') === 'true';
var path = el.getAttribute('data-path');
if (isDir) {
// Navigate into directory
loadFileBrowserDirectory(path);
} else {
// Select file
listContainer.querySelectorAll('.file-browser-item').forEach(function(item) {
item.classList.remove('selected');
});
el.classList.add('selected');
// Enable select button
var selectBtn = document.getElementById('fileBrowserSelectBtn');
if (selectBtn) {
selectBtn.disabled = false;
selectBtn.setAttribute('data-selected-path', path);
}
}
};
// Double-click to select file or enter directory
el.ondblclick = function() {
var isDir = el.getAttribute('data-is-dir') === 'true';
var path = el.getAttribute('data-path');
if (isDir) {
loadFileBrowserDirectory(path);
} else {
// Select and close
closeFileBrowserModal(path);
}
};
});
}
function initFileBrowserEvents() {
// Close button
var closeBtn = document.getElementById('fileBrowserCloseBtn');
if (closeBtn) {
closeBtn.onclick = function() { closeFileBrowserModal(null); };
}
// Cancel button
var cancelBtn = document.getElementById('fileBrowserCancelBtn');
if (cancelBtn) {
cancelBtn.onclick = function() { closeFileBrowserModal(null); };
}
// Select button
var selectBtn = document.getElementById('fileBrowserSelectBtn');
if (selectBtn) {
selectBtn.onclick = function() {
var path = selectBtn.getAttribute('data-selected-path');
closeFileBrowserModal(path);
};
}
// Up button
var upBtn = document.getElementById('fileBrowserUpBtn');
if (upBtn) {
upBtn.onclick = function() {
// Get parent path
var currentPath = fileBrowserState.currentPath;
var parentPath = currentPath.replace(/[/\\][^/\\]+$/, '') || '/';
loadFileBrowserDirectory(parentPath);
};
}
// Home button
var homeBtn = document.getElementById('fileBrowserHomeBtn');
if (homeBtn) {
homeBtn.onclick = function() {
loadFileBrowserDirectory('');
};
}
// Show hidden checkbox
var showHiddenCheckbox = document.getElementById('fileBrowserShowHidden');
if (showHiddenCheckbox) {
showHiddenCheckbox.onchange = function() {
fileBrowserState.showHidden = showHiddenCheckbox.checked;
loadFileBrowserDirectory(fileBrowserState.currentPath);
};
}
// Click outside to close
var overlay = document.getElementById('fileBrowserOverlay');
if (overlay) {
overlay.onclick = function(e) {
if (e.target === overlay) {
closeFileBrowserModal(null);
}
};
}
}
function closeFileBrowserModal(selectedPath) {
var overlay = document.getElementById('fileBrowserOverlay');
if (overlay) {
overlay.remove();
}
if (fileBrowserState.onSelect && selectedPath) {
fileBrowserState.onSelect(selectedPath);
}
fileBrowserState.onSelect = null;
}
function initToolConfigModalEvents(tool, currentConfig, models) {
// Local tags state (copy from config)
var currentTags = (currentConfig.tags || []).slice();
@@ -754,38 +989,13 @@ function initToolConfigModalEvents(tool, currentConfig, models) {
// Environment file browse button (only for gemini/qwen)
var envFileBrowseBtn = document.getElementById('envFileBrowseBtn');
if (envFileBrowseBtn) {
envFileBrowseBtn.onclick = async function() {
try {
// Use file dialog API if available
var response = await fetch('/api/dialog/open-file', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: t('cli.envFile'),
filters: [
{ name: 'Environment Files', extensions: ['env'] },
{ name: 'All Files', extensions: ['*'] }
],
defaultPath: ''
})
});
if (response.ok) {
var data = await response.json();
if (data.filePath) {
var envFileInput = document.getElementById('envFileInput');
if (envFileInput) {
envFileInput.value = data.filePath;
}
}
} else {
// Fallback: prompt user to enter path manually
showRefreshToast('File dialog not available. Please enter path manually.', 'info');
envFileBrowseBtn.onclick = function() {
showFileBrowserModal(function(selectedPath) {
var envFileInput = document.getElementById('envFileInput');
if (envFileInput && selectedPath) {
envFileInput.value = selectedPath;
}
} catch (err) {
console.error('Failed to open file dialog:', err);
showRefreshToast('File dialog not available. Please enter path manually.', 'info');
}
});
};
}