openrune 0.4.2 → 0.5.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/README.md +51 -0
- package/bin/rune.js +67 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -62,11 +62,62 @@ Each `.rune` file is just JSON — portable, shareable, version-controllable:
|
|
|
62
62
|
{
|
|
63
63
|
"name": "reviewer",
|
|
64
64
|
"role": "Code reviewer, security focused",
|
|
65
|
+
"permissions": {
|
|
66
|
+
"fileWrite": false,
|
|
67
|
+
"bash": false,
|
|
68
|
+
"allowPaths": ["src/**"],
|
|
69
|
+
"denyPaths": [".env", "secrets/**"]
|
|
70
|
+
},
|
|
65
71
|
"history": [],
|
|
66
72
|
"memory": []
|
|
67
73
|
}
|
|
68
74
|
```
|
|
69
75
|
|
|
76
|
+
### Permissions
|
|
77
|
+
|
|
78
|
+
Control what each agent can do. No permissions = full access (backward compatible).
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"permissions": {
|
|
83
|
+
"fileWrite": false,
|
|
84
|
+
"bash": false,
|
|
85
|
+
"network": false,
|
|
86
|
+
"allowPaths": ["src/**", "tests/**"],
|
|
87
|
+
"denyPaths": [".env", "secrets/**", "node_modules/**"]
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
- `fileWrite: false` — agent can read but not write/edit files
|
|
93
|
+
- `bash: false` — agent cannot run shell commands
|
|
94
|
+
- `network: false` — agent cannot make web requests
|
|
95
|
+
- `allowPaths` / `denyPaths` — restrict file access to specific patterns
|
|
96
|
+
|
|
97
|
+
A reviewer that can only read `src/`: safe. A coder that can write anywhere: powerful. You decide per agent.
|
|
98
|
+
|
|
99
|
+
### Structured logging
|
|
100
|
+
|
|
101
|
+
Track what agents do, how long it takes, and how much it costs:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
rune run reviewer.rune "Review this project" --auto --log review.json
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"agent": "reviewer",
|
|
110
|
+
"prompt": "Review this project",
|
|
111
|
+
"duration_ms": 12340,
|
|
112
|
+
"cost_usd": 0.045,
|
|
113
|
+
"tool_calls": [
|
|
114
|
+
{ "tool": "Read", "input": { "file_path": "src/index.ts" } },
|
|
115
|
+
{ "tool": "Grep", "input": { "pattern": "TODO" } }
|
|
116
|
+
],
|
|
117
|
+
"result": "Found 3 issues..."
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
70
121
|
### Headless execution
|
|
71
122
|
|
|
72
123
|
Run agents from the terminal. No GUI needed:
|
package/bin/rune.js
CHANGED
|
@@ -701,12 +701,16 @@ function runRune(file, restArgs) {
|
|
|
701
701
|
let prompt = ''
|
|
702
702
|
let outputFormat = 'text'
|
|
703
703
|
let autoMode = false
|
|
704
|
+
let logFile = ''
|
|
704
705
|
for (let i = 0; i < restArgs.length; i++) {
|
|
705
706
|
if (restArgs[i] === '--output' && restArgs[i + 1]) {
|
|
706
707
|
outputFormat = restArgs[i + 1]
|
|
707
708
|
i++
|
|
708
709
|
} else if (restArgs[i] === '--auto') {
|
|
709
710
|
autoMode = true
|
|
711
|
+
} else if (restArgs[i] === '--log' && restArgs[i + 1]) {
|
|
712
|
+
logFile = restArgs[i + 1]
|
|
713
|
+
i++
|
|
710
714
|
} else if (!prompt) {
|
|
711
715
|
prompt = restArgs[i]
|
|
712
716
|
}
|
|
@@ -743,6 +747,40 @@ function runRune(file, restArgs) {
|
|
|
743
747
|
|
|
744
748
|
const systemPrompt = systemParts.join('\n')
|
|
745
749
|
|
|
750
|
+
// Build permissions args from .rune file
|
|
751
|
+
function buildPermissionArgs(runeData) {
|
|
752
|
+
const args = []
|
|
753
|
+
if (runeData.permissions) {
|
|
754
|
+
const p = runeData.permissions
|
|
755
|
+
const allowed = []
|
|
756
|
+
const disallowed = []
|
|
757
|
+
|
|
758
|
+
if (p.fileWrite === false) disallowed.push('Write', 'Edit')
|
|
759
|
+
if (p.bash === false) disallowed.push('Bash')
|
|
760
|
+
if (p.network === false) disallowed.push('WebFetch', 'WebSearch')
|
|
761
|
+
|
|
762
|
+
if (p.allowPaths && p.allowPaths.length > 0) {
|
|
763
|
+
for (const ap of p.allowPaths) {
|
|
764
|
+
allowed.push(`Read(${ap})`, `Glob(${ap})`, `Grep(${ap})`)
|
|
765
|
+
if (p.fileWrite !== false) allowed.push(`Write(${ap})`, `Edit(${ap})`)
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
if (p.denyPaths && p.denyPaths.length > 0) {
|
|
769
|
+
for (const dp of p.denyPaths) {
|
|
770
|
+
disallowed.push(`Read(${dp})`, `Write(${dp})`, `Edit(${dp})`, `Glob(${dp})`, `Grep(${dp})`)
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
if (allowed.length > 0) args.push('--allowedTools', allowed.join(' '))
|
|
775
|
+
if (disallowed.length > 0) args.push('--disallowedTools', disallowed.join(' '))
|
|
776
|
+
}
|
|
777
|
+
return args
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// Logging
|
|
781
|
+
const logEntries = []
|
|
782
|
+
const logStart = Date.now()
|
|
783
|
+
|
|
746
784
|
// Auto mode: agent can read/write files, run commands, fix errors autonomously
|
|
747
785
|
if (autoMode) {
|
|
748
786
|
console.log(`🔮 [auto] ${rune.name} is working on: ${prompt}\n`)
|
|
@@ -761,6 +799,10 @@ function runRune(file, restArgs) {
|
|
|
761
799
|
'--verbose',
|
|
762
800
|
'--output-format', 'stream-json',
|
|
763
801
|
]
|
|
802
|
+
// Apply permissions (override dangerously-skip if permissions are set)
|
|
803
|
+
const permArgs = buildPermissionArgs(rune)
|
|
804
|
+
claudeArgs.push(...permArgs)
|
|
805
|
+
|
|
764
806
|
if (systemPrompt) {
|
|
765
807
|
claudeArgs.push('--system-prompt', systemPrompt)
|
|
766
808
|
}
|
|
@@ -796,6 +838,7 @@ function runRune(file, restArgs) {
|
|
|
796
838
|
if (block.type === 'tool_use') {
|
|
797
839
|
const tool = block.name || 'unknown'
|
|
798
840
|
const input = block.input || {}
|
|
841
|
+
logEntries.push({ type: 'tool_use', tool, input, ts: Date.now() })
|
|
799
842
|
if (tool === 'Bash') {
|
|
800
843
|
console.log(` ▶ Bash: ${(input.command || '').slice(0, 120)}`)
|
|
801
844
|
} else if (tool === 'Write') {
|
|
@@ -819,12 +862,13 @@ function runRune(file, restArgs) {
|
|
|
819
862
|
|
|
820
863
|
// Tool results
|
|
821
864
|
if (event.type === 'user' && event.tool_use_result) {
|
|
822
|
-
|
|
865
|
+
logEntries.push({ type: 'tool_result', result: event.tool_use_result, ts: Date.now() })
|
|
823
866
|
}
|
|
824
867
|
|
|
825
868
|
// Final result
|
|
826
869
|
if (event.type === 'result') {
|
|
827
870
|
fullOutput = event.result || ''
|
|
871
|
+
logEntries.push({ type: 'result', cost_usd: event.total_cost_usd, usage: event.usage, duration_ms: event.duration_ms, ts: Date.now() })
|
|
828
872
|
if (fullOutput) console.log(`\n${fullOutput}`)
|
|
829
873
|
}
|
|
830
874
|
} catch {}
|
|
@@ -841,6 +885,27 @@ function runRune(file, restArgs) {
|
|
|
841
885
|
rune.history.push({ role: 'assistant', text: fullOutput.trim(), ts: Date.now() })
|
|
842
886
|
fs.writeFileSync(filePath, JSON.stringify(rune, null, 2))
|
|
843
887
|
|
|
888
|
+
// Write structured log
|
|
889
|
+
if (logFile) {
|
|
890
|
+
const costEntry = logEntries.find(e => e.type === 'result')
|
|
891
|
+
const log = {
|
|
892
|
+
agent: rune.name,
|
|
893
|
+
role: rune.role,
|
|
894
|
+
prompt,
|
|
895
|
+
mode: 'auto',
|
|
896
|
+
permissions: rune.permissions || null,
|
|
897
|
+
duration_ms: Date.now() - logStart,
|
|
898
|
+
cost_usd: costEntry?.cost_usd || null,
|
|
899
|
+
usage: costEntry?.usage || null,
|
|
900
|
+
tool_calls: logEntries.filter(e => e.type === 'tool_use').map(e => ({ tool: e.tool, input: e.input, ts: e.ts })),
|
|
901
|
+
result: fullOutput.trim(),
|
|
902
|
+
exit_code: code,
|
|
903
|
+
ts: new Date().toISOString(),
|
|
904
|
+
}
|
|
905
|
+
fs.writeFileSync(path.resolve(logFile), JSON.stringify(log, null, 2))
|
|
906
|
+
console.log(` 📋 Log saved: ${logFile}`)
|
|
907
|
+
}
|
|
908
|
+
|
|
844
909
|
if (code !== 0) console.error(`\n ⚠️ Agent exited with code ${code}`)
|
|
845
910
|
else console.log(`\n✓ ${rune.name} finished`)
|
|
846
911
|
process.exit(code || 0)
|
|
@@ -1361,6 +1426,7 @@ Usage:
|
|
|
1361
1426
|
rune run <file.rune> "prompt" Run agent headlessly (no GUI)
|
|
1362
1427
|
--auto Auto mode: agent writes files, runs commands, fixes errors
|
|
1363
1428
|
--output json|text Output format (default: text)
|
|
1429
|
+
--log <file.json> Save structured log (tool calls, cost, duration)
|
|
1364
1430
|
rune pipe <a.rune> <b.rune> ... "prompt" Chain agents in a pipeline
|
|
1365
1431
|
--output json|text Output format (default: text)
|
|
1366
1432
|
rune watch <file.rune> Set up automated triggers
|
package/package.json
CHANGED