cralph 1.0.0-beta.0 → 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -140,13 +140,19 @@ Use `--yes` to skip confirmation (for CI/automation).
140
140
  - **Enter** - Confirm
141
141
  - **Ctrl+C** - Exit
142
142
 
143
+ ## Platform Notes
144
+
145
+ ### macOS Protected Directories
146
+
147
+ cralph gracefully handles macOS permission errors (`EPERM`, `EACCES`) when scanning directories. Protected locations like `~/Pictures/Photo Booth Library` or iCloud folders are silently skipped, allowing the CLI to run from any directory including root (`/`).
148
+
143
149
  ## Testing
144
150
 
145
151
  ```bash
146
152
  bun test
147
153
  ```
148
154
 
149
- - **Unit tests** - Config, prompt building, CLI
155
+ - **Unit tests** - Config, prompt building, CLI, access error handling
150
156
  - **E2E tests** - Full loop with Claude (requires auth)
151
157
 
152
158
  ## Requirements
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cralph",
3
- "version": "1.0.0-beta.0",
3
+ "version": "1.0.0-beta.1",
4
4
  "description": "Claude in a loop. Point at refs, give it a rule, let it cook.",
5
5
  "author": "mguleryuz",
6
6
  "license": "MIT",
package/src/paths.ts CHANGED
@@ -11,10 +11,29 @@ const CONTROLS = dim("↑↓ Navigate • Space Toggle • Enter • Ctrl+C Exit
11
11
  * List directories in a given path
12
12
  */
13
13
  async function listDirectories(basePath: string): Promise<string[]> {
14
- const entries = await readdir(basePath, { withFileTypes: true });
15
- return entries
16
- .filter((e) => e.isDirectory() && !e.name.startsWith("."))
17
- .map((e) => e.name);
14
+ try {
15
+ const entries = await readdir(basePath, { withFileTypes: true });
16
+ return entries
17
+ .filter((e) => e.isDirectory() && !e.name.startsWith("."))
18
+ .map((e) => e.name);
19
+ } catch (error) {
20
+ // Silently skip directories we can't access (EPERM, EACCES)
21
+ if (isAccessError(error)) {
22
+ return [];
23
+ }
24
+ throw error;
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Check if an error is a permission/access error
30
+ */
31
+ export function isAccessError(error: unknown): boolean {
32
+ if (error && typeof error === "object" && "code" in error) {
33
+ const code = (error as { code: string }).code;
34
+ return code === "EPERM" || code === "EACCES";
35
+ }
36
+ return false;
18
37
  }
19
38
 
20
39
  /**
@@ -37,7 +56,7 @@ const EXCLUDED_DIRS = [
37
56
  /**
38
57
  * List directories recursively up to a certain depth
39
58
  */
40
- async function listDirectoriesRecursive(
59
+ export async function listDirectoriesRecursive(
41
60
  basePath: string,
42
61
  maxDepth: number = 3
43
62
  ): Promise<string[]> {
@@ -46,7 +65,17 @@ async function listDirectoriesRecursive(
46
65
  async function walk(dir: string, depth: number) {
47
66
  if (depth > maxDepth) return;
48
67
 
49
- const entries = await readdir(dir, { withFileTypes: true });
68
+ let entries;
69
+ try {
70
+ entries = await readdir(dir, { withFileTypes: true });
71
+ } catch (error) {
72
+ // Silently skip directories we can't access (EPERM, EACCES)
73
+ if (isAccessError(error)) {
74
+ return;
75
+ }
76
+ throw error;
77
+ }
78
+
50
79
  for (const entry of entries) {
51
80
  // Skip hidden and excluded directories
52
81
  if (!entry.isDirectory()) continue;
@@ -66,14 +95,24 @@ async function listDirectoriesRecursive(
66
95
  /**
67
96
  * List files matching patterns in a directory (recursive)
68
97
  */
69
- async function listFilesRecursive(
98
+ export async function listFilesRecursive(
70
99
  basePath: string,
71
100
  extensions: string[]
72
101
  ): Promise<string[]> {
73
102
  const results: string[] = [];
74
103
 
75
104
  async function walk(dir: string) {
76
- const entries = await readdir(dir, { withFileTypes: true });
105
+ let entries;
106
+ try {
107
+ entries = await readdir(dir, { withFileTypes: true });
108
+ } catch (error) {
109
+ // Silently skip directories we can't access (EPERM, EACCES)
110
+ if (isAccessError(error)) {
111
+ return;
112
+ }
113
+ throw error;
114
+ }
115
+
77
116
  for (const entry of entries) {
78
117
  const fullPath = join(dir, entry.name);
79
118
  if (entry.isDirectory()) {