rw-runner 0.1.0

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Davontae Jackson
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,133 @@
1
+ # rw-runner
2
+
3
+ Autonomous task runner powered by Claude. Define tasks, set completion requirements, and let Claude work through them autonomously.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ # Using bun (recommended)
9
+ bun install -g rw-runner
10
+
11
+ # Using npm
12
+ npm install -g rw-runner
13
+ ```
14
+
15
+ **Requirements:** [Claude Code CLI](https://claude.ai/code) must be installed and authenticated.
16
+
17
+ ## Quick Start
18
+
19
+ ```bash
20
+ # Initialize rw in your project
21
+ rw init
22
+
23
+ # Add a task
24
+ rw add task
25
+
26
+ # Add context files Claude can reference
27
+ rw add resource
28
+
29
+ # Start the autonomous loop
30
+ rw run
31
+ ```
32
+
33
+ ## How It Works
34
+
35
+ ```
36
+ ┌─────────────────────────────────────────────────────────────────┐
37
+ │ RALPH LOOP │
38
+ ├─────────────────────────────────────────────────────────────────┤
39
+ │ │
40
+ │ 1. Claude picks the most important pending task │
41
+ │ 2. Claude works on it autonomously │
42
+ │ 3. Claude outputs "DONE: task name" │
43
+ │ 4. Ralph runs requirement checks (tests, commands) │
44
+ │ ├── PASS → mark complete, next task │
45
+ │ └── FAIL → retry with error context (max 3) │
46
+ │ 5. Repeat until all tasks complete │
47
+ │ │
48
+ │ HOTKEYS: │
49
+ │ [t] - takeover (jump into Claude session) │
50
+ │ [q] - quit │
51
+ │ │
52
+ └─────────────────────────────────────────────────────────────────┘
53
+ ```
54
+
55
+ ## Commands
56
+
57
+ | Command | Description |
58
+ |---------|-------------|
59
+ | `rw init` | Initialize rw in current directory |
60
+ | `rw add task` | Add a task interactively |
61
+ | `rw add resource` | Add a context file |
62
+ | `rw validate` | Check spec.md syntax |
63
+ | `rw status` | Show task completion status |
64
+ | `rw run` | Start the autonomous loop |
65
+
66
+ ## spec.md Format
67
+
68
+ ```markdown
69
+ # Spec
70
+
71
+ ## Tasks
72
+ - [ ] Implement user authentication
73
+ - [run]: `npm test`
74
+ [expect]: PASS
75
+ [expect-not]: FAIL
76
+
77
+ - [ ] Add rate limiting
78
+ - [run]: `go test ./...`
79
+ [expect-exit]: 0
80
+ - [run]: `curl localhost:3000/health`
81
+ [eval]: "Returns 200 OK"
82
+
83
+ ## Resources
84
+ [file]: `docs/architecture.md`
85
+ High-level system design. Read this first.
86
+
87
+ [file]: `docs/api.md`
88
+ API endpoint documentation.
89
+ ```
90
+
91
+ ### Task States
92
+
93
+ - `[ ]` - pending
94
+ - `[x]` - completed
95
+ - `[!]` - failed (skipped after 3 retries)
96
+
97
+ ### Requirement Checks
98
+
99
+ | Check | Description |
100
+ |-------|-------------|
101
+ | `[expect]` | Output must contain this string |
102
+ | `[expect-not]` | Output must NOT contain this string |
103
+ | `[expect-exit]` | Exit code must match |
104
+ | `[eval]` | LLM evaluates output against criteria |
105
+
106
+ ## Configuration
107
+
108
+ Global config at `~/.rw/config.yaml`:
109
+
110
+ ```yaml
111
+ # Model for LLM evaluations
112
+ model: claude-sonnet
113
+
114
+ # Max retries before marking task as failed
115
+ max_retries: 3
116
+
117
+ # Delay between loop iterations (seconds)
118
+ loop_delay: 2
119
+ ```
120
+
121
+ ## Takeover Mode
122
+
123
+ Press `t` during `rw run` to jump into the active Claude session. This lets you:
124
+
125
+ - Provide additional context
126
+ - Guide Claude through tricky parts
127
+ - Fix issues manually
128
+
129
+ When you exit (Ctrl+D or `/exit`), Ralph asks if you want to run requirements, then continues the loop.
130
+
131
+ ## License
132
+
133
+ MIT
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "rw-runner",
3
+ "version": "0.1.0",
4
+ "description": "Autonomous task runner powered by Claude",
5
+ "author": "Davontae Jackson",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/theworksofvon/rw-runner"
11
+ },
12
+ "homepage": "https://github.com/theworksofvon/rw-runner#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/theworksofvon/rw-runner/issues"
15
+ },
16
+ "keywords": [
17
+ "cli",
18
+ "claude",
19
+ "ai",
20
+ "automation",
21
+ "tasks",
22
+ "autonomous",
23
+ "agent",
24
+ "bun"
25
+ ],
26
+ "files": [
27
+ "src"
28
+ ],
29
+ "bin": {
30
+ "rw": "./src/index.ts"
31
+ },
32
+ "scripts": {
33
+ "start": "bun run src/index.ts",
34
+ "dev": "bun --watch src/index.ts",
35
+ "build": "bun build src/index.ts --compile --outfile rw",
36
+ "prepublishOnly": "echo 'Ready to publish!'"
37
+ },
38
+ "engines": {
39
+ "node": ">=18",
40
+ "bun": ">=1.0"
41
+ },
42
+ "devDependencies": {
43
+ "@types/bun": "latest"
44
+ },
45
+ "peerDependencies": {
46
+ "typescript": "^5"
47
+ },
48
+ "dependencies": {
49
+ "@inquirer/prompts": "^8.2.0",
50
+ "commander": "^14.0.2",
51
+ "ora": "^9.0.0",
52
+ "picocolors": "^1.1.1"
53
+ }
54
+ }
@@ -0,0 +1,138 @@
1
+ import { input, confirm, select } from "@inquirer/prompts";
2
+ import { ui } from "../lib/ui";
3
+ import { isInitialized, addTask, addResource } from "../lib/spec";
4
+ import type { Task, Resource, CheckType } from "../types";
5
+
6
+ export async function addTaskCommand(): Promise<void> {
7
+ ui.logo();
8
+
9
+ if (!isInitialized()) {
10
+ ui.error("rw not initialized. Run `rw init` first.");
11
+ process.exit(1);
12
+ }
13
+
14
+ ui.header("Add task");
15
+
16
+ // Get task description
17
+ const description = await input({
18
+ message: "Task description:",
19
+ validate: (v) => (v.trim() ? true : "Description required"),
20
+ });
21
+
22
+ // Build requirements
23
+ const requirements: Task["requirements"] = [];
24
+
25
+ const addRequirements = await confirm({
26
+ message: "Add requirements (commands to verify completion)?",
27
+ default: true,
28
+ });
29
+
30
+ if (addRequirements) {
31
+ let addMore = true;
32
+
33
+ while (addMore) {
34
+ ui.newline();
35
+ ui.info("Adding requirement...");
36
+
37
+ const command = await input({
38
+ message: "Command to run:",
39
+ validate: (v) => (v.trim() ? true : "Command required"),
40
+ });
41
+
42
+ const checks: Task["requirements"][0]["checks"] = [];
43
+ let addMoreChecks = true;
44
+
45
+ while (addMoreChecks) {
46
+ const checkType = await select<CheckType>({
47
+ message: "Check type:",
48
+ choices: [
49
+ {
50
+ name: "expect - output must contain string",
51
+ value: "expect",
52
+ },
53
+ {
54
+ name: "expect-not - output must NOT contain string",
55
+ value: "expect-not",
56
+ },
57
+ {
58
+ name: "expect-exit - exit code must match",
59
+ value: "expect-exit",
60
+ },
61
+ {
62
+ name: "eval - LLM evaluates output against criteria",
63
+ value: "eval",
64
+ },
65
+ ],
66
+ });
67
+
68
+ let valuePrompt = "Value:";
69
+ if (checkType === "expect") valuePrompt = "String to find in output:";
70
+ if (checkType === "expect-not")
71
+ valuePrompt = "String that should NOT be in output:";
72
+ if (checkType === "expect-exit") valuePrompt = "Expected exit code:";
73
+ if (checkType === "eval") valuePrompt = "Evaluation criteria:";
74
+
75
+ const value = await input({
76
+ message: valuePrompt,
77
+ validate: (v) => (v.trim() ? true : "Value required"),
78
+ });
79
+
80
+ checks.push({ type: checkType, value });
81
+
82
+ addMoreChecks = await confirm({
83
+ message: "Add another check for this command?",
84
+ default: false,
85
+ });
86
+ }
87
+
88
+ requirements.push({ command, checks });
89
+
90
+ addMore = await confirm({
91
+ message: "Add another requirement command?",
92
+ default: false,
93
+ });
94
+ }
95
+ }
96
+
97
+ const task: Task = {
98
+ description,
99
+ status: "pending",
100
+ requirements,
101
+ };
102
+
103
+ addTask(task);
104
+
105
+ ui.newline();
106
+ ui.success(`Added task: ${description}`);
107
+
108
+ if (requirements.length > 0) {
109
+ ui.info(` ${requirements.length} requirement(s) configured`);
110
+ }
111
+ }
112
+
113
+ export async function addResourceCommand(): Promise<void> {
114
+ ui.logo();
115
+
116
+ if (!isInitialized()) {
117
+ ui.error("rw not initialized. Run `rw init` first.");
118
+ process.exit(1);
119
+ }
120
+
121
+ ui.header("Add resource");
122
+
123
+ const path = await input({
124
+ message: "File path (e.g., docs/architecture.md):",
125
+ validate: (v) => (v.trim() ? true : "Path required"),
126
+ });
127
+
128
+ const description = await input({
129
+ message: "Description (what is this file for?):",
130
+ validate: (v) => (v.trim() ? true : "Description required"),
131
+ });
132
+
133
+ const resource: Resource = { path, description };
134
+ addResource(resource);
135
+
136
+ ui.newline();
137
+ ui.success(`Added resource: ${path}`);
138
+ }
@@ -0,0 +1,46 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
+ import { ui } from "../lib/ui";
3
+ import { getSpecTemplate } from "../lib/spec";
4
+ import { ensureConfigDir } from "../lib/config";
5
+
6
+ const RALPH_DIR = ".rw";
7
+ const SPEC_PATH = `${RALPH_DIR}/spec.md`;
8
+ const CHANGELOG_PATH = `${RALPH_DIR}/changelog.md`;
9
+
10
+ export async function initCommand(): Promise<void> {
11
+ ui.logo();
12
+
13
+ // Check if already initialized
14
+ if (existsSync(RALPH_DIR)) {
15
+ ui.warn("rw is already initialized in this directory");
16
+ ui.info(`Edit ${SPEC_PATH} to add tasks and resources`);
17
+ return;
18
+ }
19
+
20
+ ui.header("Initializing rw");
21
+
22
+ // Create .rw directory
23
+ mkdirSync(RALPH_DIR, { recursive: true });
24
+ ui.success(`Created ${RALPH_DIR}/`);
25
+
26
+ // Create spec.md
27
+ writeFileSync(SPEC_PATH, getSpecTemplate());
28
+ ui.success(`Created ${SPEC_PATH}`);
29
+
30
+ // Create empty changelog
31
+ writeFileSync(CHANGELOG_PATH, "# Changelog\n\n");
32
+ ui.success(`Created ${CHANGELOG_PATH}`);
33
+
34
+ // Ensure global config dir exists
35
+ ensureConfigDir();
36
+
37
+ ui.newline();
38
+ ui.box(
39
+ `Ready! Next steps:
40
+
41
+ rw add task Add your first task
42
+ rw add resource Add context files
43
+ rw run Start the loop`,
44
+ "green"
45
+ );
46
+ }
@@ -0,0 +1,38 @@
1
+ import { ui } from "../lib/ui";
2
+ import { isInitialized, validateSpec, getPendingTasks } from "../lib/spec";
3
+ import { readConfig } from "../lib/config";
4
+ import { startLoop } from "../lib/loop";
5
+
6
+ export async function runCommand(): Promise<void> {
7
+ if (!isInitialized()) {
8
+ ui.error("rw not initialized. Run `rw init` first.");
9
+ process.exit(1);
10
+ }
11
+
12
+ // Validate spec first
13
+ const { valid, errors } = validateSpec();
14
+ if (!valid) {
15
+ ui.error("spec.md has errors:");
16
+ for (const error of errors) {
17
+ ui.error(` ${error}`);
18
+ }
19
+ ui.newline();
20
+ ui.info("Fix errors and try again, or run `rw validate` for details");
21
+ process.exit(1);
22
+ }
23
+
24
+ // Check for pending tasks
25
+ const pending = getPendingTasks();
26
+ if (pending.length === 0) {
27
+ ui.logo();
28
+ ui.success("No pending tasks!");
29
+ ui.info("Add tasks with: rw add task");
30
+ return;
31
+ }
32
+
33
+ // Load config
34
+ const config = readConfig();
35
+
36
+ // Start the loop
37
+ await startLoop(config);
38
+ }
@@ -0,0 +1,77 @@
1
+ import { ui } from "../lib/ui";
2
+ import { isInitialized, readSpec } from "../lib/spec";
3
+ import pc from "picocolors";
4
+
5
+ export async function statusCommand(): Promise<void> {
6
+ ui.logo();
7
+
8
+ if (!isInitialized()) {
9
+ ui.error("rw not initialized. Run `rw init` first.");
10
+ process.exit(1);
11
+ }
12
+
13
+ const spec = readSpec();
14
+
15
+ ui.header("Tasks");
16
+ ui.newline();
17
+
18
+ if (spec.tasks.length === 0) {
19
+ ui.dim("No tasks yet. Add one with: rw add task");
20
+ } else {
21
+ for (const task of spec.tasks) {
22
+ const icon = ui.taskIcon(task.status);
23
+ const desc =
24
+ task.status === "completed"
25
+ ? pc.dim(pc.strikethrough(task.description))
26
+ : task.status === "failed"
27
+ ? pc.red(task.description)
28
+ : task.description;
29
+
30
+ console.log(` ${icon} ${desc}`);
31
+
32
+ // Show requirements count if any
33
+ if (task.requirements.length > 0) {
34
+ ui.dim(` ${task.requirements.length} requirement(s)`);
35
+ }
36
+ }
37
+
38
+ // Summary
39
+ const pending = spec.tasks.filter((t) => t.status === "pending").length;
40
+ const completed = spec.tasks.filter((t) => t.status === "completed").length;
41
+ const failed = spec.tasks.filter((t) => t.status === "failed").length;
42
+ const total = spec.tasks.length;
43
+
44
+ ui.newline();
45
+ ui.divider();
46
+ ui.newline();
47
+
48
+ // Progress bar
49
+ if (total > 0) {
50
+ ui.progress(completed, total, `${completed}/${total} complete`);
51
+ }
52
+
53
+ ui.newline();
54
+ const parts: string[] = [];
55
+ if (completed > 0) parts.push(pc.green(`${completed} completed`));
56
+ if (pending > 0) parts.push(pc.yellow(`${pending} pending`));
57
+ if (failed > 0) parts.push(pc.red(`${failed} failed`));
58
+
59
+ ui.info(parts.join(", "));
60
+ }
61
+
62
+ // Resources
63
+ ui.newline();
64
+ ui.header("Resources");
65
+ ui.newline();
66
+
67
+ if (spec.resources.length === 0) {
68
+ ui.dim("No resources yet. Add one with: rw add resource");
69
+ } else {
70
+ for (const resource of spec.resources) {
71
+ console.log(` ${pc.cyan("•")} ${pc.bold(resource.path)}`);
72
+ ui.dim(` ${resource.description}`);
73
+ }
74
+ }
75
+
76
+ ui.newline();
77
+ }
@@ -0,0 +1,44 @@
1
+ import { ui } from "../lib/ui";
2
+ import { isInitialized, validateSpec, readSpec } from "../lib/spec";
3
+
4
+ export async function validateCommand(): Promise<void> {
5
+ ui.logo();
6
+
7
+ if (!isInitialized()) {
8
+ ui.error("rw not initialized. Run `rw init` first.");
9
+ process.exit(1);
10
+ }
11
+
12
+ ui.header("Validating spec.md");
13
+
14
+ const { valid, errors } = validateSpec();
15
+
16
+ if (valid) {
17
+ const spec = readSpec();
18
+ ui.newline();
19
+ ui.success("spec.md is valid");
20
+ ui.newline();
21
+ ui.info(`${spec.tasks.length} task(s) found`);
22
+ ui.info(`${spec.resources.length} resource(s) found`);
23
+
24
+ // Show task breakdown
25
+ const pending = spec.tasks.filter((t) => t.status === "pending").length;
26
+ const completed = spec.tasks.filter((t) => t.status === "completed").length;
27
+ const failed = spec.tasks.filter((t) => t.status === "failed").length;
28
+
29
+ if (spec.tasks.length > 0) {
30
+ ui.newline();
31
+ ui.info(
32
+ `Status: ${ui.green(`${completed} completed`)}, ${pending} pending, ${ui.red(`${failed} failed`)}`
33
+ );
34
+ }
35
+ } else {
36
+ ui.newline();
37
+ ui.error("spec.md has errors:");
38
+ ui.newline();
39
+ for (const error of errors) {
40
+ ui.error(error);
41
+ }
42
+ process.exit(1);
43
+ }
44
+ }
package/src/index.ts ADDED
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env bun
2
+ import { Command } from "commander";
3
+ import pc from "picocolors";
4
+ import { initCommand } from "./commands/init";
5
+ import { addTaskCommand, addResourceCommand } from "./commands/add";
6
+ import { validateCommand } from "./commands/validate";
7
+ import { statusCommand } from "./commands/status";
8
+ import { runCommand } from "./commands/run";
9
+
10
+ const program = new Command();
11
+
12
+ program
13
+ .name("rw")
14
+ .description(pc.dim("Autonomous task runner powered by Claude"))
15
+ .version("0.1.0");
16
+
17
+ // init command
18
+ program
19
+ .command("init")
20
+ .description("Initialize rw in current directory")
21
+ .action(initCommand);
22
+
23
+ // add command with subcommands
24
+ const add = program.command("add").description("Add tasks or resources");
25
+
26
+ add
27
+ .command("task")
28
+ .description("Add a new task to spec.md")
29
+ .action(addTaskCommand);
30
+
31
+ add
32
+ .command("resource")
33
+ .description("Add a new resource to spec.md")
34
+ .action(addResourceCommand);
35
+
36
+ // validate command
37
+ program
38
+ .command("validate")
39
+ .description("Validate spec.md syntax")
40
+ .action(validateCommand);
41
+
42
+ // status command
43
+ program
44
+ .command("status")
45
+ .description("Show task completion status")
46
+ .action(statusCommand);
47
+
48
+ // run command
49
+ program
50
+ .command("run")
51
+ .description("Start the autonomous task loop")
52
+ .action(runCommand);
53
+
54
+ // Parse arguments
55
+ program.parse();