cralph 1.0.0-alpha.6 → 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.
Files changed (3) hide show
  1. package/README.md +11 -7
  2. package/package.json +1 -1
  3. package/src/paths.ts +47 -8
package/README.md CHANGED
@@ -26,13 +26,11 @@ cralph wraps this into a CLI with config, logging, and TODO tracking.
26
26
  ## Install
27
27
 
28
28
  ```bash
29
- bun add -g cralph
30
- ```
29
+ # Install Bun (if not already installed)
30
+ curl -fsSL https://bun.sh/install | bash
31
31
 
32
- Or with npm:
33
-
34
- ```bash
35
- npm install -g cralph
32
+ # Install cralph
33
+ bun add -g cralph
36
34
  ```
37
35
 
38
36
  ## Quick Start
@@ -142,13 +140,19 @@ Use `--yes` to skip confirmation (for CI/automation).
142
140
  - **Enter** - Confirm
143
141
  - **Ctrl+C** - Exit
144
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
+
145
149
  ## Testing
146
150
 
147
151
  ```bash
148
152
  bun test
149
153
  ```
150
154
 
151
- - **Unit tests** - Config, prompt building, CLI
155
+ - **Unit tests** - Config, prompt building, CLI, access error handling
152
156
  - **E2E tests** - Full loop with Claude (requires auth)
153
157
 
154
158
  ## Requirements
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cralph",
3
- "version": "1.0.0-alpha.6",
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()) {