@bibobonking/opensol 0.0.0-stage → 3.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.
Files changed (114) hide show
  1. package/.github/workflows/build.yml +25 -0
  2. package/README.md +74 -2
  3. package/dist/assets/index-BmN7pBji.css +1 -0
  4. package/dist/assets/index-DgNDjkOz.js +301 -0
  5. package/dist/favicon.svg +15 -0
  6. package/dist/index.html +55 -0
  7. package/dist/preview-host.html +33 -0
  8. package/index.html +54 -0
  9. package/package.json +43 -4
  10. package/postcss.config.js +6 -0
  11. package/public/favicon.svg +15 -0
  12. package/public/preview-host.html +33 -0
  13. package/shared/plugin-builder.test.mjs +43 -0
  14. package/shared/plugin-package.d.mts +34 -0
  15. package/shared/plugin-package.mjs +197 -0
  16. package/shared/plugin-package.test.mjs +94 -0
  17. package/src/App.tsx +152 -0
  18. package/src/agent/adapters/anthropic.ts +233 -0
  19. package/src/agent/adapters/gemini.ts +225 -0
  20. package/src/agent/adapters/openai-responses.ts +215 -0
  21. package/src/agent/adapters/openai.ts +200 -0
  22. package/src/agent/adapters/shared.ts +96 -0
  23. package/src/agent/approval.ts +20 -0
  24. package/src/agent/compact.ts +234 -0
  25. package/src/agent/index.ts +36 -0
  26. package/src/agent/model.ts +20 -0
  27. package/src/agent/prompt.ts +107 -0
  28. package/src/agent/queue.test.mjs +111 -0
  29. package/src/agent/queue.ts +84 -0
  30. package/src/agent/subagent.ts +109 -0
  31. package/src/agent/tools.ts +541 -0
  32. package/src/components/ChatView.tsx +403 -0
  33. package/src/components/CommandPalette.tsx +416 -0
  34. package/src/components/Composer.tsx +678 -0
  35. package/src/components/ContextBar.tsx +58 -0
  36. package/src/components/DeployModal.tsx +512 -0
  37. package/src/components/ErrorBoundary.tsx +75 -0
  38. package/src/components/HomeDashboard.tsx +189 -0
  39. package/src/components/Logo.tsx +41 -0
  40. package/src/components/Markdown.tsx +232 -0
  41. package/src/components/MessageItem.tsx +655 -0
  42. package/src/components/ModelPicker.tsx +487 -0
  43. package/src/components/PixleSol.tsx +243 -0
  44. package/src/components/PluginBuilder.tsx +285 -0
  45. package/src/components/PluginManager.tsx +341 -0
  46. package/src/components/PreviewPane.tsx +166 -0
  47. package/src/components/SettingsModal.tsx +2052 -0
  48. package/src/components/Sidebar.tsx +504 -0
  49. package/src/components/Splash.tsx +104 -0
  50. package/src/components/ToolCallCard.tsx +177 -0
  51. package/src/components/TopBar.tsx +182 -0
  52. package/src/components/TrajectoryView.tsx +178 -0
  53. package/src/components/WorkspacePanel.tsx +710 -0
  54. package/src/components/ui.tsx +311 -0
  55. package/src/core/kernel.ts +45 -0
  56. package/src/core/plugins/tools/fs.ts +219 -0
  57. package/src/core/plugins/tools/git.ts +83 -0
  58. package/src/core/plugins/tools/grep.ts +98 -0
  59. package/src/core/plugins/tools/shell.ts +88 -0
  60. package/src/core/plugins/tools/subagent.ts +36 -0
  61. package/src/core/plugins/tools/web.ts +97 -0
  62. package/src/core/services/agent-loop.ts +417 -0
  63. package/src/core/services/approval.ts +66 -0
  64. package/src/core/services/compaction.ts +45 -0
  65. package/src/core/services/llm.ts +32 -0
  66. package/src/core/services/sessions.ts +47 -0
  67. package/src/core/services/subagents.ts +15 -0
  68. package/src/core/services/system-prompt.ts +177 -0
  69. package/src/core/services/tools.ts +135 -0
  70. package/src/core/services/ui.ts +109 -0
  71. package/src/core/types.ts +139 -0
  72. package/src/data/providers.ts +125 -0
  73. package/src/data/skills.ts +68 -0
  74. package/src/index.css +776 -0
  75. package/src/lib/boot.ts +94 -0
  76. package/src/lib/context.ts +75 -0
  77. package/src/lib/conversation.test.mjs +64 -0
  78. package/src/lib/conversation.ts +59 -0
  79. package/src/lib/daemon.ts +475 -0
  80. package/src/lib/hooks.ts +33 -0
  81. package/src/lib/permissions.ts +60 -0
  82. package/src/lib/pixlesol.test.mjs +51 -0
  83. package/src/lib/pixlesol.ts +55 -0
  84. package/src/lib/plugin-folder.test.mjs +145 -0
  85. package/src/lib/plugin-folder.ts +96 -0
  86. package/src/lib/reasoning.ts +87 -0
  87. package/src/lib/sse.ts +75 -0
  88. package/src/lib/themes.test.mjs +72 -0
  89. package/src/lib/themes.ts +52 -0
  90. package/src/lib/usage.test.mjs +72 -0
  91. package/src/lib/usage.ts +155 -0
  92. package/src/lib/utils.ts +73 -0
  93. package/src/main.tsx +16 -0
  94. package/src/plugins/api.test.mjs +77 -0
  95. package/src/plugins/api.ts +113 -0
  96. package/src/plugins/bridge.d.mts +1 -0
  97. package/src/plugins/bridge.mjs +26 -0
  98. package/src/plugins/bridge.test.mjs +17 -0
  99. package/src/plugins/index.ts +52 -0
  100. package/src/plugins/preview.test.mjs +18 -0
  101. package/src/plugins/preview.ts +34 -0
  102. package/src/plugins/runtime.ts +194 -0
  103. package/src/plugins/types.ts +40 -0
  104. package/src/store/chats.ts +278 -0
  105. package/src/store/models.ts +120 -0
  106. package/src/store/settings.ts +445 -0
  107. package/src/store/ui.ts +132 -0
  108. package/src/store/workspace.ts +299 -0
  109. package/src/types.ts +337 -0
  110. package/src/vite-env.d.ts +1 -0
  111. package/tailwind.config.js +83 -0
  112. package/tsconfig.json +22 -0
  113. package/tsconfig.tsbuildinfo +1 -0
  114. package/vite.config.ts +19 -0
@@ -0,0 +1,416 @@
1
+ import { Fragment, useEffect, useMemo, useRef, useState } from 'react'
2
+ import { createPortal } from 'react-dom'
3
+ import {
4
+ ArrowDownToLine,
5
+ ArrowUpRight,
6
+ Cpu,
7
+ FileText,
8
+ FolderOpen,
9
+ Globe2,
10
+ Info,
11
+ MessageSquare,
12
+ Moon,
13
+ Palette,
14
+ Plus,
15
+ Rocket,
16
+ Search,
17
+ Sparkles,
18
+ Sun,
19
+ Wrench,
20
+ } from 'lucide-react'
21
+ import type { LucideIcon } from 'lucide-react'
22
+ import { useChats, exportChats } from '../store/chats'
23
+ import { useSettings } from '../store/settings'
24
+ import { useUi } from '../store/ui'
25
+ import { useWorkspace } from '../store/workspace'
26
+ import { cn, formatRelativeTime } from '../lib/utils'
27
+
28
+ interface PaletteCommand {
29
+ id: string
30
+ label: string
31
+ hint?: string
32
+ group: string
33
+ icon: LucideIcon
34
+ keywords?: string
35
+ action: () => void
36
+ }
37
+
38
+ export function CommandPalette() {
39
+ const open = useUi((state) => state.paletteOpen)
40
+ const setOpen = useUi((state) => state.setPaletteOpen)
41
+ const openSettings = useUi((state) => state.openSettings)
42
+ const setWorkspaceOpen = useUi((state) => state.setWorkspaceOpen)
43
+ const setDeployOpen = useUi((state) => state.setDeployOpen)
44
+ const focusComposer = useUi((state) => state.focusComposer)
45
+ const notify = useUi((state) => state.notify)
46
+ const sessions = useChats((state) => state.sessions)
47
+ const entries = useWorkspace((state) => state.entries)
48
+ const [query, setQuery] = useState('')
49
+ const [activeIndex, setActiveIndex] = useState(0)
50
+ const inputRef = useRef<HTMLInputElement>(null)
51
+ const restoreRef = useRef<HTMLElement | null>(null)
52
+
53
+ useEffect(() => {
54
+ if (!open) return
55
+ setQuery('')
56
+ setActiveIndex(0)
57
+ restoreRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null
58
+ const previous = document.body.style.overflow
59
+ document.body.style.overflow = 'hidden'
60
+ const timer = window.setTimeout(() => inputRef.current?.focus(), 30)
61
+ return () => {
62
+ window.clearTimeout(timer)
63
+ document.body.style.overflow = previous
64
+ restoreRef.current?.focus()
65
+ }
66
+ }, [open])
67
+
68
+ const commands = useMemo<PaletteCommand[]>(() => {
69
+ const themeIsDark = document.documentElement.classList.contains('dark')
70
+ const close = () => setOpen(false)
71
+ const actions: PaletteCommand[] = [
72
+ {
73
+ id: 'action-new-chat',
74
+ label: 'New chat',
75
+ hint: 'Start a clean conversation',
76
+ group: 'Actions',
77
+ icon: Plus,
78
+ action: () => {
79
+ useChats.getState().newChat()
80
+ focusComposer()
81
+ close()
82
+ },
83
+ },
84
+ {
85
+ id: 'action-workspace',
86
+ label: 'Toggle workspace panel',
87
+ hint: 'Browse and edit files in the open folder',
88
+ group: 'Actions',
89
+ icon: FolderOpen,
90
+ action: () => {
91
+ setWorkspaceOpen(!useUi.getState().workspaceOpen)
92
+ close()
93
+ },
94
+ },
95
+ {
96
+ id: 'action-deploy',
97
+ label: 'Deploy a page',
98
+ hint: 'Cloudflare Pages, Netlify or Vercel',
99
+ group: 'Actions',
100
+ icon: Rocket,
101
+ action: () => {
102
+ setDeployOpen(true)
103
+ close()
104
+ },
105
+ },
106
+ {
107
+ id: 'action-theme',
108
+ label: themeIsDark ? 'Switch to light theme' : 'Switch to dark theme',
109
+ group: 'Actions',
110
+ icon: themeIsDark ? Sun : Moon,
111
+ action: () => {
112
+ useSettings.getState().update({ theme: themeIsDark ? 'light' : 'dark' })
113
+ close()
114
+ },
115
+ },
116
+ {
117
+ id: 'action-models',
118
+ label: 'Model settings',
119
+ hint: 'Providers, encrypted keys and reasoning',
120
+ group: 'Settings',
121
+ icon: Cpu,
122
+ action: () => {
123
+ openSettings('models')
124
+ close()
125
+ },
126
+ },
127
+ {
128
+ id: 'action-tools',
129
+ label: 'Tool permissions',
130
+ hint: 'Approval and enablement',
131
+ group: 'Settings',
132
+ icon: Wrench,
133
+ action: () => {
134
+ openSettings('tools')
135
+ close()
136
+ },
137
+ },
138
+ {
139
+ id: 'action-search',
140
+ label: 'Search settings',
141
+ hint: 'Tavily, Brave, Serper, DuckDuckGo',
142
+ group: 'Settings',
143
+ icon: Globe2,
144
+ action: () => {
145
+ openSettings('search')
146
+ close()
147
+ },
148
+ },
149
+ {
150
+ id: 'action-skills',
151
+ label: 'Skills',
152
+ hint: 'Load and manage reusable instructions',
153
+ group: 'Settings',
154
+ icon: Sparkles,
155
+ action: () => {
156
+ openSettings('skills')
157
+ close()
158
+ },
159
+ },
160
+ {
161
+ id: 'action-deploy-settings',
162
+ label: 'Hosting credentials',
163
+ hint: 'Cloudflare, Netlify and Vercel tokens',
164
+ group: 'Settings',
165
+ icon: Rocket,
166
+ action: () => {
167
+ openSettings('deploy')
168
+ close()
169
+ },
170
+ },
171
+ {
172
+ id: 'action-appearance',
173
+ label: 'Appearance',
174
+ hint: 'Theme and token estimates',
175
+ group: 'Settings',
176
+ icon: Palette,
177
+ action: () => {
178
+ openSettings('appearance')
179
+ close()
180
+ },
181
+ },
182
+ {
183
+ id: 'action-about',
184
+ label: 'About OpenSol',
185
+ hint: 'Local storage and reset controls',
186
+ group: 'Settings',
187
+ icon: Info,
188
+ action: () => {
189
+ openSettings('about')
190
+ close()
191
+ },
192
+ },
193
+ {
194
+ id: 'action-export',
195
+ label: 'Export conversations',
196
+ hint: 'Download a JSON history file',
197
+ group: 'Actions',
198
+ icon: ArrowDownToLine,
199
+ action: () => {
200
+ exportChats()
201
+ notify('Conversation history exported.', 'success')
202
+ close()
203
+ },
204
+ },
205
+ ]
206
+
207
+ const chatCommands: PaletteCommand[] = [...sessions]
208
+ .sort((a, b) => b.updatedAt - a.updatedAt)
209
+ .map((session) => ({
210
+ id: `chat-${session.id}`,
211
+ label: session.title,
212
+ hint: `${session.messages.length} messages · ${formatRelativeTime(session.updatedAt)}`,
213
+ group: 'Conversations',
214
+ icon: MessageSquare,
215
+ keywords: session.messages
216
+ .filter((message) => message.role === 'user')
217
+ .map((message) => message.content)
218
+ .join(' ')
219
+ .slice(0, 1000),
220
+ action: () => {
221
+ useChats.getState().setActive(session.id)
222
+ close()
223
+ },
224
+ }))
225
+
226
+ const fileCommands: PaletteCommand[] = entries
227
+ .filter((entry) => entry.type === 'file')
228
+ .slice(0, 1500)
229
+ .map((entry) => ({
230
+ id: `file-${entry.path}`,
231
+ label: entry.name,
232
+ hint: entry.path,
233
+ group: 'Workspace files',
234
+ icon: FileText,
235
+ keywords: entry.path,
236
+ action: () => {
237
+ useWorkspace.getState().setActiveFile(entry.path)
238
+ setWorkspaceOpen(true)
239
+ close()
240
+ },
241
+ }))
242
+
243
+ return [...actions, ...chatCommands, ...fileCommands]
244
+ }, [sessions, entries, focusComposer, notify, openSettings, setOpen, setWorkspaceOpen, setDeployOpen])
245
+
246
+ const filtered = useMemo(() => {
247
+ const needle = query.trim().toLowerCase()
248
+ const limit = needle ? 40 : 18
249
+ if (!needle) {
250
+ const recentChats = new Set(
251
+ [...sessions]
252
+ .sort((a, b) => b.updatedAt - a.updatedAt)
253
+ .slice(0, 2)
254
+ .map((session) => `chat-${session.id}`),
255
+ )
256
+ return commands
257
+ .filter(
258
+ (command) => command.group !== 'Conversations' || recentChats.has(command.id),
259
+ )
260
+ .filter((command, index, all) => all.findIndex((item) => item.id === command.id) === index)
261
+ .slice(0, limit)
262
+ }
263
+ return commands
264
+ .filter((command) =>
265
+ `${command.label} ${command.hint ?? ''} ${command.keywords ?? ''} ${command.group}`
266
+ .toLowerCase()
267
+ .includes(needle),
268
+ )
269
+ .slice(0, limit)
270
+ }, [commands, query, sessions])
271
+
272
+ useEffect(() => {
273
+ setActiveIndex(0)
274
+ }, [query])
275
+
276
+ useEffect(() => {
277
+ if (!open) return
278
+ const node = document.querySelector<HTMLElement>(`[data-palette-index="${activeIndex}"]`)
279
+ node?.scrollIntoView({ block: 'nearest' })
280
+ }, [activeIndex, open])
281
+
282
+ useEffect(() => {
283
+ if (activeIndex > filtered.length - 1) setActiveIndex(Math.max(0, filtered.length - 1))
284
+ }, [filtered.length, activeIndex])
285
+
286
+ if (!open) return null
287
+
288
+ const run = (index: number) => {
289
+ const command = filtered[index]
290
+ if (!command) return
291
+ command.action()
292
+ }
293
+
294
+ return createPortal(
295
+ <div className="fixed inset-0 z-[90] flex items-start justify-center px-3 pt-[10vh] sm:pt-[14vh]">
296
+ <button
297
+ type="button"
298
+ aria-label="Close command palette"
299
+ tabIndex={-1}
300
+ className="absolute inset-0 cursor-default bg-ink/20 backdrop-blur-[2px] dark:bg-black/45"
301
+ onClick={() => setOpen(false)}
302
+ />
303
+ <div
304
+ role="dialog"
305
+ aria-modal="true"
306
+ aria-label="Search and commands"
307
+ className="modal-panel relative flex max-h-[min(72dvh,560px)] w-full max-w-[560px] flex-col overflow-hidden rounded-2xl border border-line surface-lift shadow-float"
308
+ >
309
+ <div className="flex h-12 shrink-0 items-center gap-2.5 border-b border-line px-3.5">
310
+ <Search size={15} className="shrink-0 text-faint" />
311
+ <input
312
+ ref={inputRef}
313
+ value={query}
314
+ onChange={(event) => setQuery(event.target.value)}
315
+ onKeyDown={(event) => {
316
+ if (event.key === 'ArrowDown') {
317
+ event.preventDefault()
318
+ if (filtered.length > 0) {
319
+ setActiveIndex((index) => Math.min(index + 1, filtered.length - 1))
320
+ }
321
+ } else if (event.key === 'ArrowUp') {
322
+ event.preventDefault()
323
+ setActiveIndex((index) => Math.max(index - 1, 0))
324
+ } else if (event.key === 'Enter') {
325
+ event.preventDefault()
326
+ run(activeIndex)
327
+ } else if (event.key === 'Escape') {
328
+ setOpen(false)
329
+ }
330
+ }}
331
+ placeholder="Search chats, files, settings, actions…"
332
+ className="h-full min-w-0 flex-1 bg-transparent text-[13px] text-ink outline-none placeholder:text-faint"
333
+ role="combobox"
334
+ aria-expanded="true"
335
+ aria-controls="palette-results"
336
+ aria-activedescendant={filtered[activeIndex] ? `palette-${filtered[activeIndex].id}` : undefined}
337
+ />
338
+ <kbd className="hidden rounded border border-line bg-raised px-1.5 py-0.5 text-[9px] text-faint sm:block">
339
+ Esc
340
+ </kbd>
341
+ </div>
342
+
343
+ <div id="palette-results" role="listbox" className="thin-scroll min-h-0 flex-1 overflow-y-auto p-1.5">
344
+ {filtered.length === 0 && (
345
+ <div className="px-4 py-10 text-center">
346
+ <p className="text-[12px] font-medium text-muted">No matches</p>
347
+ <p className="mt-1 text-[10.5px] text-faint">Try a chat title, a file name or “deploy”.</p>
348
+ </div>
349
+ )}
350
+ {filtered.map((command, index) => {
351
+ const Icon = command.icon
352
+ const showGroup = index === 0 || filtered[index - 1].group !== command.group
353
+ return (
354
+ <Fragment key={command.id}>
355
+ {showGroup && (
356
+ <p className="px-2 pb-1 pt-2 text-[9.5px] font-medium uppercase tracking-[0.08em] text-faint">
357
+ {command.group}
358
+ </p>
359
+ )}
360
+ <button
361
+ type="button"
362
+ id={`palette-${command.id}`}
363
+ data-palette-index={index}
364
+ role="option"
365
+ aria-selected={activeIndex === index}
366
+ onMouseMove={() => setActiveIndex(index)}
367
+ onClick={() => run(index)}
368
+ className={cn(
369
+ 'flex w-full items-center gap-2.5 rounded-lg px-2 py-2 text-left transition-colors',
370
+ activeIndex === index ? 'bg-raised' : 'hover:bg-raised/60',
371
+ )}
372
+ >
373
+ <span
374
+ className={cn(
375
+ 'flex h-6 w-6 shrink-0 items-center justify-center rounded-md border border-line bg-panel',
376
+ activeIndex === index ? 'text-accent' : 'text-muted',
377
+ )}
378
+ >
379
+ <Icon size={12} />
380
+ </span>
381
+ <span className="min-w-0 flex-1">
382
+ <span className="block truncate text-[11.5px] font-medium text-ink">{command.label}</span>
383
+ {command.hint && (
384
+ <span className="mt-0.5 block truncate text-[9.5px] text-faint">{command.hint}</span>
385
+ )}
386
+ </span>
387
+ <ArrowUpRight
388
+ size={11}
389
+ className={cn(
390
+ 'shrink-0 text-faint transition-opacity',
391
+ activeIndex === index ? 'opacity-100' : 'opacity-0',
392
+ )}
393
+ />
394
+ </button>
395
+ </Fragment>
396
+ )
397
+ })}
398
+ </div>
399
+
400
+ <div className="flex h-8 shrink-0 items-center justify-between border-t border-line bg-raised/30 px-3 text-[9px] text-faint">
401
+ <span className="flex items-center gap-2">
402
+ <span>
403
+ <kbd className="rounded border border-line bg-panel px-1">↑</kbd>{' '}
404
+ <kbd className="rounded border border-line bg-panel px-1">↓</kbd> navigate
405
+ </span>
406
+ <span>
407
+ <kbd className="rounded border border-line bg-panel px-1">↵</kbd> open
408
+ </span>
409
+ </span>
410
+ <span>OpenSol</span>
411
+ </div>
412
+ </div>
413
+ </div>,
414
+ document.body,
415
+ )
416
+ }