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 +21 -0
- package/README.md +133 -0
- package/package.json +54 -0
- package/src/commands/add.ts +138 -0
- package/src/commands/init.ts +46 -0
- package/src/commands/run.ts +38 -0
- package/src/commands/status.ts +77 -0
- package/src/commands/validate.ts +44 -0
- package/src/index.ts +55 -0
- package/src/lib/checker.ts +140 -0
- package/src/lib/config.ts +89 -0
- package/src/lib/loop.ts +262 -0
- package/src/lib/runner.ts +107 -0
- package/src/lib/spec.ts +294 -0
- package/src/lib/ui.ts +117 -0
- package/src/types.ts +45 -0
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();
|