diff --git a/crates/local-deployment/src/container.rs b/crates/local-deployment/src/container.rs index e5d9a449ad..b85996dcec 100644 --- a/crates/local-deployment/src/container.rs +++ b/crates/local-deployment/src/container.rs @@ -704,7 +704,24 @@ impl LocalContainerService { && !copy_files.trim().is_empty() { let worktree_path = workspace_dir.join(&repo.name); - self.copy_project_files(&repo.path, &worktree_path, copy_files) + + // Strip repo name prefix from patterns if present (legacy format from search results) + // e.g., "repo-name/.env, repo-name/config.json" -> ".env, config.json" + let repo_prefix = format!("{}/", repo.name); + let normalized_copy_files: String = copy_files + .split(',') + .map(|p| { + let trimmed = p.trim(); + if trimmed.starts_with(&repo_prefix) { + trimmed.strip_prefix(&repo_prefix).unwrap_or(trimmed) + } else { + trimmed + } + }) + .collect::>() + .join(", "); + + self.copy_project_files(&repo.path, &worktree_path, &normalized_copy_files) .await .unwrap_or_else(|e| { tracing::warn!( diff --git a/crates/local-deployment/src/copy.rs b/crates/local-deployment/src/copy.rs index 213fa9afa2..f453eb1054 100644 --- a/crates/local-deployment/src/copy.rs +++ b/crates/local-deployment/src/copy.rs @@ -324,4 +324,43 @@ mod tests { assert_eq!(std::fs::read_dir(dst.path()).unwrap().count(), 0); } + + #[test] + fn test_copy_hidden_files_via_glob_pattern() { + let source_dir = TempDir::new().unwrap(); + let target_dir = TempDir::new().unwrap(); + + // Create hidden files + fs::write(source_dir.path().join(".env"), "SECRET=value").unwrap(); + fs::write(source_dir.path().join(".env.local"), "LOCAL=value").unwrap(); + fs::write(source_dir.path().join(".hidden"), "hidden content").unwrap(); + + // Copy using glob pattern that should match hidden files + copy_project_files_impl(source_dir.path(), target_dir.path(), ".*").unwrap(); + + // All hidden files should be copied + assert!(target_dir.path().join(".env").exists()); + assert!(target_dir.path().join(".env.local").exists()); + assert!(target_dir.path().join(".hidden").exists()); + } + + #[test] + fn test_copy_gitignored_files() { + let source_dir = TempDir::new().unwrap(); + let target_dir = TempDir::new().unwrap(); + + // Create a .gitignore that would normally exclude .env + fs::write(source_dir.path().join(".gitignore"), ".env\n*.secret").unwrap(); + + // Create files that would be gitignored + fs::write(source_dir.path().join(".env"), "SECRET=value").unwrap(); + fs::write(source_dir.path().join("api.secret"), "api key").unwrap(); + + // Copy the gitignored files explicitly - they should still be copied + copy_project_files_impl(source_dir.path(), target_dir.path(), ".env, *.secret").unwrap(); + + // Gitignored files should still be copied when explicitly specified + assert!(target_dir.path().join(".env").exists()); + assert!(target_dir.path().join("api.secret").exists()); + } } diff --git a/crates/server/src/main.rs b/crates/server/src/main.rs index 7981064731..2a9e0be329 100644 --- a/crates/server/src/main.rs +++ b/crates/server/src/main.rs @@ -119,6 +119,7 @@ async fn main() -> Result<(), VibeKanbanError> { } tracing::info!("Server running on http://{host}:{actual_port}"); + println!("\n>>> vibe-kanban running at: http://{host}:{actual_port}\n"); if !cfg!(debug_assertions) { tracing::info!("Opening browser..."); diff --git a/frontend/src/components/projects/CopyFilesField.tsx b/frontend/src/components/projects/CopyFilesField.tsx index 9aa313b12c..0bd063e7c7 100644 --- a/frontend/src/components/projects/CopyFilesField.tsx +++ b/frontend/src/components/projects/CopyFilesField.tsx @@ -5,6 +5,7 @@ interface CopyFilesFieldProps { value: string; onChange: (value: string) => void; projectId: string; + repoName?: string; disabled?: boolean; } @@ -12,6 +13,7 @@ export function CopyFilesField({ value, onChange, projectId, + repoName, disabled = false, }: CopyFilesFieldProps) { const { t } = useTranslation('projects'); @@ -25,6 +27,7 @@ export function CopyFilesField({ disabled={disabled} className="w-full px-3 py-2 text-sm border border-input bg-background text-foreground disabled:opacity-50 rounded-md resize-vertical focus:outline-none focus:ring-2 focus:ring-ring" projectId={projectId} + repoName={repoName} maxRows={6} /> ); diff --git a/frontend/src/components/ui/multi-file-search-textarea.tsx b/frontend/src/components/ui/multi-file-search-textarea.tsx index 4e2382cc01..9efafd05e9 100644 --- a/frontend/src/components/ui/multi-file-search-textarea.tsx +++ b/frontend/src/components/ui/multi-file-search-textarea.tsx @@ -18,6 +18,7 @@ interface MultiFileSearchTextareaProps { disabled?: boolean; className?: string; projectId: string; + repoName?: string; onKeyDown?: (e: React.KeyboardEvent) => void; maxRows?: number; } @@ -30,6 +31,7 @@ export function MultiFileSearchTextarea({ disabled = false, className, projectId, + repoName, onKeyDown, maxRows = 10, }: MultiFileSearchTextareaProps) { @@ -218,8 +220,14 @@ export function MultiFileSearchTextarea({ const before = value.slice(0, currentTokenStart); const after = value.slice(currentTokenEnd); + // Strip repo name prefix if provided (search results include repo_name/path format) + let filePath = file.path; + if (repoName && filePath.startsWith(`${repoName}/`)) { + filePath = filePath.slice(repoName.length + 1); + } + // Smart comma handling - add ", " if not at end and next char isn't comma/newline - let insertion = file.path; + let insertion = filePath; const trimmedAfter = after.trimStart(); const needsComma = trimmedAfter.length > 0 && diff --git a/frontend/src/pages/settings/ProjectSettings.tsx b/frontend/src/pages/settings/ProjectSettings.tsx index 6e28c897b7..acbecd242c 100644 --- a/frontend/src/pages/settings/ProjectSettings.tsx +++ b/frontend/src/pages/settings/ProjectSettings.tsx @@ -885,6 +885,11 @@ export function ProjectSettings() { updateScriptsDraft({ copy_files: value }) } projectId={selectedProject.id} + repoName={ + repositories.find( + (r) => r.id === selectedScriptsRepoId + )?.name + } />

{t('settings.projects.scripts.copyFiles.helper')} diff --git a/npx-cli/package.json b/npx-cli/package.json index 4fdffc8d80..3127a47a6a 100644 --- a/npx-cli/package.json +++ b/npx-cli/package.json @@ -4,7 +4,8 @@ "version": "0.0.148", "main": "index.js", "bin": { - "vibe-kanban": "bin/cli.js" + "vibe-kanban": "bin/cli.js", + "vibe-kanban-local": "bin/cli.js" }, "keywords": [], "author": "bloop",