openralph 0.0.1 → 0.0.2
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/LICENSE +21 -0
- package/README.md +291 -0
- package/bin/ralph +51 -0
- package/package.json +26 -7
- package/postinstall.mjs +145 -0
- package/index.js +0 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Luke Parker
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
# openralph
|
|
2
|
+
|
|
3
|
+
AI agent loop for autonomous task execution. Reads a PRD, picks one task, completes it, commits, repeats.
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<img src="ralph-task.jpg" alt="Ralph TUI - Task view with PRD items" width="100%" />
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<img src="ralph-output details.jpg" alt="Ralph TUI - Output view showing agent activity" width="100%" />
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
<p align="center">
|
|
14
|
+
<img src="openralph-terminal.jpg" alt="Ralph with openralph terminal side-by-side" width="100%" />
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
## Acknowledgements
|
|
18
|
+
|
|
19
|
+
openralph is a fork of the original `opencode-ralph` project. Huge thanks to Hona for the original work and inspiration: https://github.com/hona/opencode-ralph.
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Install stable release
|
|
25
|
+
bun install -g openralph
|
|
26
|
+
|
|
27
|
+
# Or install dev snapshot (latest from dev branch)
|
|
28
|
+
bun install -g openralph@dev
|
|
29
|
+
|
|
30
|
+
# Initialize PRD, progress log, and prompt
|
|
31
|
+
ralph init
|
|
32
|
+
|
|
33
|
+
# Run in any project directory
|
|
34
|
+
ralph
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Install from Source
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
git clone https://github.com/shuv1337/openralph.git
|
|
41
|
+
cd openralph
|
|
42
|
+
bun install
|
|
43
|
+
bun run build:single # compiles for current platform
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## What is Ralph?
|
|
47
|
+
|
|
48
|
+
Ralph-driven development forces an AI agent to re-read full context every iteration, eliminating context drift. Each loop:
|
|
49
|
+
|
|
50
|
+
1. Read `prd.json`
|
|
51
|
+
2. Pick ONE task
|
|
52
|
+
3. Complete it
|
|
53
|
+
4. Commit (updating the PRD in the same commit)
|
|
54
|
+
5. Repeat until done
|
|
55
|
+
|
|
56
|
+
The agent never pushes—only commits—so you maintain review control.
|
|
57
|
+
|
|
58
|
+
**Why it works:**
|
|
59
|
+
- Deterministic failures are debuggable. When Ralph fails, fix the prompt.
|
|
60
|
+
- `AGENTS.md` accumulates wisdom so future iterations don't rediscover fire.
|
|
61
|
+
- Human review checkpoint before anything goes live.
|
|
62
|
+
|
|
63
|
+
See: [ghuntley.com/ralph](https://ghuntley.com/ralph/) · [lukeparker.dev/stop-chatting-with-ai-start-loops-ralph-driven-development](https://lukeparker.dev/stop-chatting-with-ai-start-loops-ralph-driven-development)
|
|
64
|
+
|
|
65
|
+
## Usage
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
ralph # uses prd.json in current directory
|
|
69
|
+
ralph --plan BACKLOG.json # different PRD file
|
|
70
|
+
ralph --progress progress.txt # custom progress log
|
|
71
|
+
ralph --model anthropic/claude-opus-4 # different model
|
|
72
|
+
ralph --reset # remove generated files and state, then exit
|
|
73
|
+
ralph init --from plan.md # convert unstructured plan to PRD JSON
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
| Option | Default | Description |
|
|
77
|
+
|--------|---------|-------------|
|
|
78
|
+
| `--plan, -p` | `prd.json` | PRD file path |
|
|
79
|
+
| `--progress` | `progress.txt` | Progress log path |
|
|
80
|
+
| `--model, -m` | `opencode/claude-opus-4-5` | Model (provider/model format) |
|
|
81
|
+
| `--adapter` | `opencode-server` | Adapter (opencode-server, opencode-run, codex) |
|
|
82
|
+
| `--prompt` | see below | Custom prompt (`{plan}` and `{progress}` placeholders) |
|
|
83
|
+
| `--prompt-file` | `.ralph-prompt.md` | Prompt file path |
|
|
84
|
+
| `--reset, -r` | `false` | Remove generated files and state, then exit |
|
|
85
|
+
| `--headless, -H` | `false` | CI-friendly output |
|
|
86
|
+
| `--format` | `text` | Headless output format (text, jsonl, json) |
|
|
87
|
+
| `--timestamps` | `false` | Include timestamps in headless output |
|
|
88
|
+
| `--max-iterations` | (none) | Cap iterations (headless) |
|
|
89
|
+
| `--max-time` | (none) | Cap runtime seconds (headless) |
|
|
90
|
+
| `--server, -s` | (none) | OpenCode server URL |
|
|
91
|
+
| `--server-timeout` | `5000` | Health check timeout in ms |
|
|
92
|
+
| `--agent, -a` | (none) | Agent name (e.g., build/plan/general) |
|
|
93
|
+
| `--debug, -d` | `false` | Manual session creation |
|
|
94
|
+
| `--yes` | `false` | Auto-confirm prompts |
|
|
95
|
+
| `--auto-reset` | `true` | Auto-reset when no TTY prompt |
|
|
96
|
+
|
|
97
|
+
### Init Subcommand
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
ralph init # create template PRD and prompt
|
|
101
|
+
ralph init --from plan.md # convert markdown plan to PRD JSON
|
|
102
|
+
ralph init --force # overwrite existing files
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
| Option | Description |
|
|
106
|
+
|--------|-------------|
|
|
107
|
+
| `--from` | Source plan or notes to convert into PRD JSON |
|
|
108
|
+
| `--force` | Overwrite existing files |
|
|
109
|
+
|
|
110
|
+
**Default prompt:**
|
|
111
|
+
```
|
|
112
|
+
READ all of {plan} and {progress}. Pick ONE task with passes=false (prefer highest-risk/highest-impact). Keep changes small: one logical change per commit. Update {plan} by setting passes=true and adding notes or steps as needed. Append a brief entry to {progress} with what changed and why. Run feedback loops before committing: bun run typecheck, bun test, bun run lint (if missing, note it in {progress} and continue). Commit change (update {plan} in the same commit). ONLY do one task unless GLARINGLY OBVIOUS steps should run together. Quality bar: production code, maintainable, tests when appropriate. If you learn a critical operational detail, update AGENTS.md. When ALL tasks complete, create .ralph-done and output <promise>COMPLETE</promise>. NEVER GIT PUSH. ONLY COMMIT.
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Configuration
|
|
116
|
+
|
|
117
|
+
Ralph reads configuration from `~/.config/ralph/config.json`:
|
|
118
|
+
|
|
119
|
+
```json
|
|
120
|
+
{
|
|
121
|
+
"model": "opencode/claude-opus-4-5",
|
|
122
|
+
"plan": "prd.json",
|
|
123
|
+
"progress": "progress.txt",
|
|
124
|
+
"adapter": "opencode-server",
|
|
125
|
+
"server": "http://localhost:4190",
|
|
126
|
+
"serverTimeout": 5000
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
CLI arguments override config file values.
|
|
131
|
+
|
|
132
|
+
### Adapters
|
|
133
|
+
|
|
134
|
+
Ralph supports multiple adapters for running the AI agent:
|
|
135
|
+
|
|
136
|
+
| Adapter | Description |
|
|
137
|
+
|---------|-------------|
|
|
138
|
+
| `opencode-server` | Default. Connects to OpenCode server via SDK |
|
|
139
|
+
| `opencode-run` | Spawns `opencode run` as PTY subprocess |
|
|
140
|
+
| `codex` | Spawns OpenAI Codex CLI as PTY subprocess |
|
|
141
|
+
|
|
142
|
+
## Workflow Files
|
|
143
|
+
|
|
144
|
+
| File | Purpose |
|
|
145
|
+
|------|---------|
|
|
146
|
+
| `prd.json` | PRD plan items with `passes` state |
|
|
147
|
+
| `progress.txt` | Progress log appended each iteration |
|
|
148
|
+
| `.ralph-prompt.md` | Prompt template used for loop runs |
|
|
149
|
+
| `.ralph-state.json` | Persisted state for resume after Ctrl+C |
|
|
150
|
+
| `.ralph-lock` | Prevents multiple instances |
|
|
151
|
+
| `.ralph-done` | Agent creates this when all tasks complete |
|
|
152
|
+
| `.ralph-pause` | Created by `p` key to pause loop |
|
|
153
|
+
|
|
154
|
+
**Generated file markers:** Files created by `ralph init` include markers so `ralph --reset` can identify and remove them safely without touching user-created files:
|
|
155
|
+
- `prd.json`: Wrapped with `{ metadata: { generated: true, ... }, items: [...] }`
|
|
156
|
+
- `.ralph-prompt.md`: YAML frontmatter with `generated: true`
|
|
157
|
+
- `progress.txt`: Contains "Initialized via ralph init." marker
|
|
158
|
+
|
|
159
|
+
Add to `.gitignore`:
|
|
160
|
+
```
|
|
161
|
+
.ralph-*
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Writing PRDs
|
|
165
|
+
|
|
166
|
+
Prefer PRD JSON with `passes` flags so Ralph can track scope and progress. Two formats are supported:
|
|
167
|
+
|
|
168
|
+
**Plain array (user-created):**
|
|
169
|
+
```json
|
|
170
|
+
[
|
|
171
|
+
{
|
|
172
|
+
"category": "functional",
|
|
173
|
+
"description": "Create the CLI entry point",
|
|
174
|
+
"steps": [
|
|
175
|
+
"Run the CLI with --help",
|
|
176
|
+
"Verify the help output renders"
|
|
177
|
+
],
|
|
178
|
+
"passes": false
|
|
179
|
+
}
|
|
180
|
+
]
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Wrapped format (generated by `ralph init`):**
|
|
184
|
+
```json
|
|
185
|
+
{
|
|
186
|
+
"metadata": {
|
|
187
|
+
"generated": true,
|
|
188
|
+
"generator": "ralph-init",
|
|
189
|
+
"createdAt": "2025-01-13T00:00:00.000Z",
|
|
190
|
+
"sourceFile": "plan.md"
|
|
191
|
+
},
|
|
192
|
+
"items": [
|
|
193
|
+
{
|
|
194
|
+
"category": "functional",
|
|
195
|
+
"description": "Create the CLI entry point",
|
|
196
|
+
"passes": false
|
|
197
|
+
}
|
|
198
|
+
]
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**Guidelines:**
|
|
203
|
+
- Small, isolated tasks—one commit each
|
|
204
|
+
- Explicit verification steps
|
|
205
|
+
- Set `passes` to true only when verified
|
|
206
|
+
- 1000+ lines is normal; more detail = fewer hallucinations
|
|
207
|
+
|
|
208
|
+
Legacy markdown checkboxes are still supported, but `ralph init --from plan.md` is the recommended upgrade path.
|
|
209
|
+
|
|
210
|
+
## Progress Log
|
|
211
|
+
|
|
212
|
+
Append a short entry each iteration. Example:
|
|
213
|
+
|
|
214
|
+
```
|
|
215
|
+
## Iteration 3 - 2025-01-10T12:34:56Z
|
|
216
|
+
- Task: Wire up API client
|
|
217
|
+
- Checks: typecheck, test
|
|
218
|
+
- Commit: abc123
|
|
219
|
+
- Notes: Added retry logic for timeouts
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## AGENTS.md
|
|
223
|
+
|
|
224
|
+
Ralph writes operational learnings here. Future iterations read it.
|
|
225
|
+
|
|
226
|
+
```markdown
|
|
227
|
+
# AGENTS.md
|
|
228
|
+
|
|
229
|
+
## Build
|
|
230
|
+
- Run `bun install` before `bun run dev`
|
|
231
|
+
|
|
232
|
+
## Pitfalls
|
|
233
|
+
- Never import from `solid-js`, use `@opentui/solid`
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Keybindings
|
|
237
|
+
|
|
238
|
+
| Key | Action |
|
|
239
|
+
|-----|--------|
|
|
240
|
+
| `q` / `Ctrl+C` | Quit |
|
|
241
|
+
| `p` | Pause/Resume loop |
|
|
242
|
+
| `c` | Open command palette |
|
|
243
|
+
| `:` | Steering mode (send message to agent) |
|
|
244
|
+
| `t` | Launch terminal with attach command |
|
|
245
|
+
| `Shift+T` | Toggle tasks panel |
|
|
246
|
+
| `o` | Toggle Details/Output view |
|
|
247
|
+
| `d` | Toggle progress dashboard |
|
|
248
|
+
| `?` | Show help overlay |
|
|
249
|
+
| `↑` / `k` | Navigate up (in tasks panel) |
|
|
250
|
+
| `↓` / `j` | Navigate down (in tasks panel) |
|
|
251
|
+
| `n` | New session (debug mode only) |
|
|
252
|
+
| `Escape` | Close overlay/panel |
|
|
253
|
+
|
|
254
|
+
## Architecture
|
|
255
|
+
|
|
256
|
+
```
|
|
257
|
+
src/
|
|
258
|
+
├── index.ts # CLI entry, wires TUI to loop
|
|
259
|
+
├── loop.ts # Main agent loop (prompt → events → commit)
|
|
260
|
+
├── app.tsx # Solid.js TUI root component
|
|
261
|
+
├── state.ts # State types and persistence
|
|
262
|
+
├── plan.ts # PRD + markdown plan parser
|
|
263
|
+
├── git.ts # Git operations (hash, diff, commits)
|
|
264
|
+
├── lock.ts # Lock file management
|
|
265
|
+
├── prompt.ts # User confirmation prompts
|
|
266
|
+
├── components/ # TUI components (header, log, footer)
|
|
267
|
+
└── util/ # Helpers (time formatting, logging)
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
**Data flow:** `index.ts` starts the TUI (`app.tsx`) and the loop (`loop.ts`) in parallel. The loop sends callbacks to update TUI state. State persists to `.ralph-state.json` for resume capability.
|
|
271
|
+
|
|
272
|
+
## Testing
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
bun test # run all tests
|
|
276
|
+
bun test --watch # watch mode
|
|
277
|
+
bun test --coverage # with coverage
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
```
|
|
281
|
+
tests/
|
|
282
|
+
├── unit/ # Module isolation tests
|
|
283
|
+
├── integration/ # Full workflow tests
|
|
284
|
+
├── fixtures/ # Test plans and PRD JSON
|
|
285
|
+
└── helpers/ # Mock factories, temp file utils
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Requirements
|
|
289
|
+
|
|
290
|
+
- [Bun](https://bun.sh) v1.0+
|
|
291
|
+
- [OpenCode](https://opencode.ai) CLI running
|
package/bin/ralph
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { createRequire } from "node:module";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
|
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
|
|
12
|
+
const platformMap = {
|
|
13
|
+
darwin: "darwin",
|
|
14
|
+
linux: "linux",
|
|
15
|
+
win32: "windows",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const archMap = {
|
|
19
|
+
x64: "x64",
|
|
20
|
+
arm64: "arm64",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const platform = platformMap[os.platform()] ?? os.platform();
|
|
24
|
+
const arch = archMap[os.arch()] ?? os.arch();
|
|
25
|
+
const packageName = `openralph-${platform}-${arch}`;
|
|
26
|
+
const binaryName = platform === "windows" ? "ralph.exe" : "ralph";
|
|
27
|
+
|
|
28
|
+
let binaryPath;
|
|
29
|
+
try {
|
|
30
|
+
const packageJsonPath = require.resolve(`${packageName}/package.json`);
|
|
31
|
+
const packageDir = path.dirname(packageJsonPath);
|
|
32
|
+
binaryPath = path.join(packageDir, "bin", binaryName);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
35
|
+
console.error(`Failed to resolve ${packageName}: ${message}`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!fs.existsSync(binaryPath)) {
|
|
40
|
+
console.error(`Binary not found at ${binaryPath}`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const child = spawn(binaryPath, process.argv.slice(2), { stdio: "inherit" });
|
|
45
|
+
child.on("exit", (code) => {
|
|
46
|
+
process.exit(code ?? 0);
|
|
47
|
+
});
|
|
48
|
+
child.on("error", (error) => {
|
|
49
|
+
console.error(`Failed to run ${binaryName}: ${error.message}`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
});
|
package/package.json
CHANGED
|
@@ -1,10 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openralph",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
"
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "Ralph Driven Development using OpenCode SDK and OpenTUI",
|
|
5
|
+
"bin": {
|
|
6
|
+
"ralph": "./bin/ralph"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"postinstall": "node ./postinstall.mjs"
|
|
10
|
+
},
|
|
11
|
+
"optionalDependencies": {
|
|
12
|
+
"openralph-darwin-arm64": "0.0.2",
|
|
13
|
+
"openralph-darwin-x64": "0.0.2",
|
|
14
|
+
"openralph-linux-arm64": "0.0.2",
|
|
15
|
+
"openralph-linux-x64": "0.0.2",
|
|
16
|
+
"openralph-windows-x64": "0.0.2"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/shuv1337/openralph.git"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"cli",
|
|
24
|
+
"ai",
|
|
25
|
+
"coding",
|
|
26
|
+
"assistant"
|
|
27
|
+
],
|
|
9
28
|
"license": "MIT"
|
|
10
|
-
}
|
|
29
|
+
}
|
package/postinstall.mjs
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Postinstall script for openralph
|
|
4
|
+
*
|
|
5
|
+
* This script runs after `bun install -g openralph` and symlinks the
|
|
6
|
+
* platform-specific binary to the bin directory.
|
|
7
|
+
*
|
|
8
|
+
* On Windows, npm handles the .exe directly so we skip symlinking.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fs from "fs";
|
|
12
|
+
import path from "path";
|
|
13
|
+
import os from "os";
|
|
14
|
+
import { fileURLToPath } from "url";
|
|
15
|
+
import { createRequire } from "module";
|
|
16
|
+
|
|
17
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const require = createRequire(import.meta.url);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Detect the current platform and architecture
|
|
22
|
+
*/
|
|
23
|
+
function detectPlatformAndArch() {
|
|
24
|
+
let platform;
|
|
25
|
+
switch (os.platform()) {
|
|
26
|
+
case "darwin":
|
|
27
|
+
platform = "darwin";
|
|
28
|
+
break;
|
|
29
|
+
case "linux":
|
|
30
|
+
platform = "linux";
|
|
31
|
+
break;
|
|
32
|
+
case "win32":
|
|
33
|
+
platform = "windows";
|
|
34
|
+
break;
|
|
35
|
+
default:
|
|
36
|
+
platform = os.platform();
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let arch;
|
|
41
|
+
switch (os.arch()) {
|
|
42
|
+
case "x64":
|
|
43
|
+
arch = "x64";
|
|
44
|
+
break;
|
|
45
|
+
case "arm64":
|
|
46
|
+
arch = "arm64";
|
|
47
|
+
break;
|
|
48
|
+
default:
|
|
49
|
+
arch = os.arch();
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { platform, arch };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Find the platform-specific binary package
|
|
58
|
+
*/
|
|
59
|
+
function findBinary() {
|
|
60
|
+
const { platform, arch } = detectPlatformAndArch();
|
|
61
|
+
const packageName = `openralph-${platform}-${arch}`;
|
|
62
|
+
const binaryName = platform === "windows" ? "ralph.exe" : "ralph";
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
// Use require.resolve to find the package
|
|
66
|
+
const packageJsonPath = require.resolve(`${packageName}/package.json`);
|
|
67
|
+
const packageDir = path.dirname(packageJsonPath);
|
|
68
|
+
const binaryPath = path.join(packageDir, "bin", binaryName);
|
|
69
|
+
|
|
70
|
+
if (!fs.existsSync(binaryPath)) {
|
|
71
|
+
throw new Error(`Binary not found at ${binaryPath}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return { binaryPath, binaryName, packageName };
|
|
75
|
+
} catch (error) {
|
|
76
|
+
throw new Error(`Could not find package ${packageName}: ${error.message}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Prepare the bin directory and return paths
|
|
82
|
+
*/
|
|
83
|
+
function prepareBinDirectory(binaryName) {
|
|
84
|
+
const binDir = path.join(__dirname, "bin");
|
|
85
|
+
const targetPath = path.join(binDir, binaryName);
|
|
86
|
+
|
|
87
|
+
// Ensure bin directory exists
|
|
88
|
+
if (!fs.existsSync(binDir)) {
|
|
89
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Remove existing binary/symlink if it exists
|
|
93
|
+
if (fs.existsSync(targetPath)) {
|
|
94
|
+
fs.unlinkSync(targetPath);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return { binDir, targetPath };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Create symlink to the platform binary
|
|
102
|
+
*/
|
|
103
|
+
function symlinkBinary(sourcePath, binaryName) {
|
|
104
|
+
const { targetPath } = prepareBinDirectory(binaryName);
|
|
105
|
+
|
|
106
|
+
fs.symlinkSync(sourcePath, targetPath);
|
|
107
|
+
console.log(`ralph binary symlinked: ${targetPath} -> ${sourcePath}`);
|
|
108
|
+
|
|
109
|
+
// Verify the file exists after operation
|
|
110
|
+
if (!fs.existsSync(targetPath)) {
|
|
111
|
+
throw new Error(`Failed to symlink binary to ${targetPath}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Main function
|
|
117
|
+
*/
|
|
118
|
+
async function main() {
|
|
119
|
+
try {
|
|
120
|
+
// On Windows, npm handles the .exe directly via the bin field
|
|
121
|
+
// No postinstall symlinking needed
|
|
122
|
+
if (os.platform() === "win32") {
|
|
123
|
+
console.log(
|
|
124
|
+
"Windows detected: binary setup not needed (using packaged .exe)"
|
|
125
|
+
);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const { binaryPath, binaryName, packageName } = findBinary();
|
|
130
|
+
console.log(`Found ${packageName} at ${binaryPath}`);
|
|
131
|
+
symlinkBinary(binaryPath, binaryName);
|
|
132
|
+
} catch (error) {
|
|
133
|
+
console.error("Failed to setup ralph binary:", error.message);
|
|
134
|
+
// Don't fail the install - the JS launcher will handle finding the binary
|
|
135
|
+
process.exit(0);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
main();
|
|
141
|
+
} catch (error) {
|
|
142
|
+
console.error("Postinstall script error:", error.message);
|
|
143
|
+
// Exit gracefully - don't break npm install
|
|
144
|
+
process.exit(0);
|
|
145
|
+
}
|
package/index.js
DELETED