macOS app for managing shell configuration through a native GUI.
xcodegen generate # Regenerate .xcodeproj from project.yml
xcodebuild -project ShellCraft.xcodeproj -scheme ShellCraft -configuration Debug \
-derivedDataPath ~/Library/Developer/Xcode/DerivedData/ShellCraft-btghkkwhgqrpkvcxxrffbfehbzie build
open ~/Library/Developer/Xcode/DerivedData/ShellCraft-btghkkwhgqrpkvcxxrffbfehbzie/Build/Products/Debug/ShellCraft.appAlways launch the app after a successful build.
Automated via release.sh. The script handles version bump, build, code signing, notarization, packaging, git tag, and GitHub release creation.
./release.sh # Interactive: prompts for version bump type
./release.sh --bump patch # Non-interactive: auto-selects bump type (patch/minor/major)
./release.sh --dry-run # Preview version changes without executing
./release.sh --skip-notarize # Skip notarization (for testing builds)- Developer ID signing — handled automatically via Xcode managed signing. If export fails, install a Developer ID Application certificate from developer.apple.com
- Notarytool keychain profile — run:
xcrun notarytool store-credentials "ShellCraft" --apple-id YOUR_APPLE_ID --team-id N9DRSTM2U6 - GitHub CLI —
brew install ghand authenticate withgh auth login
- Preflight checks (tools, certificates, clean git, main branch)
- Prompts for version bump (patch/minor/major/custom) and increments build number
- Updates
project.yml, regenerates.xcodeproj(with icon fix) - Archives and exports with Developer ID signing
- Submits to Apple notary service and staples the ticket
- Packages as
Releases/ShellCraft-{version}.zip - Commits version bump, tags
v{version}, pushes - Creates GitHub release with auto-generated notes
Releases/ShellCraft-{version}.zip— notarized release artifact (gitignored)- Build artifacts in
build/are cleaned up automatically
- XcodeGen:
project.ymlis the source of truth. Never edit.xcodeprojmanually — runxcodegen generateafter adding/removing files. - Deployment target: macOS 26.0 (Tahoe)
- Swift 6 with strict concurrency (
SWIFT_STRICT_CONCURRENCY: complete) - No SPM dependencies — pure SwiftUI + AppKit
XcodeGen doesn't natively understand .icon bundles (Icon Composer format). Two issues arise:
- Folder expansion: XcodeGen recurses into
ShellCraft.icon/and addsicon.json/Assets/as individual files instead of treating it as an opaque folder reference. - Wrong file type: XcodeGen's
type: folderproduceslastKnownFileType = folder, but Xcode needsfolder.iconcomposer.iconto recognize it as an app icon.
Fix in project.yml: Exclude from normal sources, add back as folder reference, and set the icon name:
sources:
- path: ShellCraft
excludes:
- "ShellCraft.icon"
- path: ShellCraft/ShellCraft.icon
type: folder
buildPhase: resources
settings:
base:
ASSETCATALOG_COMPILER_APPICON_NAME: ShellCraftPost-generation finalizer: options.postGenCommand runs
scripts/finalize-project.sh. It patches the Icon Composer PBX reference before
calling Apple's converter. Release and CI generation use this same hook; do not
apply the old sed command after generation because the PBX file has become
project.xcproj.
XcodeGen checks converter availability before generation, then runs
scripts/finalize-project.sh after generation. The script uses
Apple's xcodebuild -convert-project 'Xcode Project' to write
project.xcproj inside the existing .xcodeproj bundle. Generation requires an
installed Xcode 27.2 or later; it uses DEVELOPER_DIR when suitable, otherwise
finds a compatible Xcode in /Applications. This selection applies only to the
conversion process and does not change the selected build toolchain. Set
XCODE_PROJECT_CONVERTER_DIR to require a specific converter without discovery.
If conversion fails, the finalizer restores the previous JSON (or the initial
PBX project) and retains failed output for diagnosis. Xcode 27
and later can read the generated JSON project. Do not run a PBX-only patch after
the finalizer.
- MVVM with
@MainActor @ObservableViewModels AppState— environment object for sidebar selection and unsaved-changes tracking per section- Round-trip safe writes — raw
.zshrclines are kept in memory; only targeted lines are modified viaShellConfigWriter.Modification ProcessServicewraps shell commands via/bin/zsh -cFileIOServicehandles all file reads/writes with atomic writes and automatic backup
ShellCraft/
├── App/ # ShellCraftApp, AppState
├── Models/ # Data models (ShellAlias, OhMyZshPlugin, etc.)
├── Services/ # Business logic (ShellConfigParser, OhMyZshService, etc.)
├── ViewModels/ # @MainActor @Observable VMs — one per sidebar section
├── Views/
│ ├── Sidebar/ # SidebarView, SidebarSection enum
│ ├── Shell/ # Aliases, Functions
│ ├── OhMyZsh/ # Oh My Zsh themes/plugins/settings
│ ├── Path/ # PATH manager
│ ├── Environment/ # Env vars
│ ├── Claude/ # Claude Code settings (tabbed)
│ ├── Git/ # Git config
│ ├── SSH/ # SSH config
│ ├── Secrets/ # Keychain secrets
│ ├── Tools/ # Custom tools
│ ├── Homebrew/ # Homebrew packages
│ └── Shared/ # Reusable: SaveBar, ImportExportToolbar, SearchableList, etc.
├── Extensions/ # String+Shell, URL+Home
├── Utilities/ # ShellLineParser, FileWatcher, PathValidator
└── Resources/ # Assets.xcassets
- Add case to
SidebarSectionenum (SidebarSection.swift) — setdisplayName,icon,group - Add routing in
ContentView.detailView(for:) - Create Model, Service, ViewModel, and View files following existing patterns
- Run
xcodegen generateto pick up new files
- SaveBar pattern: All editable sections show a
SaveBarat the bottom whenhasUnsavedChangesis true. Changes are only written on explicit Save; Discard reverts to the loaded state. - Import/Export: Each section implements
exportData(),previewImport(_:),applyImport(_:). Export usesImportExportService.export()withNSSavePanel. Import shows anImportConfirmationSheetpreview before applying. - Dirty tracking: ViewModels compare current state against a saved snapshot (e.g.,
savedSnapshot,originalTheme,originalEnabledPlugins). ShellConfigWriter.Modification: All.zshrcwrites go through.updateLine,.insertAfter,.deleteLine, or.appendLine— applied in reverse index order for safe multi-edit.- Tabbed sections (Claude Code, Oh My Zsh): Use
TabViewwith.tabViewStyle(.grouped)and a tab enum withrawValue/iconproperties.
.expandingTildeInPath— expands~to home directory.shellEscaped— escapes special shell characters.singleQuoted/.doubleQuoted— wraps for shell safety.abbreviatingWithTildeInPath— replaces home dir with~.trimmed— trims whitespace/newlines
This repo uses Clawpatch for local automated code review. Keep .clawpatch/ ignored; it is generated runtime state containing features, findings, reports, runs, and patch attempts.
Standard workflow:
clawpatch doctor
clawpatch init # first time only
clawpatch map
clawpatch review --limit 10
clawpatch report --output .clawpatch/reports/summary.md
clawpatch show --finding <id>
clawpatch fix --finding <id>
clawpatch revalidate --finding <id>If this repo needs hand-authored feature coverage, keep those curated definitions in tools/clawpatch/features/ and sync/copy them into .clawpatch/features/ before review. Do not commit .clawpatch/ generated state.
Durable memory promoted from ~/.claude/projects/-Users-omarshahine-GitHub-ShellCraft/memory during the AGENTS.md migration. Keep this section current when project-specific operating knowledge changes.
- Always launch the app after building — run
openon the built.appfrom DerivedData after every successfulxcodebuild
- macOS 26.0 (Tahoe) deployment target, Swift 6 strict concurrency
- Uses XcodeGen (
project.yml) to generateShellCraft.xcodeproj - Build output:
~/Library/Developer/Xcode/DerivedData/ShellCraft-btghkkwhgqrpkvcxxrffbfehbzie/Build/Products/Debug/ShellCraft.app - Uses
@MainActor @Observablepattern for ViewModels ProcessServicewraps shell commands via/bin/zsh -cString+Shell.swiftprovides.trimmed,.singleQuoted,.shellEscapedextensions
- See import-export-progress.md for detailed status
ShellCraft/Services/ImportExportService.swift— NSSavePanel/NSOpenPanel wrappers,ExportFileType,ImportPreview: Identifiable,shellHeader(section:),importFileURL()ShellCraft/Views/Shared/ImportExportToolbar.swift— ReusableMenuwith Export/Import buttonsShellCraft/Views/Shared/ImportConfirmationSheet.swift— Shared confirmation sheet showing preview
| Section | ViewModel Methods | View Integration | Format | Import Style |
|---|---|---|---|---|
| Aliases | exportData(), previewImport(), applyImport() |
Toolbar + sheet + helpers | .sh |
Merge by name |
| Functions | exportData(), previewImport(), applyImport() |
Toolbar + sheet + helpers | .sh |
Merge by name |
| PATH | exportData(), previewImport(), applyImport() |
Toolbar + sheet + helpers | .sh |
Merge (skip dupes) |
| Env Vars | exportData(), previewImport(), applyImport() |
Toolbar + sheet + helpers | .sh |
Merge by key |
| Git | exportData(), previewImport(), applyImport() |
Toolbar + sheet + helpers | .gitconfig |
Replace |
| Claude | exportData(), previewImport(), applyImport() |
Toolbar + sheet + helpers | .json |
Replace |
| SSH | exportData(), previewImport(), applyImport() |
Toolbar + sheet + helpers (hosts only) | .txt |
Merge by Host |
| Secrets | exportData(), previewImport(), applyImport() (async) |
Toolbar + sheet + helpers | .json |
Merge by serviceName |
| Custom Tools | exportData(), previewImport(), applyImport() |
Toolbar + sheet + helpers | .json |
Merge by name |
| Homebrew | exportData(), importBrewfile(at:) |
Toolbar + confirmation alert | Brewfile |
brew bundle direct |
- Builds successfully with
xcodegen generate && xcodebuild - App launches and runs
A planned new sidebar section for managing Oh My Zsh themes, plugins, and settings. See plan file for full details.