fix: disable sandbox for ~/.claude access, add path input field for hidden folders
- Remove App Sandbox from Release entitlements (developer tool needs filesystem access to ~/.claude/projects — same as VS Code, iTerm2) - Explicitly set get-task-allow=false in entitlements for notarization - Add path input field in header so users can type paths with hidden folders (e.g. ~/.claude/projects) — press Enter or click arrow to scan - Field pre-populated with ~/.claude/projects on launch - Signed, notarized, stapled DMG
This commit is contained in:
parent
5c693bf3d8
commit
53ed5a6cd1
@ -101,13 +101,24 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
String _searchQuery = '';
|
String _searchQuery = '';
|
||||||
_Project? _selectedProject;
|
_Project? _selectedProject;
|
||||||
_Project? _customFolderProject;
|
_Project? _customFolderProject;
|
||||||
|
final _pathController = TextEditingController();
|
||||||
|
final _pathFocusNode = FocusNode();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
final home = _getRealHome() ?? '';
|
||||||
|
_pathController.text = '$home/.claude/projects';
|
||||||
_scanProjects();
|
_scanProjects();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_pathController.dispose();
|
||||||
|
_pathFocusNode.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the real user home directory, even inside App Sandbox.
|
/// Returns the real user home directory, even inside App Sandbox.
|
||||||
/// In sandbox, HOME points to ~/Library/Containers/<bundleid>/Data.
|
/// In sandbox, HOME points to ~/Library/Containers/<bundleid>/Data.
|
||||||
String? _getRealHome() {
|
String? _getRealHome() {
|
||||||
@ -291,6 +302,35 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolve a path string, expanding ~ to the home directory.
|
||||||
|
String _resolvePath(String input) {
|
||||||
|
final home = _getRealHome() ?? Platform.environment['HOME'] ?? '';
|
||||||
|
var path = input.trim();
|
||||||
|
if (path.startsWith('~/')) {
|
||||||
|
path = '$home${path.substring(1)}';
|
||||||
|
} else if (path == '~') {
|
||||||
|
path = home;
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _scanFromPathInput() async {
|
||||||
|
final resolved = _resolvePath(_pathController.text);
|
||||||
|
final dir = Directory(resolved);
|
||||||
|
if (await dir.exists()) {
|
||||||
|
await _scanFolder(resolved);
|
||||||
|
} else {
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Folder not found: $resolved'),
|
||||||
|
backgroundColor: AppColors.error,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _pickFolder() async {
|
Future<void> _pickFolder() async {
|
||||||
final result = await FilePicker.platform.getDirectoryPath(
|
final result = await FilePicker.platform.getDirectoryPath(
|
||||||
dialogTitle: 'Select a folder containing Claude session files',
|
dialogTitle: 'Select a folder containing Claude session files',
|
||||||
@ -498,7 +538,54 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
: '${_projects.length} projects',
|
: '${_projects.length} projects',
|
||||||
style: const TextStyle(fontSize: 12, color: AppColors.textMuted),
|
style: const TextStyle(fontSize: 12, color: AppColors.textMuted),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
// Path input field
|
||||||
|
SizedBox(
|
||||||
|
width: 320,
|
||||||
|
child: TextField(
|
||||||
|
controller: _pathController,
|
||||||
|
focusNode: _pathFocusNode,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: AppColors.textPrimary,
|
||||||
|
fontFamily: 'JetBrains Mono',
|
||||||
|
),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: '~/ .claude/projects',
|
||||||
|
hintStyle: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: AppColors.textMuted,
|
||||||
|
fontFamily: 'JetBrains Mono',
|
||||||
|
),
|
||||||
|
isDense: true,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 10,
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
filled: true,
|
||||||
|
fillColor: AppColors.surface,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
borderSide: const BorderSide(color: AppColors.surfaceBorder),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
borderSide: const BorderSide(color: AppColors.surfaceBorder),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
borderSide: const BorderSide(color: AppColors.assistant),
|
||||||
|
),
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_forward, size: 14,
|
||||||
|
color: AppColors.textMuted),
|
||||||
|
tooltip: 'Scan this folder',
|
||||||
|
onPressed: _scanFromPathInput,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onSubmitted: (_) => _scanFromPathInput(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
ElevatedButton.icon(
|
ElevatedButton.icon(
|
||||||
onPressed: _loadSingleFile,
|
onPressed: _loadSingleFile,
|
||||||
icon: const Icon(Icons.insert_drive_file_outlined, size: 14),
|
icon: const Icon(Icons.insert_drive_file_outlined, size: 14),
|
||||||
|
|||||||
@ -724,6 +724,7 @@
|
|||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
"DEVELOPMENT_TEAM[sdk=macosx*]" = LD76P8L42W;
|
"DEVELOPMENT_TEAM[sdk=macosx*]" = LD76P8L42W;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
|
CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION = YES;
|
||||||
OTHER_CODE_SIGN_FLAGS = "--timestamp --options runtime";
|
OTHER_CODE_SIGN_FLAGS = "--timestamp --options runtime";
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
|||||||
@ -2,11 +2,7 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.security.app-sandbox</key>
|
<key>com.apple.security.get-task-allow</key>
|
||||||
<true/>
|
<false/>
|
||||||
<key>com.apple.security.files.user-selected.read-only</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.security.files.home-directory.read-only</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user