openrune 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.
Files changed (36) hide show
  1. package/.claude-plugin/marketplace.json +17 -0
  2. package/.claude-plugin/plugin.json +24 -0
  3. package/LICENSE +21 -0
  4. package/README.md +257 -0
  5. package/bin/rune.js +718 -0
  6. package/bootstrap.js +4 -0
  7. package/channel/rune-channel.ts +467 -0
  8. package/electron-builder.yml +61 -0
  9. package/finder-extension/FinderSync.swift +47 -0
  10. package/finder-extension/RuneFinderSync.appex/Contents/Info.plist +27 -0
  11. package/finder-extension/RuneFinderSync.appex/Contents/MacOS/RuneFinderSync +0 -0
  12. package/finder-extension/main.swift +5 -0
  13. package/package.json +53 -0
  14. package/renderer/index.html +12 -0
  15. package/renderer/src/App.tsx +43 -0
  16. package/renderer/src/features/chat/activity-block.tsx +152 -0
  17. package/renderer/src/features/chat/chat-header.tsx +58 -0
  18. package/renderer/src/features/chat/chat-input.tsx +190 -0
  19. package/renderer/src/features/chat/chat-panel.tsx +150 -0
  20. package/renderer/src/features/chat/markdown-renderer.tsx +26 -0
  21. package/renderer/src/features/chat/message-bubble.tsx +79 -0
  22. package/renderer/src/features/chat/message-list.tsx +178 -0
  23. package/renderer/src/features/chat/types.ts +32 -0
  24. package/renderer/src/features/chat/use-chat.ts +251 -0
  25. package/renderer/src/features/terminal/terminal-panel.tsx +132 -0
  26. package/renderer/src/global.d.ts +29 -0
  27. package/renderer/src/globals.css +92 -0
  28. package/renderer/src/hooks/use-ipc.ts +24 -0
  29. package/renderer/src/lib/markdown.ts +83 -0
  30. package/renderer/src/lib/utils.ts +6 -0
  31. package/renderer/src/main.tsx +10 -0
  32. package/renderer/tsconfig.json +16 -0
  33. package/renderer/vite.config.ts +23 -0
  34. package/src/main.ts +782 -0
  35. package/src/preload.ts +58 -0
  36. package/tsconfig.json +14 -0
@@ -0,0 +1,92 @@
1
+ @import "tailwindcss";
2
+
3
+ @theme {
4
+ --color-background: oklch(0.145 0 0);
5
+ --color-foreground: oklch(0.93 0.005 270);
6
+ --color-card: oklch(0.205 0 0);
7
+ --color-muted: oklch(0.455 0 0);
8
+ --color-muted-foreground: oklch(0.556 0 0);
9
+ --color-border: oklch(1 0 0 / 10%);
10
+ --color-input: oklch(1 0 0 / 15%);
11
+ --color-ring: oklch(0.556 0 0);
12
+
13
+ --color-accent: oklch(0.777 0.152 181.912);
14
+ --color-accent-foreground: oklch(0.145 0 0);
15
+ --color-accent-red: oklch(0.704 0.191 22.216);
16
+
17
+ --color-background-secondary: oklch(0.17 0 0);
18
+ --color-background-tertiary: oklch(0.205 0 0);
19
+
20
+ --color-sidebar: oklch(0.17 0 0);
21
+ --color-sidebar-border: oklch(1 0 0 / 8%);
22
+ --color-surface: oklch(0.17 0 0);
23
+ --color-user-bg: oklch(0.19 0.02 181);
24
+ }
25
+
26
+ * { box-sizing: border-box; }
27
+ html, body, #root { height: 100%; }
28
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
29
+
30
+ ::-webkit-scrollbar { width: 6px; height: 6px; }
31
+ ::-webkit-scrollbar-track { background: transparent; }
32
+ ::-webkit-scrollbar-thumb { background: oklch(1 0 0 / 8%); border-radius: 3px; }
33
+ ::-webkit-scrollbar-thumb:hover { background: oklch(1 0 0 / 16%); }
34
+ ::-webkit-scrollbar-corner { background: transparent; }
35
+
36
+ ::selection { background: oklch(0.777 0.152 181.912 / 25%); color: inherit; }
37
+ :focus-visible { outline: 2px solid oklch(0.556 0 0 / 50%); outline-offset: 2px; }
38
+
39
+ .msg-content.rendered { white-space: normal; }
40
+ .msg-content code { background: oklch(1 0 0 / 6%); border-radius: 4px; padding: 2px 6px; font-family: 'JetBrains Mono', 'Cascadia Code', 'Fira Code', monospace; font-size: 0.875em; }
41
+ .msg-content pre { background: oklch(0.13 0 0); border: 1px solid oklch(1 0 0 / 8%); border-radius: 8px; padding: 14px 16px; overflow-x: auto; margin: 8px 0; }
42
+ .msg-content pre code { background: none; padding: 0; font-size: 12.5px; line-height: 1.6; }
43
+ .msg-content h3 { font-size: 15px; font-weight: 600; margin: 14px 0 6px; }
44
+ .msg-content h4 { font-size: 14px; font-weight: 600; margin: 12px 0 4px; }
45
+ .msg-content h5 { font-size: 13px; font-weight: 600; margin: 10px 0 4px; }
46
+ .msg-content h6 { font-size: 12px; font-weight: 500; margin: 8px 0 2px; color: oklch(0.556 0 0); }
47
+ .msg-content blockquote { border-left: 2px solid oklch(0.777 0.152 181.912 / 50%); padding: 4px 14px; margin: 8px 0; color: oklch(0.556 0 0); background: oklch(0.777 0.152 181.912 / 4%); border-radius: 0 6px 6px 0; }
48
+ .msg-content ul { padding-left: 22px; margin: 4px 0; list-style-type: disc; }
49
+ .msg-content ol { padding-left: 22px; margin: 4px 0; list-style-type: decimal; }
50
+ .msg-content li { margin: 2px 0; line-height: 1.7; }
51
+ .msg-content li::marker { color: oklch(0.556 0 0); }
52
+ .msg-content hr { border: none; border-top: 1px solid oklch(1 0 0 / 8%); margin: 12px 0; }
53
+ .msg-content strong { font-weight: 600; }
54
+ .msg-content em { font-style: italic; color: oklch(0.65 0 0); }
55
+ .msg-content p { margin: 4px 0; line-height: 1.7; }
56
+ .msg-content br { display: block; content: ''; margin: 2px 0; }
57
+ .msg-content a { color: oklch(0.777 0.152 181.912); text-decoration: none; }
58
+ .msg-content a:hover { text-decoration: underline; }
59
+ .msg-content table { border-collapse: collapse; width: 100%; margin: 10px 0; font-size: 12.5px; }
60
+ .msg-content th, .msg-content td { border: 1px solid oklch(1 0 0 / 8%); padding: 8px 12px; text-align: left; }
61
+ .msg-content th { background: oklch(0.17 0 0); font-weight: 500; }
62
+
63
+ .msg-content.streaming.empty-streaming::after { content: 'Thinking...'; animation: pulse 1.5s ease-in-out infinite; color: oklch(0.455 0 0); font-style: italic; }
64
+ @keyframes pulse { 0%, 100% { opacity: 0.4; } 50% { opacity: 1; } }
65
+ .msg-content.streaming.rendered { animation: fadeChunk 0.15s ease-out; }
66
+ @keyframes fadeChunk { from { opacity: 0.7; } to { opacity: 1; } }
67
+
68
+ /* ── Activity Blocks ──────────────────────────────── */
69
+ .activity-block { border-radius: 8px; border: 1px solid oklch(1 0 0 / 6%); overflow: hidden; animation: activityIn 0.2s ease-out; }
70
+ @keyframes activityIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }
71
+
72
+ .activity-header { display: flex; align-items: center; gap: 6px; width: 100%; padding: 6px 10px; background: none; border: none; cursor: pointer; text-align: left; font-size: 11px; line-height: 1.4; color: oklch(0.65 0 0); transition: background 0.1s; }
73
+ .activity-header:hover { background: oklch(1 0 0 / 3%); }
74
+ .activity-icon { flex-shrink: 0; }
75
+ .activity-label { font-weight: 600; font-size: 11px; white-space: nowrap; }
76
+ .activity-preview { color: oklch(0.5 0 0); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
77
+
78
+ .activity-result-badge { font-size: 9px; font-weight: 500; padding: 1px 5px; border-radius: 4px; background: oklch(0.45 0.12 155 / 15%); color: oklch(0.65 0.15 155); white-space: nowrap; }
79
+
80
+ .activity-body { padding: 0 10px 8px 28px; font-size: 11px; color: oklch(0.6 0 0); line-height: 1.5; }
81
+ .activity-thinking-body { font-style: italic; white-space: pre-wrap; word-break: break-word; }
82
+
83
+ .activity-args, .activity-result-content { background: oklch(0.12 0 0); border: 1px solid oklch(1 0 0 / 6%); border-radius: 6px; padding: 6px 10px; font-family: 'JetBrains Mono', 'Cascadia Code', 'Fira Code', monospace; font-size: 10.5px; line-height: 1.5; overflow-x: auto; white-space: pre-wrap; word-break: break-all; margin: 0; color: oklch(0.65 0 0); }
84
+ .activity-result-content { max-height: 200px; overflow-y: auto; word-break: break-word; color: oklch(0.6 0 0); }
85
+
86
+ .activity-thinking { border-left: 2px solid oklch(0.65 0.15 300); background: oklch(0.65 0.15 300 / 4%); }
87
+ .activity-tool-use { border-left: 2px solid oklch(0.777 0.152 181.912); background: oklch(0.777 0.152 181.912 / 4%); }
88
+ .activity-tool-result { border-left: 2px solid oklch(0.65 0.15 155); background: oklch(0.65 0.15 155 / 4%); }
89
+
90
+ /* ── Sonner Toast ─────────────────────────────────── */
91
+ [data-sonner-toaster] { pointer-events: none !important; }
92
+ [data-sonner-toast] { pointer-events: auto !important; }
@@ -0,0 +1,24 @@
1
+ import { useEffect, useCallback, useRef } from 'react'
2
+
3
+ export function useIPCOn(channel: RuneOnChannel, callback: (data: any) => void) {
4
+ const callbackRef = useRef(callback)
5
+ callbackRef.current = callback
6
+
7
+ useEffect(() => {
8
+ const handler = (data: any) => callbackRef.current(data)
9
+ window.rune.on(channel, handler)
10
+ return () => window.rune.off(channel, handler)
11
+ }, [channel])
12
+ }
13
+
14
+ export function useIPCSend() {
15
+ return useCallback((channel: RuneSendChannel, data?: unknown) => {
16
+ window.rune.send(channel, data)
17
+ }, [])
18
+ }
19
+
20
+ export function useIPCInvoke() {
21
+ return useCallback((channel: RuneInvokeChannel, data?: unknown) => {
22
+ return window.rune.invoke(channel, data)
23
+ }, [])
24
+ }
@@ -0,0 +1,83 @@
1
+ function escHtml(s: string): string {
2
+ return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
3
+ }
4
+
5
+ function inlineMd(s: string): string {
6
+ s = s.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
7
+ s = s.replace(/__(.+?)__/g, '<strong>$1</strong>')
8
+ s = s.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, '<em>$1</em>')
9
+ s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" title="$1">$1</a>')
10
+ return s
11
+ }
12
+
13
+ export function renderMarkdown(src: string): string {
14
+ const blocks: string[] = []
15
+ let md = src.replace(/```([\w-]*)\n([\s\S]*?)```/g, (_, lang, code) => {
16
+ blocks.push('<pre><code>' + escHtml(code) + '</code></pre>')
17
+ return '\x01' + (blocks.length - 1) + '\x01'
18
+ })
19
+ md = md.replace(/`([^`\n]+)`/g, (_, code) => {
20
+ blocks.push('<code>' + escHtml(code) + '</code>')
21
+ return '\x01' + (blocks.length - 1) + '\x01'
22
+ })
23
+
24
+ md = escHtml(md)
25
+
26
+ const lines = md.split('\n')
27
+ let out = '', inUl = false, inOl = false, inTable = false
28
+
29
+ for (let li = 0; li < lines.length; li++) {
30
+ const line = lines[li]
31
+ const isUl = /^[\-\*] /.test(line)
32
+ const isOl = /^\d+\. /.test(line)
33
+ const isTableRow = /^\|(.+)\|$/.test(line.trim())
34
+
35
+ if (inUl && !isUl) { out += '</ul>'; inUl = false }
36
+ if (inOl && !isOl) { out += '</ol>'; inOl = false }
37
+ if (inTable && !isTableRow) { out += '</tbody></table>'; inTable = false }
38
+
39
+ if (isTableRow) {
40
+ const cells = line.trim().slice(1, -1).split('|').map(c => c.trim())
41
+ if (cells.every(c => /^[-:]+$/.test(c))) continue
42
+ if (!inTable) {
43
+ inTable = true
44
+ out += '<table><thead><tr>' + cells.map(c => '<th>' + inlineMd(c) + '</th>').join('') + '</tr></thead><tbody>'
45
+ } else {
46
+ out += '<tr>' + cells.map(c => '<td>' + inlineMd(c) + '</td>').join('') + '</tr>'
47
+ }
48
+ continue
49
+ }
50
+
51
+ const hm = line.match(/^(#{1,4}) (.+)$/)
52
+ if (hm) {
53
+ const lv = Math.min(hm[1].length + 2, 6)
54
+ out += '<h' + lv + '>' + inlineMd(hm[2]) + '</h' + lv + '>'
55
+ continue
56
+ }
57
+ if (/^[-*_]{3,}$/.test(line.trim())) { out += '<hr>'; continue }
58
+ if (/^&gt; /.test(line)) {
59
+ out += '<blockquote>' + inlineMd(line.slice(5)) + '</blockquote>'
60
+ continue
61
+ }
62
+ if (isUl) {
63
+ if (!inUl) { out += '<ul>'; inUl = true }
64
+ out += '<li>' + inlineMd(line.replace(/^[\-\*] /, '')) + '</li>'
65
+ continue
66
+ }
67
+ if (isOl) {
68
+ if (!inOl) { out += '<ol>'; inOl = true }
69
+ out += '<li>' + inlineMd(line.replace(/^\d+\. /, '')) + '</li>'
70
+ continue
71
+ }
72
+ if (line.trim() === '') { out += '<br>'; continue }
73
+ out += '<p>' + inlineMd(line) + '</p>'
74
+ }
75
+ if (inUl) out += '</ul>'
76
+ if (inOl) out += '</ol>'
77
+ if (inTable) out += '</tbody></table>'
78
+
79
+ blocks.forEach((html, i) => {
80
+ out = out.replace('\x01' + i + '\x01', html)
81
+ })
82
+ return out
83
+ }
@@ -0,0 +1,6 @@
1
+ import { type ClassValue, clsx } from 'clsx'
2
+ import { twMerge } from 'tailwind-merge'
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }
@@ -0,0 +1,10 @@
1
+ import React from 'react'
2
+ import ReactDOM from 'react-dom/client'
3
+ import { App } from './App'
4
+ import './globals.css'
5
+
6
+ ReactDOM.createRoot(document.getElementById('root')!).render(
7
+ <React.StrictMode>
8
+ <App />
9
+ </React.StrictMode>
10
+ )
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "jsx": "react-jsx",
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "resolveJsonModule": true,
10
+ "skipLibCheck": true,
11
+ "paths": {
12
+ "@/*": ["./src/*"]
13
+ }
14
+ },
15
+ "include": ["src/**/*"]
16
+ }
@@ -0,0 +1,23 @@
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+ import tailwindcss from '@tailwindcss/vite'
4
+ import path from 'path'
5
+
6
+ export default defineConfig({
7
+ root: __dirname,
8
+ plugins: [react(), tailwindcss()],
9
+ base: './',
10
+ resolve: {
11
+ alias: {
12
+ '@': path.resolve(__dirname, 'src'),
13
+ },
14
+ },
15
+ define: {
16
+ 'process.env': '{}',
17
+ 'process.platform': JSON.stringify('darwin'),
18
+ },
19
+ build: {
20
+ outDir: path.resolve(__dirname, '..', 'dist', 'renderer'),
21
+ emptyOutDir: true,
22
+ },
23
+ })