openrune 1.1.2 → 2.0.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 +26 -77
- package/README.md +28 -81
- package/bin/rune.js +43 -702
- package/package.json +10 -40
- package/.claude-plugin/marketplace.json +0 -17
- package/.claude-plugin/plugin.json +0 -24
- package/.mcp.json +0 -16
- package/bootstrap.js +0 -8
- package/channel/rune-channel.ts +0 -486
- package/electron-builder.yml +0 -61
- package/finder-extension/FinderSync.swift +0 -47
- package/finder-extension/RuneFinderSync.appex/Contents/Info.plist +0 -27
- package/finder-extension/RuneFinderSync.appex/Contents/MacOS/RuneFinderSync +0 -0
- package/finder-extension/main.swift +0 -5
- package/renderer/index.html +0 -12
- package/renderer/src/App.tsx +0 -44
- package/renderer/src/features/chat/activity-block.tsx +0 -152
- package/renderer/src/features/chat/chat-header.tsx +0 -58
- package/renderer/src/features/chat/chat-input.tsx +0 -190
- package/renderer/src/features/chat/chat-panel.tsx +0 -151
- package/renderer/src/features/chat/markdown-renderer.tsx +0 -26
- package/renderer/src/features/chat/message-bubble.tsx +0 -79
- package/renderer/src/features/chat/message-list.tsx +0 -178
- package/renderer/src/features/chat/types.ts +0 -32
- package/renderer/src/features/chat/use-chat.ts +0 -260
- package/renderer/src/features/terminal/terminal-panel.tsx +0 -155
- package/renderer/src/global.d.ts +0 -29
- package/renderer/src/globals.css +0 -92
- package/renderer/src/hooks/use-ipc.ts +0 -24
- package/renderer/src/lib/markdown.ts +0 -83
- package/renderer/src/lib/utils.ts +0 -6
- package/renderer/src/main.tsx +0 -10
- package/renderer/tsconfig.json +0 -16
- package/renderer/vite.config.ts +0 -23
- package/screenshot-chatting-ui.png +0 -0
- package/src/main.ts +0 -796
- package/src/preload.ts +0 -58
- package/tsconfig.json +0 -14
package/bin/rune.js
CHANGED
|
@@ -1,17 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const { spawn } = require('child_process')
|
|
4
4
|
const path = require('path')
|
|
5
5
|
const fs = require('fs')
|
|
6
|
-
const os = require('os')
|
|
7
|
-
|
|
8
|
-
// Platform check
|
|
9
|
-
const IS_MAC = process.platform === 'darwin'
|
|
10
|
-
const IS_WIN = process.platform === 'win32'
|
|
11
|
-
|
|
12
|
-
const RUNE_HOME = path.join(os.homedir(), '.rune')
|
|
13
|
-
const APP_DIR = path.join(RUNE_HOME, 'app')
|
|
14
|
-
const QUICK_ACTION_DIR = IS_MAC ? path.join(os.homedir(), 'Library', 'Services') : null
|
|
15
6
|
|
|
16
7
|
const [,, command, ...args] = process.argv
|
|
17
8
|
|
|
@@ -22,564 +13,27 @@ function ensureDir(dir) {
|
|
|
22
13
|
// ── Commands ────────────────────────────────────
|
|
23
14
|
|
|
24
15
|
switch (command) {
|
|
25
|
-
case 'install': return install()
|
|
26
16
|
case 'new': return createRune(args[0], args)
|
|
27
|
-
case 'open': return openRune(args[0])
|
|
28
17
|
case 'run': return runRune(args[0], args.slice(1))
|
|
29
18
|
case 'pipe': return pipeRunes(args)
|
|
30
19
|
case 'loop': return loopRunes(args)
|
|
31
20
|
case 'watch': return watchRune(args[0], args.slice(1))
|
|
32
21
|
case 'list': return listRunes()
|
|
33
|
-
case '
|
|
22
|
+
case 'open':
|
|
23
|
+
console.log(' ℹ️ `rune open` has been removed from the CLI.')
|
|
24
|
+
console.log(' For a GUI, use RuneChat: https://github.com/gilhyun/runechat')
|
|
25
|
+
process.exit(0)
|
|
26
|
+
case 'install':
|
|
27
|
+
case 'uninstall':
|
|
28
|
+
console.log(' ℹ️ `rune install` / `rune uninstall` are no longer needed.')
|
|
29
|
+
console.log(' Rune is now a pure CLI — install with: npm install -g openrune')
|
|
30
|
+
process.exit(0)
|
|
34
31
|
case 'help':
|
|
35
32
|
case '--help':
|
|
36
33
|
case '-h':
|
|
37
34
|
default: return showHelp()
|
|
38
35
|
}
|
|
39
36
|
|
|
40
|
-
// ── install ──────────────────────────────────────
|
|
41
|
-
|
|
42
|
-
function install() {
|
|
43
|
-
console.log('🔮 Installing Rune...\n')
|
|
44
|
-
ensureDir(RUNE_HOME)
|
|
45
|
-
ensureDir(APP_DIR)
|
|
46
|
-
|
|
47
|
-
// 1. Build the app (dev mode: use local source)
|
|
48
|
-
const projectRoot = path.resolve(__dirname, '..')
|
|
49
|
-
console.log(' Building Rune app...')
|
|
50
|
-
try {
|
|
51
|
-
execSync('npm run build', { cwd: projectRoot, stdio: 'inherit' })
|
|
52
|
-
} catch (e) {
|
|
53
|
-
console.error(' ❌ Build failed')
|
|
54
|
-
process.exit(1)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// 2. Rebuild native modules for Electron (node-pty)
|
|
58
|
-
console.log(' Rebuilding native modules for Electron...')
|
|
59
|
-
try {
|
|
60
|
-
execSync('npx electron-rebuild -m .', { cwd: projectRoot, stdio: 'inherit' })
|
|
61
|
-
console.log(' ✅ Native modules rebuilt')
|
|
62
|
-
} catch (e) {
|
|
63
|
-
console.error(' ⚠️ electron-rebuild failed (terminal may not work)')
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// 3. Store project path for later use
|
|
67
|
-
fs.writeFileSync(path.join(RUNE_HOME, 'project-path'), projectRoot)
|
|
68
|
-
console.log(` ✅ App built at ${projectRoot}`)
|
|
69
|
-
|
|
70
|
-
// 3. Install macOS Quick Action for right-click menu
|
|
71
|
-
if (IS_MAC) {
|
|
72
|
-
installQuickAction(projectRoot)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// 4. Register .rune file association (macOS)
|
|
76
|
-
if (IS_MAC) {
|
|
77
|
-
registerFileAssociation(projectRoot)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// 5. Install Claude Code channel plugin
|
|
81
|
-
installChannelPlugin()
|
|
82
|
-
|
|
83
|
-
console.log('\n🔮 Rune installed successfully!\n')
|
|
84
|
-
console.log(' Usage:')
|
|
85
|
-
console.log(' Right-click any folder → Quick Actions → New Rune')
|
|
86
|
-
console.log(' Double-click any .rune file to open')
|
|
87
|
-
console.log(' rune new <name> Create .rune file in current directory')
|
|
88
|
-
console.log(' rune open <file> Open a .rune file')
|
|
89
|
-
console.log(' rune list List .rune files in current directory')
|
|
90
|
-
console.log('')
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function installQuickAction(projectRoot) {
|
|
94
|
-
console.log(' Installing macOS Quick Action...')
|
|
95
|
-
|
|
96
|
-
ensureDir(QUICK_ACTION_DIR)
|
|
97
|
-
|
|
98
|
-
// Remove old workflow if exists
|
|
99
|
-
const workflowDir = path.join(QUICK_ACTION_DIR, 'New Rune.workflow')
|
|
100
|
-
if (fs.existsSync(workflowDir)) {
|
|
101
|
-
fs.rmSync(workflowDir, { recursive: true })
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Create helper shell script that the Quick Action will call
|
|
105
|
-
const helperScript = path.join(RUNE_HOME, 'create-rune.sh')
|
|
106
|
-
const nodebin = process.execPath
|
|
107
|
-
const runeBin = path.resolve(__dirname, 'rune.js')
|
|
108
|
-
|
|
109
|
-
// Write the helper shell script
|
|
110
|
-
const scriptContent = `#!/bin/bash
|
|
111
|
-
# Get the folder path — works for both file and folder right-clicks
|
|
112
|
-
INPUT="$1"
|
|
113
|
-
if [ -z "$INPUT" ]; then
|
|
114
|
-
INPUT=$(osascript -e 'tell application "Finder" to get POSIX path of (target of front Finder window as alias)' 2>/dev/null)
|
|
115
|
-
fi
|
|
116
|
-
if [ -z "$INPUT" ]; then
|
|
117
|
-
INPUT="$HOME"
|
|
118
|
-
fi
|
|
119
|
-
# If input is a file, use its parent directory
|
|
120
|
-
if [ -f "$INPUT" ]; then
|
|
121
|
-
FOLDER=$(dirname "$INPUT")
|
|
122
|
-
else
|
|
123
|
-
FOLDER="$INPUT"
|
|
124
|
-
fi
|
|
125
|
-
# Remove trailing slash
|
|
126
|
-
FOLDER="\${FOLDER%/}"
|
|
127
|
-
|
|
128
|
-
# Create .rune file with folder name as agent name
|
|
129
|
-
NAME=$(basename "$FOLDER")
|
|
130
|
-
FILEPATH="$FOLDER/$NAME.rune"
|
|
131
|
-
|
|
132
|
-
cat > "$FILEPATH" << RUNEEOF
|
|
133
|
-
{
|
|
134
|
-
"name": "$NAME",
|
|
135
|
-
"role": "General assistant",
|
|
136
|
-
"icon": "bot",
|
|
137
|
-
"createdAt": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
|
138
|
-
"history": []
|
|
139
|
-
}
|
|
140
|
-
RUNEEOF
|
|
141
|
-
`
|
|
142
|
-
fs.writeFileSync(helperScript, scriptContent, { mode: 0o755 })
|
|
143
|
-
|
|
144
|
-
// Create workflow directory structure
|
|
145
|
-
const contentsDir = path.join(workflowDir, 'Contents')
|
|
146
|
-
ensureDir(contentsDir)
|
|
147
|
-
|
|
148
|
-
const infoPlist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
149
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
150
|
-
<plist version="1.0">
|
|
151
|
-
<dict>
|
|
152
|
-
<key>NSServices</key>
|
|
153
|
-
<array>
|
|
154
|
-
<dict>
|
|
155
|
-
<key>NSMenuItem</key>
|
|
156
|
-
<dict>
|
|
157
|
-
<key>default</key>
|
|
158
|
-
<string>New Rune</string>
|
|
159
|
-
</dict>
|
|
160
|
-
<key>NSMessage</key>
|
|
161
|
-
<string>runWorkflowAsService</string>
|
|
162
|
-
<key>NSRequiredContext</key>
|
|
163
|
-
<dict/>
|
|
164
|
-
<key>NSSendFileTypes</key>
|
|
165
|
-
<array>
|
|
166
|
-
<string>public.item</string>
|
|
167
|
-
</array>
|
|
168
|
-
</dict>
|
|
169
|
-
</array>
|
|
170
|
-
</dict>
|
|
171
|
-
</plist>`
|
|
172
|
-
|
|
173
|
-
const wflow = `<?xml version="1.0" encoding="UTF-8"?>
|
|
174
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
175
|
-
<plist version="1.0">
|
|
176
|
-
<dict>
|
|
177
|
-
<key>AMApplicationBuild</key>
|
|
178
|
-
<string>523</string>
|
|
179
|
-
<key>AMApplicationVersion</key>
|
|
180
|
-
<string>2.10</string>
|
|
181
|
-
<key>AMDocumentVersion</key>
|
|
182
|
-
<string>2</string>
|
|
183
|
-
<key>actions</key>
|
|
184
|
-
<array>
|
|
185
|
-
<dict>
|
|
186
|
-
<key>action</key>
|
|
187
|
-
<dict>
|
|
188
|
-
<key>AMAccepts</key>
|
|
189
|
-
<dict>
|
|
190
|
-
<key>Container</key>
|
|
191
|
-
<string>List</string>
|
|
192
|
-
<key>Optional</key>
|
|
193
|
-
<true/>
|
|
194
|
-
<key>Types</key>
|
|
195
|
-
<array>
|
|
196
|
-
<string>com.apple.cocoa.path</string>
|
|
197
|
-
</array>
|
|
198
|
-
</dict>
|
|
199
|
-
<key>AMActionVersion</key>
|
|
200
|
-
<string>2.0.3</string>
|
|
201
|
-
<key>AMApplication</key>
|
|
202
|
-
<array>
|
|
203
|
-
<string>Automator</string>
|
|
204
|
-
</array>
|
|
205
|
-
<key>AMParameterProperties</key>
|
|
206
|
-
<dict>
|
|
207
|
-
<key>COMMAND_STRING</key>
|
|
208
|
-
<dict/>
|
|
209
|
-
<key>CheckedForUserDefaultShell</key>
|
|
210
|
-
<dict/>
|
|
211
|
-
<key>inputMethod</key>
|
|
212
|
-
<dict/>
|
|
213
|
-
<key>shell</key>
|
|
214
|
-
<dict/>
|
|
215
|
-
<key>source</key>
|
|
216
|
-
<dict/>
|
|
217
|
-
</dict>
|
|
218
|
-
<key>AMProvides</key>
|
|
219
|
-
<dict>
|
|
220
|
-
<key>Container</key>
|
|
221
|
-
<string>List</string>
|
|
222
|
-
<key>Types</key>
|
|
223
|
-
<array>
|
|
224
|
-
<string>com.apple.cocoa.path</string>
|
|
225
|
-
</array>
|
|
226
|
-
</dict>
|
|
227
|
-
<key>ActionBundlePath</key>
|
|
228
|
-
<string>/System/Library/Automator/Run Shell Script.action</string>
|
|
229
|
-
<key>ActionName</key>
|
|
230
|
-
<string>Run Shell Script</string>
|
|
231
|
-
<key>ActionParameters</key>
|
|
232
|
-
<dict>
|
|
233
|
-
<key>COMMAND_STRING</key>
|
|
234
|
-
<string>"${helperScript}" "$@"</string>
|
|
235
|
-
<key>CheckedForUserDefaultShell</key>
|
|
236
|
-
<true/>
|
|
237
|
-
<key>inputMethod</key>
|
|
238
|
-
<integer>1</integer>
|
|
239
|
-
<key>shell</key>
|
|
240
|
-
<string>/bin/bash</string>
|
|
241
|
-
<key>source</key>
|
|
242
|
-
<string></string>
|
|
243
|
-
</dict>
|
|
244
|
-
<key>BundleIdentifier</key>
|
|
245
|
-
<string>com.apple.RunShellScript</string>
|
|
246
|
-
<key>CFBundleVersion</key>
|
|
247
|
-
<string>2.0.3</string>
|
|
248
|
-
<key>CanShowSelectedItemsWhenRun</key>
|
|
249
|
-
<false/>
|
|
250
|
-
<key>CanShowWhenRun</key>
|
|
251
|
-
<true/>
|
|
252
|
-
<key>Category</key>
|
|
253
|
-
<array>
|
|
254
|
-
<string>AMCategoryUtilities</string>
|
|
255
|
-
</array>
|
|
256
|
-
<key>Class Name</key>
|
|
257
|
-
<string>RunShellScriptAction</string>
|
|
258
|
-
<key>InputUUID</key>
|
|
259
|
-
<string>A5C0F22C-6B6A-4E8E-8B6A-1F6C4E5D3A2B</string>
|
|
260
|
-
<key>Keywords</key>
|
|
261
|
-
<array>
|
|
262
|
-
<string>Shell</string>
|
|
263
|
-
<string>Script</string>
|
|
264
|
-
</array>
|
|
265
|
-
<key>OutputUUID</key>
|
|
266
|
-
<string>B7D1F33D-7C7B-5F9F-9C7B-2A7D5F6E4B3C</string>
|
|
267
|
-
<key>UUID</key>
|
|
268
|
-
<string>C8E2A44E-8D8C-6A0A-0D8C-3A8E6A7F5C4D</string>
|
|
269
|
-
<key>UnlocalizedApplications</key>
|
|
270
|
-
<array>
|
|
271
|
-
<string>Automator</string>
|
|
272
|
-
</array>
|
|
273
|
-
<key>arguments</key>
|
|
274
|
-
<dict>
|
|
275
|
-
<key>0</key>
|
|
276
|
-
<dict>
|
|
277
|
-
<key>default value</key>
|
|
278
|
-
<string>/bin/bash</string>
|
|
279
|
-
<key>name</key>
|
|
280
|
-
<string>shell</string>
|
|
281
|
-
<key>required</key>
|
|
282
|
-
<string>0</string>
|
|
283
|
-
<key>type</key>
|
|
284
|
-
<string>0</string>
|
|
285
|
-
</dict>
|
|
286
|
-
<key>1</key>
|
|
287
|
-
<dict>
|
|
288
|
-
<key>default value</key>
|
|
289
|
-
<string></string>
|
|
290
|
-
<key>name</key>
|
|
291
|
-
<string>COMMAND_STRING</string>
|
|
292
|
-
<key>required</key>
|
|
293
|
-
<string>0</string>
|
|
294
|
-
<key>type</key>
|
|
295
|
-
<string>0</string>
|
|
296
|
-
</dict>
|
|
297
|
-
<key>2</key>
|
|
298
|
-
<dict>
|
|
299
|
-
<key>default value</key>
|
|
300
|
-
<integer>1</integer>
|
|
301
|
-
<key>name</key>
|
|
302
|
-
<string>inputMethod</string>
|
|
303
|
-
<key>required</key>
|
|
304
|
-
<string>0</string>
|
|
305
|
-
<key>type</key>
|
|
306
|
-
<string>0</string>
|
|
307
|
-
</dict>
|
|
308
|
-
<key>3</key>
|
|
309
|
-
<dict>
|
|
310
|
-
<key>default value</key>
|
|
311
|
-
<string></string>
|
|
312
|
-
<key>name</key>
|
|
313
|
-
<string>source</string>
|
|
314
|
-
<key>required</key>
|
|
315
|
-
<string>0</string>
|
|
316
|
-
<key>type</key>
|
|
317
|
-
<string>0</string>
|
|
318
|
-
</dict>
|
|
319
|
-
</dict>
|
|
320
|
-
<key>isViewVisible</key>
|
|
321
|
-
<true/>
|
|
322
|
-
<key>location</key>
|
|
323
|
-
<string>309.000000:627.000000</string>
|
|
324
|
-
<key>nibPath</key>
|
|
325
|
-
<string>/System/Library/Automator/Run Shell Script.action/Contents/Resources/Base.lproj/main.nib</string>
|
|
326
|
-
</dict>
|
|
327
|
-
</dict>
|
|
328
|
-
</array>
|
|
329
|
-
<key>connectors</key>
|
|
330
|
-
<dict/>
|
|
331
|
-
<key>workflowMetaData</key>
|
|
332
|
-
<dict>
|
|
333
|
-
<key>serviceInputTypeIdentifier</key>
|
|
334
|
-
<string>com.apple.Automator.fileSystemObject</string>
|
|
335
|
-
<key>serviceApplicationBundleID</key>
|
|
336
|
-
<string>com.apple.Finder</string>
|
|
337
|
-
<key>workflowTypeIdentifier</key>
|
|
338
|
-
<string>com.apple.Automator.servicesMenu</string>
|
|
339
|
-
</dict>
|
|
340
|
-
</dict>
|
|
341
|
-
</plist>`
|
|
342
|
-
|
|
343
|
-
fs.writeFileSync(path.join(contentsDir, 'Info.plist'), infoPlist)
|
|
344
|
-
fs.writeFileSync(path.join(contentsDir, 'document.wflow'), wflow)
|
|
345
|
-
|
|
346
|
-
// Flush services cache
|
|
347
|
-
try {
|
|
348
|
-
execSync('/System/Library/CoreServices/pbs -flush', { stdio: 'ignore' })
|
|
349
|
-
} catch {}
|
|
350
|
-
try {
|
|
351
|
-
execSync('/System/Library/CoreServices/pbs -update', { stdio: 'ignore' })
|
|
352
|
-
} catch {}
|
|
353
|
-
|
|
354
|
-
console.log(' ✅ Quick Action installed: Right-click folder → Quick Actions → New Rune')
|
|
355
|
-
console.log(' 💡 If not visible, go to System Settings → Extensions → Finder → enable "New Rune"')
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
function registerFileAssociation(projectRoot) {
|
|
359
|
-
console.log(' Registering .rune file association...')
|
|
360
|
-
|
|
361
|
-
// Find the actual Electron binary (not the Node wrapper script)
|
|
362
|
-
const electronBinary = path.join(projectRoot, 'node_modules', 'electron', 'dist', 'Electron.app', 'Contents', 'MacOS', 'Electron')
|
|
363
|
-
|
|
364
|
-
// Create a minimal .app wrapper for macOS file association
|
|
365
|
-
const appDir = path.join(APP_DIR, 'Rune.app')
|
|
366
|
-
// Remove old app if exists
|
|
367
|
-
if (fs.existsSync(appDir)) fs.rmSync(appDir, { recursive: true })
|
|
368
|
-
const appContents = path.join(appDir, 'Contents')
|
|
369
|
-
const appMacOS = path.join(appContents, 'MacOS')
|
|
370
|
-
ensureDir(appMacOS)
|
|
371
|
-
|
|
372
|
-
// Info.plist with file association
|
|
373
|
-
const appPlist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
374
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
375
|
-
<plist version="1.0">
|
|
376
|
-
<dict>
|
|
377
|
-
<key>CFBundleIdentifier</key>
|
|
378
|
-
<string>com.studio-h.rune</string>
|
|
379
|
-
<key>CFBundleName</key>
|
|
380
|
-
<string>Rune</string>
|
|
381
|
-
<key>CFBundleDisplayName</key>
|
|
382
|
-
<string>Rune</string>
|
|
383
|
-
<key>CFBundleVersion</key>
|
|
384
|
-
<string>0.1.0</string>
|
|
385
|
-
<key>CFBundleShortVersionString</key>
|
|
386
|
-
<string>0.1.0</string>
|
|
387
|
-
<key>CFBundleExecutable</key>
|
|
388
|
-
<string>rune-launcher</string>
|
|
389
|
-
<key>CFBundlePackageType</key>
|
|
390
|
-
<string>APPL</string>
|
|
391
|
-
<key>LSMinimumSystemVersion</key>
|
|
392
|
-
<string>10.15</string>
|
|
393
|
-
<key>CFBundleDocumentTypes</key>
|
|
394
|
-
<array>
|
|
395
|
-
<dict>
|
|
396
|
-
<key>CFBundleTypeExtensions</key>
|
|
397
|
-
<array>
|
|
398
|
-
<string>rune</string>
|
|
399
|
-
</array>
|
|
400
|
-
<key>CFBundleTypeName</key>
|
|
401
|
-
<string>Rune Agent File</string>
|
|
402
|
-
<key>CFBundleTypeRole</key>
|
|
403
|
-
<string>Editor</string>
|
|
404
|
-
<key>LSHandlerRank</key>
|
|
405
|
-
<string>Owner</string>
|
|
406
|
-
<key>LSItemContentTypes</key>
|
|
407
|
-
<array>
|
|
408
|
-
<string>com.studio-h.rune</string>
|
|
409
|
-
</array>
|
|
410
|
-
</dict>
|
|
411
|
-
</array>
|
|
412
|
-
<key>UTExportedTypeDeclarations</key>
|
|
413
|
-
<array>
|
|
414
|
-
<dict>
|
|
415
|
-
<key>UTTypeConformsTo</key>
|
|
416
|
-
<array>
|
|
417
|
-
<string>public.json</string>
|
|
418
|
-
</array>
|
|
419
|
-
<key>UTTypeDescription</key>
|
|
420
|
-
<string>Rune Agent File</string>
|
|
421
|
-
<key>UTTypeIdentifier</key>
|
|
422
|
-
<string>com.studio-h.rune</string>
|
|
423
|
-
<key>UTTypeTagSpecification</key>
|
|
424
|
-
<dict>
|
|
425
|
-
<key>public.filename-extension</key>
|
|
426
|
-
<array>
|
|
427
|
-
<string>rune</string>
|
|
428
|
-
</array>
|
|
429
|
-
</dict>
|
|
430
|
-
</dict>
|
|
431
|
-
</array>
|
|
432
|
-
<key>LSUIElement</key>
|
|
433
|
-
<true/>
|
|
434
|
-
</dict>
|
|
435
|
-
</plist>`
|
|
436
|
-
|
|
437
|
-
fs.writeFileSync(path.join(appContents, 'Info.plist'), appPlist)
|
|
438
|
-
|
|
439
|
-
// Launcher script — dynamically finds the rune binary and uses it to resolve Electron
|
|
440
|
-
const runeBin = path.resolve(__dirname, 'rune.js')
|
|
441
|
-
const nodebin = process.execPath
|
|
442
|
-
const launcherScript = `#!/bin/bash
|
|
443
|
-
# Dynamically resolve Rune's install location via the CLI binary
|
|
444
|
-
RUNE_BIN="${runeBin}"
|
|
445
|
-
RUNE_PROJECT="$(dirname "$(dirname "$RUNE_BIN")")"
|
|
446
|
-
|
|
447
|
-
# Find Electron binary
|
|
448
|
-
ELECTRON="$RUNE_PROJECT/node_modules/electron/dist/Electron.app/Contents/MacOS/Electron"
|
|
449
|
-
|
|
450
|
-
if [ ! -f "$ELECTRON" ]; then
|
|
451
|
-
# Fallback: try the path saved by rune install
|
|
452
|
-
if [ -f "$HOME/.rune/project-path" ]; then
|
|
453
|
-
RUNE_PROJECT="$(cat "$HOME/.rune/project-path")"
|
|
454
|
-
ELECTRON="$RUNE_PROJECT/node_modules/electron/dist/Electron.app/Contents/MacOS/Electron"
|
|
455
|
-
fi
|
|
456
|
-
fi
|
|
457
|
-
|
|
458
|
-
if [ ! -f "$ELECTRON" ]; then
|
|
459
|
-
osascript -e 'display dialog "Electron not found. Run: rune install" buttons {"OK"}'
|
|
460
|
-
exit 1
|
|
461
|
-
fi
|
|
462
|
-
|
|
463
|
-
# CRITICAL: unset this so Electron runs as an app, not plain Node.js
|
|
464
|
-
unset ELECTRON_RUN_AS_NODE
|
|
465
|
-
|
|
466
|
-
# macOS passes the file path as the last argument when double-clicking
|
|
467
|
-
FILE=""
|
|
468
|
-
for arg in "$@"; do
|
|
469
|
-
if [[ "$arg" == *.rune ]]; then
|
|
470
|
-
FILE="$arg"
|
|
471
|
-
break
|
|
472
|
-
fi
|
|
473
|
-
done
|
|
474
|
-
|
|
475
|
-
cd "$RUNE_PROJECT"
|
|
476
|
-
if [ -n "$FILE" ]; then
|
|
477
|
-
exec "$ELECTRON" . "$FILE"
|
|
478
|
-
else
|
|
479
|
-
exec "$ELECTRON" .
|
|
480
|
-
fi
|
|
481
|
-
`
|
|
482
|
-
fs.writeFileSync(path.join(appMacOS, 'rune-launcher'), launcherScript, { mode: 0o755 })
|
|
483
|
-
|
|
484
|
-
// Register the app with Launch Services
|
|
485
|
-
try {
|
|
486
|
-
execSync(`/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -R -f "${appDir}"`, { stdio: 'ignore' })
|
|
487
|
-
console.log(' ✅ .rune file association registered')
|
|
488
|
-
} catch {
|
|
489
|
-
console.log(' ⚠️ Could not auto-register. Double-click a .rune file and choose "Open With" → Rune')
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
// Set as default handler for .rune files
|
|
493
|
-
try {
|
|
494
|
-
execSync(`defaults write com.apple.LaunchServices/com.apple.launchservices.secure LSHandlers -array-add '{ LSHandlerContentType = "com.studio-h.rune"; LSHandlerRoleAll = "com.studio-h.rune"; }'`, { stdio: 'ignore' })
|
|
495
|
-
} catch {}
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
function installChannelPlugin() {
|
|
499
|
-
console.log(' Installing Claude Code channel plugin...')
|
|
500
|
-
|
|
501
|
-
const projectRoot = path.resolve(__dirname, '..')
|
|
502
|
-
const claudePluginsDir = path.join(os.homedir(), '.claude', 'plugins')
|
|
503
|
-
const installedFile = path.join(claudePluginsDir, 'installed_plugins.json')
|
|
504
|
-
const marketplacesFile = path.join(claudePluginsDir, 'known_marketplaces.json')
|
|
505
|
-
|
|
506
|
-
// Check if Claude Code plugins dir exists
|
|
507
|
-
if (!fs.existsSync(claudePluginsDir)) {
|
|
508
|
-
console.log(' ⚠️ Claude Code not found (~/.claude/plugins missing). Install Claude Code first.')
|
|
509
|
-
return
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
try {
|
|
513
|
-
// 1. Register marketplace
|
|
514
|
-
const marketplaceName = 'rune'
|
|
515
|
-
let marketplaces = {}
|
|
516
|
-
if (fs.existsSync(marketplacesFile)) {
|
|
517
|
-
try { marketplaces = JSON.parse(fs.readFileSync(marketplacesFile, 'utf-8')) } catch {}
|
|
518
|
-
}
|
|
519
|
-
if (!marketplaces[marketplaceName]) {
|
|
520
|
-
marketplaces[marketplaceName] = {
|
|
521
|
-
source: { source: 'github', repo: 'gilhyun/Rune' },
|
|
522
|
-
installLocation: path.join(claudePluginsDir, 'marketplaces', marketplaceName),
|
|
523
|
-
lastUpdated: new Date().toISOString(),
|
|
524
|
-
}
|
|
525
|
-
fs.writeFileSync(marketplacesFile, JSON.stringify(marketplaces, null, 2))
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
// 2. Copy plugin to cache
|
|
529
|
-
const pluginJson = JSON.parse(fs.readFileSync(path.join(projectRoot, '.claude-plugin', 'plugin.json'), 'utf-8'))
|
|
530
|
-
const version = pluginJson.version || '0.1.0'
|
|
531
|
-
const cacheDir = path.join(claudePluginsDir, 'cache', marketplaceName, 'rune-channel', version)
|
|
532
|
-
ensureDir(cacheDir)
|
|
533
|
-
|
|
534
|
-
// Copy essential files
|
|
535
|
-
const filesToCopy = ['.claude-plugin', 'dist/rune-channel.js', 'package.json', 'LICENSE']
|
|
536
|
-
for (const f of filesToCopy) {
|
|
537
|
-
const src = path.join(projectRoot, f)
|
|
538
|
-
const dst = path.join(cacheDir, f)
|
|
539
|
-
if (!fs.existsSync(src)) continue
|
|
540
|
-
const stat = fs.statSync(src)
|
|
541
|
-
if (stat.isDirectory()) {
|
|
542
|
-
ensureDir(dst)
|
|
543
|
-
for (const child of fs.readdirSync(src)) {
|
|
544
|
-
fs.copyFileSync(path.join(src, child), path.join(dst, child))
|
|
545
|
-
}
|
|
546
|
-
} else {
|
|
547
|
-
ensureDir(path.dirname(dst))
|
|
548
|
-
fs.copyFileSync(src, dst)
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
// 3. Register in installed_plugins.json
|
|
553
|
-
let installed = { version: 2, plugins: {} }
|
|
554
|
-
if (fs.existsSync(installedFile)) {
|
|
555
|
-
try { installed = JSON.parse(fs.readFileSync(installedFile, 'utf-8')) } catch {}
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
const pluginKey = `rune-channel@${marketplaceName}`
|
|
559
|
-
const entries = installed.plugins[pluginKey] || []
|
|
560
|
-
// Add/update user-scope entry
|
|
561
|
-
const userEntry = entries.find(e => e.scope === 'user')
|
|
562
|
-
const newEntry = {
|
|
563
|
-
scope: 'user',
|
|
564
|
-
installPath: cacheDir,
|
|
565
|
-
version,
|
|
566
|
-
installedAt: userEntry?.installedAt || new Date().toISOString(),
|
|
567
|
-
lastUpdated: new Date().toISOString(),
|
|
568
|
-
}
|
|
569
|
-
if (userEntry) {
|
|
570
|
-
Object.assign(userEntry, newEntry)
|
|
571
|
-
} else {
|
|
572
|
-
entries.push(newEntry)
|
|
573
|
-
}
|
|
574
|
-
installed.plugins[pluginKey] = entries
|
|
575
|
-
fs.writeFileSync(installedFile, JSON.stringify(installed, null, 2))
|
|
576
|
-
|
|
577
|
-
console.log(' ✅ Channel plugin installed')
|
|
578
|
-
} catch (e) {
|
|
579
|
-
console.log(` ⚠️ Plugin install failed: ${e.message}`)
|
|
580
|
-
console.log(' Run manually inside Claude Code: /plugin install rune-channel@rune')
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
37
|
|
|
584
38
|
// ── new ──────────────────────────────────────────
|
|
585
39
|
|
|
@@ -601,8 +55,8 @@ function createRune(name, allArgs) {
|
|
|
601
55
|
const filePath = path.resolve(process.cwd(), fileName)
|
|
602
56
|
|
|
603
57
|
if (fs.existsSync(filePath)) {
|
|
604
|
-
console.log(` ⚠️ ${fileName} already exists
|
|
605
|
-
|
|
58
|
+
console.log(` ⚠️ ${fileName} already exists.`)
|
|
59
|
+
process.exit(1)
|
|
606
60
|
}
|
|
607
61
|
|
|
608
62
|
const runeData = {
|
|
@@ -619,66 +73,9 @@ function createRune(name, allArgs) {
|
|
|
619
73
|
console.log(` Role: ${runeData.role}`)
|
|
620
74
|
console.log(` Path: ${filePath}`)
|
|
621
75
|
console.log('')
|
|
622
|
-
console.log(
|
|
623
|
-
console.log(` rune open ${fileName}`)
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
// ── open ─────────────────────────────────────────
|
|
627
|
-
|
|
628
|
-
function findProjectRoot() {
|
|
629
|
-
// Always resolve from the actual package location (works for both global and local)
|
|
630
|
-
const fromBin = path.resolve(__dirname, '..')
|
|
631
|
-
const electronCheck = path.join(fromBin, 'node_modules', 'electron', 'dist', 'Electron.app', 'Contents', 'MacOS', 'Electron')
|
|
632
|
-
if (fs.existsSync(electronCheck)) return fromBin
|
|
633
|
-
|
|
634
|
-
// Fallback: check saved path
|
|
635
|
-
const savedPath = path.join(RUNE_HOME, 'project-path')
|
|
636
|
-
if (fs.existsSync(savedPath)) {
|
|
637
|
-
const saved = fs.readFileSync(savedPath, 'utf-8').trim()
|
|
638
|
-
const savedElectron = path.join(saved, 'node_modules', 'electron', 'dist', 'Electron.app', 'Contents', 'MacOS', 'Electron')
|
|
639
|
-
if (fs.existsSync(savedElectron)) return saved
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
return null
|
|
76
|
+
console.log(` Run: rune run ${fileName} "your prompt"`)
|
|
643
77
|
}
|
|
644
78
|
|
|
645
|
-
function openRune(file) {
|
|
646
|
-
if (!file) {
|
|
647
|
-
console.log('Usage: rune open <file.rune>')
|
|
648
|
-
process.exit(1)
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
const filePath = path.resolve(process.cwd(), file)
|
|
652
|
-
if (!fs.existsSync(filePath)) {
|
|
653
|
-
console.error(` ❌ File not found: ${filePath}`)
|
|
654
|
-
process.exit(1)
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
const projectRoot = findProjectRoot()
|
|
658
|
-
if (!projectRoot) {
|
|
659
|
-
console.error(' ❌ Electron not found. Run `rune install` first.')
|
|
660
|
-
process.exit(1)
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
const electronBinary = path.join(projectRoot, 'node_modules', 'electron', 'dist', 'Electron.app', 'Contents', 'MacOS', 'Electron')
|
|
664
|
-
|
|
665
|
-
console.log(`🔮 Opening ${path.basename(filePath)}...`)
|
|
666
|
-
|
|
667
|
-
// CRITICAL: unset ELECTRON_RUN_AS_NODE — Claude Code sets it,
|
|
668
|
-
// which makes Electron act as plain Node.js instead of an Electron app
|
|
669
|
-
const env = { ...process.env }
|
|
670
|
-
delete env.ELECTRON_RUN_AS_NODE
|
|
671
|
-
|
|
672
|
-
const child = spawn(electronBinary, ['.', filePath], {
|
|
673
|
-
cwd: projectRoot,
|
|
674
|
-
detached: true,
|
|
675
|
-
stdio: 'ignore',
|
|
676
|
-
env,
|
|
677
|
-
})
|
|
678
|
-
child.unref()
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
// ── run (headless) ──────────────────────────────
|
|
682
79
|
|
|
683
80
|
function runRune(file, restArgs) {
|
|
684
81
|
if (!file) {
|
|
@@ -782,16 +179,9 @@ function runRune(file, restArgs) {
|
|
|
782
179
|
if (autoMode) {
|
|
783
180
|
console.log(`🔮 [auto] ${rune.name} is working on: ${prompt}\n`)
|
|
784
181
|
|
|
785
|
-
// Temporarily hide .mcp.json to prevent MCP interference
|
|
786
|
-
const mcpPath = path.join(folderPath, '.mcp.json')
|
|
787
|
-
const mcpBackup = path.join(folderPath, '.mcp.json.bak')
|
|
788
|
-
let mcpHidden = false
|
|
789
|
-
if (fs.existsSync(mcpPath)) {
|
|
790
|
-
fs.renameSync(mcpPath, mcpBackup)
|
|
791
|
-
mcpHidden = true
|
|
792
|
-
}
|
|
793
|
-
|
|
794
182
|
const claudeArgs = ['-p', '--print',
|
|
183
|
+
'--mcp-config', '{"mcpServers":{}}',
|
|
184
|
+
'--strict-mcp-config',
|
|
795
185
|
'--dangerously-skip-permissions',
|
|
796
186
|
'--verbose',
|
|
797
187
|
'--output-format', 'stream-json',
|
|
@@ -805,12 +195,6 @@ function runRune(file, restArgs) {
|
|
|
805
195
|
}
|
|
806
196
|
claudeArgs.push(prompt)
|
|
807
197
|
|
|
808
|
-
const restoreMcp = () => {
|
|
809
|
-
if (mcpHidden && fs.existsSync(mcpBackup)) {
|
|
810
|
-
fs.renameSync(mcpBackup, mcpPath)
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
|
|
814
198
|
const child = spawn('claude', claudeArgs, {
|
|
815
199
|
cwd: folderPath,
|
|
816
200
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
@@ -875,7 +259,6 @@ function runRune(file, restArgs) {
|
|
|
875
259
|
child.stderr.on('data', (d) => { process.stderr.write(d) })
|
|
876
260
|
|
|
877
261
|
child.on('close', (code) => {
|
|
878
|
-
restoreMcp()
|
|
879
262
|
// Save to history
|
|
880
263
|
rune.history = rune.history || []
|
|
881
264
|
rune.history.push({ role: 'user', text: prompt, ts: Date.now() })
|
|
@@ -908,16 +291,15 @@ function runRune(file, restArgs) {
|
|
|
908
291
|
process.exit(code || 0)
|
|
909
292
|
})
|
|
910
293
|
|
|
911
|
-
// Restore .mcp.json if process is killed
|
|
912
|
-
process.on('SIGINT', restoreMcp)
|
|
913
|
-
process.on('SIGTERM', restoreMcp)
|
|
914
|
-
|
|
915
294
|
return
|
|
916
295
|
}
|
|
917
296
|
|
|
918
297
|
// Normal mode: print-only, no tool execution
|
|
919
|
-
|
|
920
|
-
|
|
298
|
+
const claudeArgs = [
|
|
299
|
+
'-p', '--print',
|
|
300
|
+
'--mcp-config', '{"mcpServers":{}}',
|
|
301
|
+
'--strict-mcp-config',
|
|
302
|
+
]
|
|
921
303
|
if (systemPrompt) {
|
|
922
304
|
claudeArgs.push('--system-prompt', systemPrompt + `\nWorking folder: ${folderPath}`)
|
|
923
305
|
}
|
|
@@ -926,9 +308,8 @@ function runRune(file, restArgs) {
|
|
|
926
308
|
}
|
|
927
309
|
claudeArgs.push('--', prompt)
|
|
928
310
|
|
|
929
|
-
const os = require('os')
|
|
930
311
|
const child = spawn('claude', claudeArgs, {
|
|
931
|
-
cwd:
|
|
312
|
+
cwd: folderPath,
|
|
932
313
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
933
314
|
env: { ...process.env },
|
|
934
315
|
})
|
|
@@ -1037,16 +418,9 @@ async function pipeRunes(args) {
|
|
|
1037
418
|
const useAuto = autoMode && isLast
|
|
1038
419
|
|
|
1039
420
|
if (useAuto) {
|
|
1040
|
-
// Temporarily hide .mcp.json
|
|
1041
|
-
const mcpPath = path.join(folderPath, '.mcp.json')
|
|
1042
|
-
const mcpBackup = path.join(folderPath, '.mcp.json.pipe.bak')
|
|
1043
|
-
let mcpHidden = false
|
|
1044
|
-
if (fs.existsSync(mcpPath)) {
|
|
1045
|
-
fs.renameSync(mcpPath, mcpBackup)
|
|
1046
|
-
mcpHidden = true
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
421
|
const claudeArgs = ['-p', '--print',
|
|
422
|
+
'--mcp-config', '{"mcpServers":{}}',
|
|
423
|
+
'--strict-mcp-config',
|
|
1050
424
|
'--dangerously-skip-permissions',
|
|
1051
425
|
'--verbose',
|
|
1052
426
|
'--output-format', 'stream-json',
|
|
@@ -1097,7 +471,6 @@ async function pipeRunes(args) {
|
|
|
1097
471
|
})
|
|
1098
472
|
child.stderr.on('data', (d) => { process.stderr.write(d) })
|
|
1099
473
|
child.on('close', (code) => {
|
|
1100
|
-
if (mcpHidden && fs.existsSync(mcpBackup)) fs.renameSync(mcpBackup, mcpPath)
|
|
1101
474
|
if (code !== 0) reject(new Error(`Agent ${rune.name} exited with code ${code}`))
|
|
1102
475
|
else resolve(fullOutput.trim())
|
|
1103
476
|
})
|
|
@@ -1111,9 +484,12 @@ async function pipeRunes(args) {
|
|
|
1111
484
|
currentInput = output
|
|
1112
485
|
|
|
1113
486
|
} else {
|
|
1114
|
-
// Normal pipe step: text output only
|
|
1115
|
-
const
|
|
1116
|
-
|
|
487
|
+
// Normal pipe step: text output only
|
|
488
|
+
const claudeArgs = [
|
|
489
|
+
'-p', '--print',
|
|
490
|
+
'--mcp-config', '{"mcpServers":{}}',
|
|
491
|
+
'--strict-mcp-config',
|
|
492
|
+
]
|
|
1117
493
|
if (systemParts.length > 0) {
|
|
1118
494
|
claudeArgs.push('--system-prompt', systemParts.join('\n') + `\nWorking folder: ${folderPath}`)
|
|
1119
495
|
}
|
|
@@ -1121,7 +497,7 @@ async function pipeRunes(args) {
|
|
|
1121
497
|
|
|
1122
498
|
const output = await new Promise((resolve, reject) => {
|
|
1123
499
|
const child = spawn('claude', claudeArgs, {
|
|
1124
|
-
cwd:
|
|
500
|
+
cwd: folderPath,
|
|
1125
501
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1126
502
|
env: { ...process.env },
|
|
1127
503
|
})
|
|
@@ -1294,15 +670,9 @@ async function loopRunes(args) {
|
|
|
1294
670
|
|
|
1295
671
|
async function runAgent(name, folderPath, systemParts, prompt, autoMode) {
|
|
1296
672
|
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
673
|
const claudeArgs = ['-p', '--print',
|
|
674
|
+
'--mcp-config', '{"mcpServers":{}}',
|
|
675
|
+
'--strict-mcp-config',
|
|
1306
676
|
'--dangerously-skip-permissions',
|
|
1307
677
|
'--verbose',
|
|
1308
678
|
'--output-format', 'stream-json',
|
|
@@ -1352,7 +722,6 @@ async function runAgent(name, folderPath, systemParts, prompt, autoMode) {
|
|
|
1352
722
|
})
|
|
1353
723
|
child.stderr.on('data', (d) => { process.stderr.write(d) })
|
|
1354
724
|
child.on('close', (code) => {
|
|
1355
|
-
if (mcpHidden && fs.existsSync(mcpBackup)) fs.renameSync(mcpBackup, mcpPath)
|
|
1356
725
|
if (code !== 0) reject(new Error(`Agent ${name} exited with code ${code}`))
|
|
1357
726
|
else resolve(fullOutput.trim())
|
|
1358
727
|
})
|
|
@@ -1360,8 +729,11 @@ async function runAgent(name, folderPath, systemParts, prompt, autoMode) {
|
|
|
1360
729
|
|
|
1361
730
|
return output
|
|
1362
731
|
} else {
|
|
1363
|
-
const
|
|
1364
|
-
|
|
732
|
+
const claudeArgs = [
|
|
733
|
+
'-p', '--print',
|
|
734
|
+
'--mcp-config', '{"mcpServers":{}}',
|
|
735
|
+
'--strict-mcp-config',
|
|
736
|
+
]
|
|
1365
737
|
if (systemParts.length > 0) {
|
|
1366
738
|
claudeArgs.push('--system-prompt', systemParts.join('\n') + `\nWorking folder: ${folderPath}`)
|
|
1367
739
|
}
|
|
@@ -1369,7 +741,7 @@ async function runAgent(name, folderPath, systemParts, prompt, autoMode) {
|
|
|
1369
741
|
|
|
1370
742
|
const output = await new Promise((resolve, reject) => {
|
|
1371
743
|
const child = spawn('claude', claudeArgs, {
|
|
1372
|
-
cwd:
|
|
744
|
+
cwd: folderPath,
|
|
1373
745
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1374
746
|
env: { ...process.env },
|
|
1375
747
|
})
|
|
@@ -1610,52 +982,21 @@ function listRunes() {
|
|
|
1610
982
|
}
|
|
1611
983
|
}
|
|
1612
984
|
|
|
1613
|
-
// ── uninstall ────────────────────────────────────
|
|
1614
|
-
|
|
1615
|
-
function uninstall() {
|
|
1616
|
-
console.log('🔮 Uninstalling Rune...\n')
|
|
1617
|
-
|
|
1618
|
-
// Remove Quick Action
|
|
1619
|
-
const workflowDir = path.join(QUICK_ACTION_DIR, 'New Rune.workflow')
|
|
1620
|
-
if (fs.existsSync(workflowDir)) {
|
|
1621
|
-
fs.rmSync(workflowDir, { recursive: true })
|
|
1622
|
-
console.log(' ✅ Quick Action removed')
|
|
1623
|
-
}
|
|
1624
|
-
|
|
1625
|
-
// Remove .app wrapper
|
|
1626
|
-
const appDir = path.join(APP_DIR, 'Rune.app')
|
|
1627
|
-
if (fs.existsSync(appDir)) {
|
|
1628
|
-
fs.rmSync(appDir, { recursive: true })
|
|
1629
|
-
console.log(' ✅ App wrapper removed')
|
|
1630
|
-
}
|
|
1631
|
-
|
|
1632
|
-
// Unregister from Launch Services
|
|
1633
|
-
if (IS_MAC) {
|
|
1634
|
-
try {
|
|
1635
|
-
execSync(`/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -u "${appDir}"`, { stdio: 'ignore' })
|
|
1636
|
-
} catch {}
|
|
1637
|
-
}
|
|
1638
|
-
|
|
1639
|
-
console.log('\n🔮 Rune uninstalled. Your .rune files are preserved.\n')
|
|
1640
|
-
}
|
|
1641
|
-
|
|
1642
985
|
// ── help ─────────────────────────────────────────
|
|
1643
986
|
|
|
1644
987
|
function showHelp() {
|
|
1645
988
|
console.log(`
|
|
1646
|
-
🔮 Rune — File-based AI Agent Toolkit
|
|
989
|
+
🔮 Rune — File-based AI Agent Toolkit (CLI)
|
|
1647
990
|
|
|
1648
991
|
Usage:
|
|
1649
|
-
rune install Install Rune (build app, register file association, add Quick Action)
|
|
1650
992
|
rune new <name> Create a new .rune file in current directory
|
|
1651
993
|
--role "description" Set the agent's role
|
|
1652
|
-
rune
|
|
1653
|
-
rune run <file.rune> "prompt" Run agent headlessly (no GUI)
|
|
994
|
+
rune run <file.rune> "prompt" Run agent headlessly
|
|
1654
995
|
--auto Auto mode: agent writes files, runs commands, fixes errors
|
|
1655
996
|
--output json|text Output format (default: text)
|
|
1656
997
|
--log <file.json> Save structured log (tool calls, cost, duration)
|
|
1657
998
|
rune pipe <a.rune> <b.rune> ... "prompt" Chain agents in a pipeline
|
|
1658
|
-
--
|
|
999
|
+
--auto Last agent can write files and run commands
|
|
1659
1000
|
rune loop <doer.rune> <reviewer.rune> "prompt" Self-correction loop
|
|
1660
1001
|
--until "condition" Stop when reviewer output contains this text
|
|
1661
1002
|
--max-iterations N Max iterations (default: 5)
|
|
@@ -1666,7 +1007,6 @@ Usage:
|
|
|
1666
1007
|
--glob "*.ts" File pattern (for file-change)
|
|
1667
1008
|
--interval 5m Schedule interval (for cron: 30s, 5m, 1h)
|
|
1668
1009
|
rune list List .rune files in current directory
|
|
1669
|
-
rune uninstall Remove Rune integration (keeps .rune files)
|
|
1670
1010
|
rune help Show this help
|
|
1671
1011
|
|
|
1672
1012
|
Examples:
|
|
@@ -1675,7 +1015,8 @@ Examples:
|
|
|
1675
1015
|
rune pipe coder.rune reviewer.rune "Implement a login page"
|
|
1676
1016
|
rune loop coder.rune reviewer.rune "Build a REST API" --until "no critical issues" --max-iterations 3 --auto
|
|
1677
1017
|
rune watch reviewer.rune --on git-commit --prompt "Review this commit"
|
|
1678
|
-
rune watch monitor.rune --on cron --interval 5m --prompt "Check server health"
|
|
1679
1018
|
echo "Fix the bug in auth.ts" | rune run backend.rune
|
|
1019
|
+
|
|
1020
|
+
Looking for a GUI? Check out RuneChat: https://github.com/gilhyun/runechat
|
|
1680
1021
|
`)
|
|
1681
1022
|
}
|