openrune 1.0.2 → 1.1.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.
package/README.ko.md CHANGED
@@ -37,7 +37,7 @@ claude # 로그인이 안 되어 있다면
37
37
 
38
38
  > **Rune의 작동 방식:** Rune은 Claude Code의 커스텀 채널(현재 베타)을 사용하여 에이전트 기능을 확장합니다. Claude API에 직접 접근하거나 인증을 처리하지 않으며, 모든 실행은 공식 Claude Code CLI를 통해 이루어집니다.
39
39
 
40
- > **사용량:** Rune은 사용자의 Claude Code CLI 세션을 통해 실행됩니다. 사용량이 구독 또는 추가 사용량에서 차감되는지 현재 확인 중이며, 확인되는 대로 업데이트하겠습니다.
40
+ > **사용량:** Rune은 사용자의 Claude Code CLI 세션을 통해 실행됩니다. 2026년 4월 기준, 서드파티 도구의 사용량은 구독이 아닌 추가 사용량(extra usage)에서 차감됩니다.
41
41
 
42
42
  ## 설치
43
43
 
@@ -71,6 +71,7 @@ Rune은 다른 접근을 합니다: **에이전트가 파일입니다.**
71
71
  | **스케줄링** | 수동 실행만 가능 | Cron, 파일 변경, git-commit 트리거 |
72
72
  | **권한** | 세션에서 상속 | 에이전트별 제어 (`fileWrite`, `bash`, `allowPaths`) |
73
73
  | **실행** | 대화형 | 헤드리스, 파이프라인, CI/CD 지원 |
74
+ | **자기 수정** | 기본 제공 없음 | `rune loop` — 자동 리뷰-수정 반복 |
74
75
 
75
76
  Rune 에이전트는 세션, 머신, 팀을 넘어 살아남습니다. 한 번 만들면 영원히 실행.
76
77
 
@@ -214,6 +215,41 @@ rune pipe architect.rune coder.rune "Build a REST API with Express" --auto
214
215
 
215
216
  architect가 설계 → coder가 구현 (파일 작성, 의존성 설치).
216
217
 
218
+ ### 자기 수정 루프
219
+
220
+ 에이전트가 자동으로 자신의 작업을 리뷰하고 수정합니다:
221
+
222
+ ```bash
223
+ rune loop coder.rune reviewer.rune "Build a REST API with Express" --until "no critical issues" --max-iterations 3 --auto
224
+ ```
225
+
226
+ ```
227
+ 🔁 Starting self-correction loop (max 3 iterations)
228
+ Stop condition: "no critical issues"
229
+
230
+ ━━━ Iteration 1/3 ━━━
231
+
232
+ ▶ [doer] coder — API 구현
233
+ ✓ coder done
234
+
235
+ ▶ [reviewer] reviewer — 치명적 이슈 2개 발견
236
+ ✓ reviewer done
237
+
238
+ ━━━ Iteration 2/3 ━━━
239
+
240
+ ▶ [doer] coder — 이슈 수정
241
+ ✓ coder done
242
+
243
+ ▶ [reviewer] reviewer — "no critical issues found"
244
+ ✓ reviewer done
245
+
246
+ ✅ Condition met: "no critical issues"
247
+
248
+ 🔁 Loop completed after 2 iterations
249
+ ```
250
+
251
+ 구현자가 구현하고, 리뷰어가 리뷰합니다. 문제가 발견되면 피드백이 자동으로 구현자에게 전달됩니다 — 조건이 충족되거나 최대 반복 횟수에 도달할 때까지.
252
+
217
253
  ### 자동화 트리거
218
254
 
219
255
  ```bash
@@ -324,6 +360,7 @@ rune open reviewer.rune
324
360
  | `rune new <name> [--role "..."]` | 에이전트 생성 |
325
361
  | `rune run <file> "prompt" [--auto] [--output json]` | 헤드리스 실행 |
326
362
  | `rune pipe <a> <b> [...] "prompt" [--auto]` | 에이전트 체이닝 |
363
+ | `rune loop <doer> <reviewer> "prompt" [--until "..."] [--max-iterations N] [--auto]` | 자기 수정 루프 |
327
364
  | `rune watch <file> --on <event> --prompt "..."` | 자동화 트리거 |
328
365
  | `rune open <file>` | 데스크톱 UI |
329
366
  | `rune list` | 현재 디렉토리의 에이전트 목록 |
package/README.md CHANGED
@@ -37,7 +37,7 @@ claude # login if you haven't
37
37
 
38
38
  > **How Rune works:** Rune uses Claude Code's custom channels (currently in beta) to extend agent capabilities. It does not access the Claude API directly or handle any authentication — all execution goes through the official Claude Code CLI.
39
39
 
40
- > **Usage:** Rune runs through your Claude Code CLI session. We are currently verifying whether usage counts toward your subscription or extra usage this section will be updated once confirmed.
40
+ > **Usage:** Rune runs through your Claude Code CLI session. As of April 2026, usage from third-party tools counts toward extra usage, not your subscription.
41
41
 
42
42
  ## Install
43
43
 
@@ -71,6 +71,7 @@ Rune takes a different approach: **agents are files.**
71
71
  | **Scheduling** | Manual execution only | Cron, file-change, and git-commit triggers |
72
72
  | **Permissions** | Inherited from session | Per-agent controls (`fileWrite`, `bash`, `allowPaths`) |
73
73
  | **Execution** | Interactive | Headless, pipelines, CI/CD-ready |
74
+ | **Self-correction** | Not built-in | `rune loop` — automatic review-fix cycles |
74
75
 
75
76
  Rune agents survive across sessions, machines, and teams. Build once, run forever.
76
77
 
@@ -214,6 +215,41 @@ rune pipe architect.rune coder.rune "Build a REST API with Express" --auto
214
215
 
215
216
  architect designs → coder implements (writes files, installs deps).
216
217
 
218
+ ### Self-correction loop
219
+
220
+ Agents review and fix their own work automatically:
221
+
222
+ ```bash
223
+ rune loop coder.rune reviewer.rune "Build a REST API with Express" --until "no critical issues" --max-iterations 3 --auto
224
+ ```
225
+
226
+ ```
227
+ 🔁 Starting self-correction loop (max 3 iterations)
228
+ Stop condition: "no critical issues"
229
+
230
+ ━━━ Iteration 1/3 ━━━
231
+
232
+ ▶ [doer] coder — implements the API
233
+ ✓ coder done
234
+
235
+ ▶ [reviewer] reviewer — finds 2 critical issues
236
+ ✓ reviewer done
237
+
238
+ ━━━ Iteration 2/3 ━━━
239
+
240
+ ▶ [doer] coder — fixes the issues
241
+ ✓ coder done
242
+
243
+ ▶ [reviewer] reviewer — "no critical issues found"
244
+ ✓ reviewer done
245
+
246
+ ✅ Condition met: "no critical issues"
247
+
248
+ 🔁 Loop completed after 2 iterations
249
+ ```
250
+
251
+ The doer implements, the reviewer reviews. If issues are found, feedback goes back to the doer automatically — until the condition is met or max iterations are reached.
252
+
217
253
  ### Automated triggers
218
254
 
219
255
  ```bash
@@ -324,6 +360,7 @@ Or double-click any `.rune` file in Finder.
324
360
  | `rune new <name> [--role "..."]` | Create agent |
325
361
  | `rune run <file> "prompt" [--auto] [--output json]` | Run headlessly |
326
362
  | `rune pipe <a> <b> [...] "prompt" [--auto]` | Chain agents |
363
+ | `rune loop <doer> <reviewer> "prompt" [--until "..."] [--max-iterations N] [--auto]` | Self-correction loop |
327
364
  | `rune watch <file> --on <event> --prompt "..."` | Automated triggers |
328
365
  | `rune open <file>` | Desktop UI |
329
366
  | `rune list` | List agents in current directory |
package/bin/rune.js CHANGED
@@ -27,6 +27,7 @@ switch (command) {
27
27
  case 'open': return openRune(args[0])
28
28
  case 'run': return runRune(args[0], args.slice(1))
29
29
  case 'pipe': return pipeRunes(args)
30
+ case 'loop': return loopRunes(args)
30
31
  case 'watch': return watchRune(args[0], args.slice(1))
31
32
  case 'list': return listRunes()
32
33
  case 'uninstall': return uninstall()
@@ -1156,6 +1157,236 @@ async function pipeRunes(args) {
1156
1157
  }
1157
1158
  }
1158
1159
 
1160
+ // ── loop (self-correction) ──────────────────────
1161
+
1162
+ async function loopRunes(args) {
1163
+ // Parse: rune loop coder.rune reviewer.rune "prompt" [--until "condition"] [--max-iterations N] [--auto]
1164
+ const runeFiles = []
1165
+ let prompt = ''
1166
+ let untilCondition = ''
1167
+ let maxIterations = 5
1168
+ let autoMode = false
1169
+
1170
+ for (let i = 0; i < args.length; i++) {
1171
+ if (args[i] === '--until' && args[i + 1]) {
1172
+ untilCondition = args[++i]
1173
+ } else if (args[i] === '--max-iterations' && args[i + 1]) {
1174
+ maxIterations = parseInt(args[++i], 10)
1175
+ } else if (args[i] === '--auto') {
1176
+ autoMode = true
1177
+ } else if (args[i].endsWith('.rune')) {
1178
+ runeFiles.push(args[i])
1179
+ } else if (!prompt) {
1180
+ prompt = args[i]
1181
+ }
1182
+ }
1183
+
1184
+ if (runeFiles.length < 2 || !prompt) {
1185
+ console.log('Usage: rune loop <doer.rune> <reviewer.rune> "prompt" [--until "condition"] [--max-iterations N] [--auto]')
1186
+ console.log('')
1187
+ console.log('Options:')
1188
+ console.log(' --until "..." Stop when the reviewer\'s output contains this text')
1189
+ console.log(' --max-iterations N Maximum number of loop iterations (default: 5)')
1190
+ console.log(' --auto Allow agents to write files and run commands')
1191
+ console.log('')
1192
+ console.log('Example:')
1193
+ console.log(' rune loop coder.rune reviewer.rune "Build a REST API" --until "no critical issues" --max-iterations 3 --auto')
1194
+ console.log('')
1195
+ console.log('The first agent implements, the last agent reviews.')
1196
+ console.log('If the reviewer finds issues, feedback is sent back to the first agent automatically.')
1197
+ process.exit(1)
1198
+ }
1199
+
1200
+ // Validate files
1201
+ for (const file of runeFiles) {
1202
+ const filePath = path.resolve(process.cwd(), file)
1203
+ if (!fs.existsSync(filePath)) {
1204
+ console.error(` ❌ File not found: ${filePath}`)
1205
+ process.exit(1)
1206
+ }
1207
+ }
1208
+
1209
+ const doerFile = runeFiles[0]
1210
+ const reviewerFile = runeFiles[runeFiles.length - 1]
1211
+ const doerPath = path.resolve(process.cwd(), doerFile)
1212
+ const reviewerPath = path.resolve(process.cwd(), reviewerFile)
1213
+
1214
+ let currentPrompt = prompt
1215
+ let iteration = 0
1216
+ let converged = false
1217
+
1218
+ console.log(`\n🔁 Starting self-correction loop (max ${maxIterations} iterations)`)
1219
+ if (untilCondition) console.log(` Stop condition: "${untilCondition}"`)
1220
+ console.log('')
1221
+
1222
+ while (iteration < maxIterations && !converged) {
1223
+ iteration++
1224
+ console.log(` ━━━ Iteration ${iteration}/${maxIterations} ━━━\n`)
1225
+
1226
+ // Step 1: Doer implements
1227
+ const doerRune = JSON.parse(fs.readFileSync(doerPath, 'utf-8'))
1228
+ const doerFolder = path.dirname(doerPath)
1229
+ const doerSystem = []
1230
+ if (doerRune.role) doerSystem.push(`Your role: ${doerRune.role}`)
1231
+ if (doerRune.memory && doerRune.memory.length > 0) {
1232
+ doerSystem.push('Saved memory:')
1233
+ doerRune.memory.forEach((m, j) => doerSystem.push(`${j + 1}. ${m}`))
1234
+ }
1235
+
1236
+ const doerContext = iteration > 1
1237
+ ? `You are in iteration ${iteration} of a self-correction loop. The reviewer found issues with your previous work:\n\n${currentPrompt}\n\nFix the issues and improve your implementation.`
1238
+ : currentPrompt
1239
+
1240
+ console.log(` ▶ [doer] ${doerRune.name} (${doerRune.role || 'assistant'})`)
1241
+
1242
+ const doerOutput = await runAgent(doerRune.name, doerFolder, doerSystem, doerContext, autoMode)
1243
+
1244
+ doerRune.history = doerRune.history || []
1245
+ doerRune.history.push({ role: 'user', text: doerContext, ts: Date.now() })
1246
+ doerRune.history.push({ role: 'assistant', text: doerOutput, ts: Date.now() })
1247
+ fs.writeFileSync(doerPath, JSON.stringify(doerRune, null, 2))
1248
+
1249
+ console.log(` ✓ ${doerRune.name} done\n`)
1250
+
1251
+ // Step 2: Reviewer reviews
1252
+ const reviewerRune = JSON.parse(fs.readFileSync(reviewerPath, 'utf-8'))
1253
+ const reviewerFolder = path.dirname(reviewerPath)
1254
+ const reviewerSystem = []
1255
+ if (reviewerRune.role) reviewerSystem.push(`Your role: ${reviewerRune.role}`)
1256
+ if (reviewerRune.memory && reviewerRune.memory.length > 0) {
1257
+ reviewerSystem.push('Saved memory:')
1258
+ reviewerRune.memory.forEach((m, j) => reviewerSystem.push(`${j + 1}. ${m}`))
1259
+ }
1260
+
1261
+ const reviewerContext = `You are the reviewer in iteration ${iteration} of a self-correction loop. Review the work done by ${doerRune.name}:\n\n${doerOutput}\n\nIf there are issues, describe them clearly so the implementer can fix them. If the work is satisfactory, say so clearly.`
1262
+
1263
+ console.log(` ▶ [reviewer] ${reviewerRune.name} (${reviewerRune.role || 'assistant'})`)
1264
+
1265
+ const reviewerOutput = await runAgent(reviewerRune.name, reviewerFolder, reviewerSystem, reviewerContext, false)
1266
+
1267
+ reviewerRune.history = reviewerRune.history || []
1268
+ reviewerRune.history.push({ role: 'user', text: reviewerContext, ts: Date.now() })
1269
+ reviewerRune.history.push({ role: 'assistant', text: reviewerOutput, ts: Date.now() })
1270
+ fs.writeFileSync(reviewerPath, JSON.stringify(reviewerRune, null, 2))
1271
+
1272
+ console.log(` ✓ ${reviewerRune.name} done\n`)
1273
+
1274
+ // Check convergence
1275
+ if (untilCondition) {
1276
+ const lower = reviewerOutput.toLowerCase()
1277
+ if (lower.includes(untilCondition.toLowerCase())) {
1278
+ converged = true
1279
+ console.log(` ✅ Condition met: "${untilCondition}"`)
1280
+ }
1281
+ }
1282
+
1283
+ if (!converged) {
1284
+ currentPrompt = reviewerOutput
1285
+ }
1286
+ }
1287
+
1288
+ if (!converged && iteration >= maxIterations) {
1289
+ console.log(` ⚠️ Max iterations (${maxIterations}) reached`)
1290
+ }
1291
+
1292
+ console.log(`\n🔁 Loop completed after ${iteration} iteration${iteration > 1 ? 's' : ''}\n`)
1293
+ }
1294
+
1295
+ async function runAgent(name, folderPath, systemParts, prompt, autoMode) {
1296
+ if (autoMode) {
1297
+ const mcpPath = path.join(folderPath, '.mcp.json')
1298
+ const mcpBackup = path.join(folderPath, '.mcp.json.loop.bak')
1299
+ let mcpHidden = false
1300
+ if (fs.existsSync(mcpPath)) {
1301
+ fs.renameSync(mcpPath, mcpBackup)
1302
+ mcpHidden = true
1303
+ }
1304
+
1305
+ const claudeArgs = ['-p', '--print',
1306
+ '--dangerously-skip-permissions',
1307
+ '--verbose',
1308
+ '--output-format', 'stream-json',
1309
+ ]
1310
+ if (systemParts.length > 0) {
1311
+ claudeArgs.push('--system-prompt', systemParts.join('\n'))
1312
+ }
1313
+ claudeArgs.push(prompt)
1314
+
1315
+ const output = await new Promise((resolve, reject) => {
1316
+ const child = spawn('claude', claudeArgs, {
1317
+ cwd: folderPath,
1318
+ stdio: ['ignore', 'pipe', 'pipe'],
1319
+ env: { ...process.env },
1320
+ })
1321
+
1322
+ let fullOutput = ''
1323
+ let buffer = ''
1324
+ child.stdout.on('data', (data) => {
1325
+ buffer += data.toString()
1326
+ const lines = buffer.split('\n')
1327
+ buffer = lines.pop()
1328
+ for (const line of lines) {
1329
+ if (!line.trim()) continue
1330
+ try {
1331
+ const event = JSON.parse(line)
1332
+ if (event.type === 'assistant' && event.message && event.message.content) {
1333
+ for (const block of event.message.content) {
1334
+ if (block.type === 'tool_use') {
1335
+ const tool = block.name || 'unknown'
1336
+ const input = block.input || {}
1337
+ if (tool === 'Bash') console.log(` ▶ Bash: ${(input.command || '').slice(0, 120)}`)
1338
+ else if (tool === 'Write') console.log(` ▶ Write: ${input.file_path || ''}`)
1339
+ else if (tool === 'Edit') console.log(` ▶ Edit: ${input.file_path || ''}`)
1340
+ else if (tool === 'Read') console.log(` ▶ Read: ${input.file_path || ''}`)
1341
+ else console.log(` ▶ ${tool}`)
1342
+ } else if (block.type === 'text' && block.text && block.text.trim()) {
1343
+ console.log(` 💬 ${block.text.trim().slice(0, 200)}`)
1344
+ }
1345
+ }
1346
+ }
1347
+ if (event.type === 'result') {
1348
+ fullOutput = event.result || ''
1349
+ }
1350
+ } catch {}
1351
+ }
1352
+ })
1353
+ child.stderr.on('data', (d) => { process.stderr.write(d) })
1354
+ child.on('close', (code) => {
1355
+ if (mcpHidden && fs.existsSync(mcpBackup)) fs.renameSync(mcpBackup, mcpPath)
1356
+ if (code !== 0) reject(new Error(`Agent ${name} exited with code ${code}`))
1357
+ else resolve(fullOutput.trim())
1358
+ })
1359
+ })
1360
+
1361
+ return output
1362
+ } else {
1363
+ const tmpdir = require('os').tmpdir()
1364
+ const claudeArgs = ['-p', '--print', '--add-dir', folderPath]
1365
+ if (systemParts.length > 0) {
1366
+ claudeArgs.push('--system-prompt', systemParts.join('\n') + `\nWorking folder: ${folderPath}`)
1367
+ }
1368
+ claudeArgs.push('--', prompt)
1369
+
1370
+ const output = await new Promise((resolve, reject) => {
1371
+ const child = spawn('claude', claudeArgs, {
1372
+ cwd: tmpdir,
1373
+ stdio: ['ignore', 'pipe', 'pipe'],
1374
+ env: { ...process.env },
1375
+ })
1376
+
1377
+ let stdout = ''
1378
+ child.stdout.on('data', (d) => { stdout += d.toString() })
1379
+ child.stderr.on('data', (d) => { process.stderr.write(d) })
1380
+ child.on('close', (code) => {
1381
+ if (code !== 0) reject(new Error(`Agent ${name} exited with code ${code}`))
1382
+ else resolve(stdout.trim())
1383
+ })
1384
+ })
1385
+
1386
+ return output
1387
+ }
1388
+ }
1389
+
1159
1390
  // ── watch (triggers) ────────────────────────────
1160
1391
 
1161
1392
  function watchRune(file, restArgs) {
@@ -1425,6 +1656,10 @@ Usage:
1425
1656
  --log <file.json> Save structured log (tool calls, cost, duration)
1426
1657
  rune pipe <a.rune> <b.rune> ... "prompt" Chain agents in a pipeline
1427
1658
  --output json|text Output format (default: text)
1659
+ rune loop <doer.rune> <reviewer.rune> "prompt" Self-correction loop
1660
+ --until "condition" Stop when reviewer output contains this text
1661
+ --max-iterations N Max iterations (default: 5)
1662
+ --auto Allow agents to write files and run commands
1428
1663
  rune watch <file.rune> Set up automated triggers
1429
1664
  --on <event> Event: file-change, git-commit, git-push, cron
1430
1665
  --prompt "..." Prompt to send when triggered
@@ -1438,6 +1673,7 @@ Examples:
1438
1673
  rune new reviewer --role "Code reviewer, security focused"
1439
1674
  rune run reviewer.rune "Review the latest commit"
1440
1675
  rune pipe coder.rune reviewer.rune "Implement a login page"
1676
+ rune loop coder.rune reviewer.rune "Build a REST API" --until "no critical issues" --max-iterations 3 --auto
1441
1677
  rune watch reviewer.rune --on git-commit --prompt "Review this commit"
1442
1678
  rune watch monitor.rune --on cron --interval 5m --prompt "Check server health"
1443
1679
  echo "Fix the bug in auth.ts" | rune run backend.rune
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openrune",
3
- "version": "1.0.2",
3
+ "version": "1.1.1",
4
4
  "description": "Persistent AI agents for Claude Code — build once, run forever.",
5
5
  "keywords": ["ai", "agent", "claude", "desktop", "electron", "mcp", "claude-code", "toolkit", "automation"],
6
6
  "repository": {