Add execCommand options to opt into keyboard-interactive auth and wire MFA only
for export-key flows, preserving non-interactive exec usage elsewhere.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat: Sync port forwarding rules
- Refactor `usePortForwardingState` to use a global store pattern, ensuring state consistency across the application.
- Integrate `usePortForwardingState` into `App.tsx` to retrieve and update port forwarding rules.
- Update `useAutoSync` in `App.tsx` to include `portForwardingRules` in the sync payload and handle incoming updates via `importRules`.
Co-authored-by: binaricat <16399091+binaricat@users.noreply.github.com>
* Normalize imported port forwarding statuses
* fix: correct indentation in usePortForwardingState.ts
Co-authored-by: binaricat <16399091+binaricat@users.noreply.github.com>
* Normalize imported port forwarding rule status
* Stabilize port forwarding rules for auto-sync
---------
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
- Added `CreateWorkspaceDialog` component for creating named workspaces with multiple hosts.
- Implemented `createWorkspaceWithHosts` in `useSessionState`.
- Integrated dialog into `App.tsx` and triggered from Quick Switcher.
- Updated `QuickSwitcher` logic to improve visibility of recent connections.
- Added i18n keys for the dialog.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
- Added `setDeviceName` method to `CloudSyncManager` to update state, persist to local storage, and notify listeners.
- Updated `useCloudSync` hook to expose the `setDeviceName` function from the manager.
- Ensures device name updates are correctly handled and persisted across sessions.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Refactored the host filtering logic in `useManagedSourceSync` to index hosts by `managedSourceId` before iterating through sources. This reduces the complexity from O(N*M) to O(N+M), where N is the number of sources and M is the number of hosts.
Benchmarks showed a ~74x speedup (from ~600ms to ~8ms) with 500 sources and 25,000 hosts.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
- Expose addExternalUpload and updateExternalUpload methods from useSftpState
- Add download task to transfer queue when starting stream download
- Update progress during download with transferred bytes, total, and speed
- Update task status on completion, failure, or cancellation
Co-Authored-By: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
* feat: stream-based SFTP download for large files
- Add showSaveDialog API for native file save dialog
- Modify handleDownload to use streaming transfer for remote files
- Show save dialog first, then stream directly to disk
- Avoid loading entire file into memory
- Fallback to memory-based download for local files or when streaming unavailable
This fixes the issue where downloading large files would cause high memory
usage as the entire file was loaded into memory before saving.
Co-Authored-By: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
* feat: stream-based download for SftpView
- Add getSftpIdForConnection to useSftpState for accessing SFTP session IDs
- Modify handleDownloadFileForSide in useSftpViewFileOps to use streaming
- Pass showSaveDialog, startStreamTransfer to SftpView hook chain
- For remote SFTP files: show save dialog then stream directly to disk
- For local files: fallback to memory-based download
This extends the stream download optimization to SftpView (dual-pane browser),
not just SFTPModal.
Co-Authored-By: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
* fix: improve download task handling and cancellation
- Add per-task cancellation for downloads via onCancelTask prop
- Add i18n translation keys for download status messages
- Prevent duplicate error toasts with errorHandled flag
- Add bridge capability check (result === undefined)
- Make direction field required in TransferTask interface
Co-Authored-By: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
---------
Co-authored-by: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
* Initial plan
* Add drag-and-drop functionality for terminal
- Local terminal: Insert absolute file paths when files are dropped
- Remote terminal: Open SFTP modal and trigger file upload
- Add visual feedback with drag overlay
- Support both files and folders
- Add translations for English and Chinese
Co-authored-by: binaricat <16399091+binaricat@users.noreply.github.com>
* Remove unintended package-lock.json changes
Co-authored-by: binaricat <16399091+binaricat@users.noreply.github.com>
* Improve folder path handling in drag-and-drop
- Extract folder path from nested files when folder is dropped
- Show folder path once instead of individual file paths
- Handle both Windows and Unix path separators
Co-authored-by: binaricat <16399091+binaricat@users.noreply.github.com>
* fix: address code review feedback for drag-and-drop
- Replace fixed 500ms delay with proper connection state check
(wait for files to load instead of arbitrary timeout)
- Add duplicate upload prevention with ref flag
- Simplify redundant ternary (dropEffect was always 'copy')
- Improve folder path extraction logic for better reliability
Co-Authored-By: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
* fix: open SFTP at current directory when dropping files
Get the current working directory from terminal session before opening
SFTP modal for file uploads, matching the behavior of the SFTP toolbar button.
Co-Authored-By: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
* fix: preserve directory structure when uploading folders via drag-drop
- Pass DropEntry[] instead of File[] to preserve relativePath info
- Add handleUploadEntries function that uses uploadEntriesDirect
- This maintains folder structure when uploading directories to remote
Co-Authored-By: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
* fix: support drag-drop upload to empty remote directories
Use loading state transition detection instead of files.length check
to determine when SFTP connection is ready. This fixes the issue where
drag-drop uploads to empty directories would silently fail because
files.length was always 0.
Co-Authored-By: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
* fix: preserve empty directories in terminal drop uploads
Pass full dropEntries array including directory markers to SFTP upload
instead of filtering to only file entries. This ensures empty folders
are created on the remote side via uploadEntriesDirect which uses
isDirectory entries to call ensureDirectory.
Co-Authored-By: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
* fix: remove unused handleUploadMultiple import
Co-Authored-By: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
* refactor: improve drag-drop code readability
- Extract path extraction logic into extractRootPathsFromDropEntries function
- Add comment explaining flushSync usage for state synchronization
- Remove redundant dropEntries.length check (already checked earlier)
Co-Authored-By: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: binaricat <16399091+binaricat@users.noreply.github.com>
Co-authored-by: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
- Reuse component-level isLocalConnection/isSerialConnection in useEffect
- Add i18n support for protocol labels (en/zh-CN)
- Use correct default port per protocol (SSH: 22, Telnet: 23, Mosh: 22)
Co-Authored-By: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
Replace wide dropdown encoding selector with a compact icon button (Languages)
that opens a popover menu. Also add tooltips to navigation buttons (up, home,
refresh).
Co-Authored-By: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
The folder compression transfer setting is SFTP-specific functionality,
so it makes more sense to place it alongside other SFTP settings like
double-click behavior, auto-sync, and show hidden files.
- Move setting from SettingsTerminalTab to SettingsFileAssociationsTab
- Add new i18n keys under settings.sftp.compressedUpload namespace
- Remove unused settings.terminal.uploadDownload translations
- Update SettingsPage props accordingly
Co-Authored-By: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
- Replace 3-option setting (ask/enabled/disabled) with boolean toggle
- Remove CompressedUploadDialog component - no more user prompts
- Auto-detect tar availability and silently fallback to regular upload
- Apply compressed upload setting to drag-and-drop uploads
- Fix upload progress updates for compressed uploads via callback
- Add i18n for upload phase labels (compressing/uploading/extracting)
- Fix empty folder handling - fallback to regular mkdir
- Preserve error message on failed upload tasks for UI display
- Fix fallback logic to only re-upload failed folders, not successful ones
- Handle cancellation during all phases (compression/transfer/extraction)
- Fix filename display to use explicit phase markers instead of substring match
- Fix Toggle onChange prop for settings to work correctly
- Use DropEntry.relativePath for correct drag-drop folder paths
- Dynamic extraction timeout based on archive size (60s base + 30s per 10MB)
Co-Authored-By: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
Add clearAndRemoveSources function that clears multiple SSH config files
in parallel but removes all sources in a single atomic update. This
prevents race conditions where concurrent removals could re-add sources
that were already deleted.
When deleting a group path with multiple managed sources, the batch
removal is now used instead of Promise.all with individual removals.
Co-Authored-By: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
When multiple managed sources sync concurrently via Promise.all, each
sync was independently updating the managedSources array, causing race
conditions where one source's lastSyncedAt would be overwritten.
Now syncManagedSource returns success status, and lastSyncedAt updates
are batched in a single update after all syncs complete.
Co-Authored-By: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
- Preserve top-level comments and blank lines when merging SSH config
by tracking preamble content before the first Host/Match block
- Validate file path availability before enabling managed sync; show
error if getPathForFile and file.path are both unavailable instead
of falling back to bare filename which would sync to wrong location
Co-Authored-By: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
- Keep preserved SSH config content outside managed block markers to
prevent data loss when first bringing existing config under management
- Always bracket IPv6 addresses in ProxyJump values regardless of port,
as OpenSSH requires brackets to disambiguate colon separators
Co-Authored-By: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
When creating a managed source from an existing SSH config file without
Netcatty markers, the code was appending the managed block without removing
original Host entries. This left duplicate Host blocks, and since OpenSSH
uses the first matching block, edits via Netcatty wouldn't take effect.
Now uses mergeWithExistingSshConfig to filter out existing Host blocks
that match managed hostnames before wrapping with markers.
Co-Authored-By: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
- Filter duplicates on managed import and convert existing hosts to managed
- Trigger initial sync when prevHosts is empty but managed sources exist
- Update previousHostsRef even when no managed sources to maintain baseline
Co-Authored-By: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
When a managed host references a jump host outside its managed source,
changes to that external jump host now trigger a sync. This ensures
ProxyJump entries stay up-to-date when jump hosts are edited.
Co-Authored-By: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
ProxyJump changes now trigger sync since hostChain.hostIds is compared
alongside other host fields like hostname, port, username, etc.
Co-Authored-By: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
When syncing managed hosts to SSH config files, properly serialize the
hostChain to ProxyJump directive instead of just adding a comment.
- Add serializeJumpHost() to format jump host as [user@]host[:port]
- Add buildProxyJumpValue() to convert hostChain.hostIds to ProxyJump
- Pass allHosts to serializer for looking up jump host details
- Supports multiple jump hosts (comma-separated)
Co-Authored-By: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
When deleting a managed group, clear the managed block in the SSH config
file before removing the source. This prevents stale host entries from
remaining in the file after the group is deleted.
- Add clearAndRemoveSource function to useManagedSourceSync
- Pass callback to VaultView and call it before removing managed sources
- Ensures SSH config files stay in sync when managed groups are deleted
Co-Authored-By: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
- Add CompressedUploadDialog component to let users choose between compressed and regular transfer methods
- Implement compressUploadService for handling folder compression and extraction on the server
- Add compressUploadBridge to expose compression functionality to the renderer process
- Add sftpUseCompressedUpload setting with three modes: ask, enabled, disabled
- Add new upload progress states: compressing, extracting, scanning, completed
- Add i18n translations for upload dialog and settings in English and Chinese
- Update SFTP modal to support compressed upload workflow with progress tracking
- Add storage key for persisting compressed upload preference across sessions
- Significantly reduces transfer time for folders by using tar compression when available on server
Use a ref to access the latest managedSources when updating lastSyncedAt
after sync completes. This prevents overwriting concurrent changes made
while a sync was in flight (e.g., user unmanaging a source or importing
a new managed file).
Co-Authored-By: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
- Use marker blocks to preserve existing SSH config directives (Match, Include, Host *, etc.)
- Only replace content between BEGIN/END NETCATTY MANAGED markers
- Update managedSources.groupName when groups are renamed or moved
- Prevents data loss for users with custom SSH config entries
Co-Authored-By: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
- Add protocol to change detection to trigger sync when protocol changes
- Sanitize labels (remove spaces) when moving hosts to managed groups via drag/drop
- Prevent duplicate managed imports by checking if file is already managed
- Add i18n keys for already managed file warning
Co-Authored-By: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
- Add pending sync tracking to process host changes that occur during sync
- Strip spaces from labels when host is/will be in a managed source group
- Remove unused FileSymlink import
Co-Authored-By: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
- Add i18n translations for "Upload folder" in English and Chinese locales
- Add folderInputRef to track folder input element in SFTPModal component
- Implement handleFolderSelect handler for processing folder uploads
- Add "Upload folder" button to SftpModalHeader with FolderUp icon
- Add hidden file input with webkitdirectory attribute for folder selection
- Update SftpModalFileList context menu to include folder upload option
- Pass folderInputRef and folder handlers through component hierarchy
- Enable users to upload entire folder structures via SFTP modal
- Fix managed host sync: add group field to change detection
- Auto-set managedSourceId when moving host to managed group
- Add 'managed' badge to managed hosts in VaultView
- Fix file path resolution for managed sources using webUtils
- Add managedSources prop to HostDetailsPanel for proper sync
- Restrict spaces in label for managed hosts
- Reorder HostDetailsPanel sections: General > Address > Port & Credentials
- Add debug logging for managed source sync troubleshooting
Add context menu option to rename snippet packages with a modal dialog.
Includes validation for empty names, duplicate names (case-insensitive),
and invalid characters (only letters, numbers, hyphens, underscores allowed).
When a package is renamed, all nested packages and snippets are updated
to reflect the new path.
Co-Authored-By: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
- Add tree view mode alongside existing grid and list views
- Implement hierarchical display of hosts organized by groups
- Add expand/collapse all controls with Chinese translations
- Support all sorting modes (A-Z, Z-A, newest, oldest) in tree view
- Persist expand/collapse state across view switches and app restarts
- Hide Groups section in tree view to avoid duplication
- Display ungrouped hosts at root level instead of "General" group
- Add missing delete group dialog with proper translations
- Maintain full functionality: search, filtering, drag-drop, context menus
Technical changes:
- Create HostTreeView component with TreeNode recursive structure
- Add useTreeExpandedState hook for persistent state management
- Extend ViewMode type to include "tree" option
- Add sortMode prop to enable dynamic sorting in tree structure
- Separate group tree logic for tree view vs other view modes
- Add comprehensive English and Chinese translations
- Add PassphraseModal component for interactive passphrase input
- Add passphraseHandler bridge to manage passphrase requests/responses
- Add sshAuthHelper for centralized SSH key decryption with passphrase support
- Update sshBridge, sftpBridge, and portForwardingBridge to use new auth helper
- Add passphrase-related IPC channels in preload and type definitions
- Add i18n translations for passphrase modal UI (en/zh-CN)
Co-Authored-By: Claude (gemini-claude-opus-4-5-thinking) <noreply@anthropic.com>
Add the ability to duplicate an SSH session by right-clicking on a tab
and selecting "Copy Tab". This creates a new session with the same
connection parameters (host, port, protocol, etc.).
Co-Authored-By: Claude <noreply@anthropic.com>
- Replace memory-based file uploads with stream transfers for large files
- Add uploadWithStreams and downloadWithStreams functions in transferBridge
- Fix cancel transfer by properly destroying streams instead of throwing
errors in callbacks (which corrupted SSH connection)
- Fix upload button not triggering upload by copying FileList before
clearing input (clearing input also clears FileList reference)
- Export getPathForFile utility for obtaining local file paths
- Add startStreamTransfer and cancelTransfer bridge methods
Co-Authored-By: Claude <noreply@anthropic.com>
- Change SFTP date format from ISO 8601 to readable YYYY-MM-DD HH:mm
- Fix lastModifiedFormatted to use formatDate instead of raw ISO string
- Hide "General" group at root level in vault (hosts already shown below)
- Fix General group filter to match hosts with empty/undefined group
- Exclude .github/** from ESLint (CI scripts don't need local linting)
Co-Authored-By: Claude <noreply@anthropic.com>
Address Codex review: mosh hosts are now filtered out during CSV export
alongside serial hosts, as the CSV import's normalizeProtocol only
recognizes ssh/telnet/local. Exporting mosh hosts would silently convert
them to SSH on re-import.
Updated toast message to say "unsupported hosts" instead of "serial hosts"
since both serial and mosh are now skipped.
Co-Authored-By: Claude <noreply@anthropic.com>
Address Codex review: serial hosts (protocol === "serial") are now
filtered out during CSV export since the CSV import format doesn't
support serial port configuration. Importing serial hosts would result
in invalid SSH entries.
When serial hosts are skipped, users are notified via a toast message
showing how many were excluded.
Co-Authored-By: Claude <noreply@anthropic.com>
- Add export button to export hosts to CSV file using import template format
- Add eye icon to toggle password visibility in host edit panel
- Add "Copy Credentials" option to host context menu for sharing host info
- Make label field optional when creating new host (defaults to hostname/IP)
- Fix duplicate keyboard shortcuts:
- clear-buffer: changed from ⌘+K to ⌘+⌃+K (Mac) and Ctrl+L to Ctrl+Shift+K (PC)
- open-sftp: changed from ⌘+Shift+S to ⌘+Shift+O (Mac) and Ctrl+Shift+O (PC)
- snippets: changed PC shortcut from Ctrl+Alt+S to Ctrl+Shift+S to match Mac
Co-Authored-By: Claude <noreply@anthropic.com>
- Add "Duplicate" option to host context menu to quickly copy hosts
- Add "Save Password" toggle in host details panel
- Add savePassword field to Host interface
- Add i18n translations for both features (en, zh-CN)
- Internationalize existing hardcoded context menu items
Co-authored-by: binaricat <16399091+binaricat@users.noreply.github.com>
The previous fix only checked the direct parent task ID, which doesn't
work for deeply nested folders:
- Top-level task "A" (user cancels this)
- Child folder "B" with parentTaskId="A" (detected cancellation)
- Grandchild file "C" with parentTaskId="B" (NOT detected!)
Now we pass rootTaskId through all levels of transferDirectory and
transferFile calls, so all nested operations check against the
original top-level task ID.
Co-Authored-By: Claude <noreply@anthropic.com>
Previously, clicking cancel only updated the task status but didn't
stop the actual file transfer operations. The transferFile and
transferDirectory functions would continue running.
Changes:
- Add cancelledTasksRef to track cancelled task IDs
- Check cancellation status at start of transferFile/transferDirectory
- Check cancellation status during directory iteration
- Add cancelled task ID to ref in cancelTransfer
- Handle cancellation errors gracefully in processTransfer
- Clean up cancelled IDs after 5s delay
Now cancelling a folder transfer actually stops the operation.
Co-Authored-By: Claude <noreply@anthropic.com>
The UploadBridge interface has a different parameter order than
NetcattyBridge for writeSftpBinaryWithProgress:
- UploadBridge: (sftpId, path, data, taskId, onProgress, ...)
- NetcattyBridge: (sftpId, path, content, transferId, encoding, onProgress, ...)
Previously, the bridge was passed directly which caused callbacks
to be passed where encoding was expected, leading to
"An object could not be cloned" errors in Electron IPC.
Now we wrap the function to properly adapt the interfaces.
Co-Authored-By: Claude <noreply@anthropic.com>
When connecting to a new host, the pane's filenameEncoding was being
preserved from the previous connection. This caused issues when:
1. A pane was set to explicit encoding (e.g., UTF-8)
2. User connects to a host configured for GB18030
3. The bridge wouldn't auto-detect because encoding wasn't "auto"
Now the encoding is reset to the host's configured sftpEncoding or
"auto" when a new connection is established, ensuring proper
auto-detection and respecting host-level encoding settings.
Co-Authored-By: Claude <noreply@anthropic.com>
- Fix temporal dead zone issue in useSftpTransfers.ts by moving
sourceEncoding declaration before its usage (P1 issue)
- Initialize filenameEncoding from host.sftpEncoding in SFTPModal.tsx
and add useEffect to update when host changes (P2 issue)
- Fix duplicate Host import in SftpModalHeader.tsx
Co-Authored-By: Claude <noreply@anthropic.com>
Merged the latest changes from main including:
- Bundled folder uploads with UploadController
- Fast delete using SSH exec
- UI improvements and bug fixes
Resolved conflicts in:
- useSftpExternalOperations.ts
- SFTPModal.tsx
- sftpBridge.cjs
Co-Authored-By: Claude <noreply@anthropic.com>
Adopts a "fail-fast" strategy for SFTP uploads, stopping the entire transfer process upon encountering any error during file or directory operations.
Introduces a new `isFatalUploadError` helper to consolidate and expand the definition of errors that should halt an upload, now explicitly including cases where the target directory is deleted or inaccessible during the transfer.
Removes specific fatal error checks from various components, streamlining error propagation and simplifying the overall error handling logic.
Ensures "Scanning files..." placeholder tasks are consistently removed when actual upload tasks are added, preventing potential state inconsistencies.
Refactors the SFTP upload mechanism to provide a more unified and robust experience.
- **Bundles folder uploads**: When uploading a folder from the local machine, it now appears as a single, aggregated task in the UI, showing overall progress instead of individual files.
- **Enhances cancellation**: Implements a new upload service and controller to manage transfers, allowing for more immediate and reliable cancellation of both individual files and bundled folder uploads.
- **Improves UI feedback**: Adds dedicated buttons for cancelling active uploads and dismissing completed, failed, or cancelled tasks.
- **Faster folder deletion**: Utilizes SSH `rm -rf` command for rapid remote folder deletion, falling back to SFTP rmdir if SSH exec is unavailable.
- Updates internationalization keys for single item deletion confirmation.
- Bundle folder uploads as single tasks showing aggregate progress
- Add unique file transfer IDs for proper cancellation tracking
- Fix cancel button to call cancelExternalUpload for external uploads
- Improve backend cancel detection using cancelled flag instead of error message
- Use SSH exec with rm -rf for fast folder deletion on remote servers
- Add FolderUp icon for folder upload tasks in transfer queue
- Add i18n key for upload cancelled message
Co-Authored-By: Claude <noreply@anthropic.com>