pragma-so 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 (65) hide show
  1. package/cli/index.ts +882 -0
  2. package/index.ts +3 -0
  3. package/package.json +53 -0
  4. package/server/connectorBinaries.ts +103 -0
  5. package/server/connectorRegistry.ts +158 -0
  6. package/server/conversation/adapterRegistry.ts +53 -0
  7. package/server/conversation/adapters/claudeAdapter.ts +138 -0
  8. package/server/conversation/adapters/codexAdapter.ts +142 -0
  9. package/server/conversation/adapters.ts +224 -0
  10. package/server/conversation/executeRunner.ts +1191 -0
  11. package/server/conversation/gitWorkflow.ts +1037 -0
  12. package/server/conversation/models.ts +23 -0
  13. package/server/conversation/pragmaCli.ts +34 -0
  14. package/server/conversation/prompts.ts +335 -0
  15. package/server/conversation/store.ts +805 -0
  16. package/server/conversation/titleGenerator.ts +106 -0
  17. package/server/conversation/turnRunner.ts +365 -0
  18. package/server/conversation/types.ts +134 -0
  19. package/server/db.ts +837 -0
  20. package/server/http/middleware.ts +31 -0
  21. package/server/http/schemas.ts +430 -0
  22. package/server/http/validators.ts +38 -0
  23. package/server/index.ts +6560 -0
  24. package/server/process/runCommand.ts +142 -0
  25. package/server/stores/agentStore.ts +167 -0
  26. package/server/stores/connectorStore.ts +299 -0
  27. package/server/stores/humanStore.ts +28 -0
  28. package/server/stores/skillStore.ts +127 -0
  29. package/server/stores/taskStore.ts +371 -0
  30. package/shared/net.ts +24 -0
  31. package/tsconfig.json +14 -0
  32. package/ui/index.html +14 -0
  33. package/ui/public/favicon-32.png +0 -0
  34. package/ui/public/favicon.png +0 -0
  35. package/ui/src/App.jsx +1338 -0
  36. package/ui/src/api.js +954 -0
  37. package/ui/src/components/CodeView.jsx +319 -0
  38. package/ui/src/components/ConnectionsView.jsx +1004 -0
  39. package/ui/src/components/ContextView.jsx +315 -0
  40. package/ui/src/components/ConversationDrawer.jsx +963 -0
  41. package/ui/src/components/EmptyPane.jsx +20 -0
  42. package/ui/src/components/FeedView.jsx +773 -0
  43. package/ui/src/components/FilesView.jsx +257 -0
  44. package/ui/src/components/InlineChatView.jsx +158 -0
  45. package/ui/src/components/InputBar.jsx +476 -0
  46. package/ui/src/components/OnboardingModal.jsx +112 -0
  47. package/ui/src/components/OutputPanel.jsx +658 -0
  48. package/ui/src/components/PlanProposalPanel.jsx +177 -0
  49. package/ui/src/components/RightPanel.jsx +951 -0
  50. package/ui/src/components/SettingsView.jsx +186 -0
  51. package/ui/src/components/Sidebar.jsx +247 -0
  52. package/ui/src/components/TestingPane.jsx +198 -0
  53. package/ui/src/components/testing/ApiTesterPanel.jsx +187 -0
  54. package/ui/src/components/testing/LogViewerPanel.jsx +64 -0
  55. package/ui/src/components/testing/TerminalPanel.jsx +104 -0
  56. package/ui/src/components/testing/WebPreviewPanel.jsx +78 -0
  57. package/ui/src/hooks/useAgents.js +81 -0
  58. package/ui/src/hooks/useConversation.js +252 -0
  59. package/ui/src/hooks/useTasks.js +161 -0
  60. package/ui/src/hooks/useWorkspace.js +259 -0
  61. package/ui/src/lib/agentIcon.js +10 -0
  62. package/ui/src/lib/conversationUtils.js +575 -0
  63. package/ui/src/main.jsx +10 -0
  64. package/ui/src/styles.css +6899 -0
  65. package/ui/vite.config.mjs +6 -0
@@ -0,0 +1,315 @@
1
+ import { useEffect, useMemo, useRef, useState } from 'react'
2
+ import ReactMarkdown from 'react-markdown'
3
+ import remarkGfm from 'remark-gfm'
4
+ import { FilePlus2, FileText, Folder, FolderPlus, Pencil } from 'lucide-react'
5
+
6
+ function ensureMdName(name) {
7
+ const trimmed = name.trim()
8
+ if (!trimmed) return ''
9
+ return trimmed.toLowerCase().endsWith('.md') ? trimmed : `${trimmed}.md`
10
+ }
11
+
12
+ export function ContextView({
13
+ folders,
14
+ files,
15
+ loading,
16
+ error,
17
+ onSave,
18
+ onCreateFile,
19
+ onCreateFolder,
20
+ }) {
21
+ const [selectedPath, setSelectedPath] = useState('')
22
+ const [isEditing, setIsEditing] = useState(false)
23
+ const [draft, setDraft] = useState('')
24
+ const [saveLoading, setSaveLoading] = useState(false)
25
+ const [saveError, setSaveError] = useState('')
26
+ const [createLoading, setCreateLoading] = useState(false)
27
+ const [createError, setCreateError] = useState('')
28
+ const [createDraft, setCreateDraft] = useState(null)
29
+
30
+ const createInputRef = useRef(null)
31
+
32
+ const selected = useMemo(() => {
33
+ if (files.length === 0) return null
34
+ if (!selectedPath) return files[0]
35
+ return files.find((file) => file.path === selectedPath) ?? files[0]
36
+ }, [files, selectedPath])
37
+
38
+ const rootFiles = useMemo(() => files.filter((file) => !file.folder), [files])
39
+
40
+ useEffect(() => {
41
+ if (!selected) {
42
+ setIsEditing(false)
43
+ setDraft('')
44
+ setSaveError('')
45
+ return
46
+ }
47
+
48
+ if (!selectedPath) {
49
+ setSelectedPath(selected.path)
50
+ }
51
+
52
+ if (!isEditing) {
53
+ setDraft(selected.content ?? '')
54
+ }
55
+ }, [selected, selectedPath, isEditing])
56
+
57
+ useEffect(() => {
58
+ if (createDraft && createInputRef.current) {
59
+ createInputRef.current.focus()
60
+ createInputRef.current.select()
61
+ }
62
+ }, [createDraft?.kind, createDraft?.folder])
63
+
64
+ async function handleSave() {
65
+ if (!selected) return
66
+
67
+ setSaveLoading(true)
68
+ setSaveError('')
69
+ try {
70
+ await onSave(selected.path, draft)
71
+ setIsEditing(false)
72
+ } catch (err) {
73
+ setSaveError(err instanceof Error ? err.message : String(err))
74
+ } finally {
75
+ setSaveLoading(false)
76
+ }
77
+ }
78
+
79
+ function startCreateFile(folder = null) {
80
+ setCreateError('')
81
+ setCreateDraft({ kind: 'file', folder, value: '' })
82
+ }
83
+
84
+ function startCreateFolder() {
85
+ setCreateError('')
86
+ setCreateDraft({ kind: 'folder', folder: null, value: '' })
87
+ }
88
+
89
+ function cancelCreateDraft() {
90
+ setCreateDraft(null)
91
+ }
92
+
93
+ async function submitCreateDraft() {
94
+ if (!createDraft) return
95
+
96
+ setCreateLoading(true)
97
+ setCreateError('')
98
+ try {
99
+ if (createDraft.kind === 'folder') {
100
+ const folderName = createDraft.value.trim()
101
+ if (!folderName) {
102
+ setCreateError('Folder name is required.')
103
+ return
104
+ }
105
+ await onCreateFolder(folderName)
106
+ } else {
107
+ const normalizedName = ensureMdName(createDraft.value)
108
+ if (!normalizedName) {
109
+ setCreateError('File name is required.')
110
+ return
111
+ }
112
+
113
+ const folder = createDraft.folder || undefined
114
+ await onCreateFile(normalizedName, folder)
115
+ const path = folder ? `${folder}/${normalizedName}` : normalizedName
116
+ setSelectedPath(path)
117
+ }
118
+
119
+ setCreateDraft(null)
120
+ } catch (err) {
121
+ setCreateError(err instanceof Error ? err.message : String(err))
122
+ } finally {
123
+ setCreateLoading(false)
124
+ }
125
+ }
126
+
127
+ function renderDraftInput(isNested = false) {
128
+ if (!createDraft) return null
129
+
130
+ const icon = createDraft.kind === 'folder' ? <Folder size={13} /> : <FileText size={13} />
131
+ const placeholder = createDraft.kind === 'folder' ? 'folder name' : 'file name'
132
+
133
+ return (
134
+ <div className={`context-draft-row ${isNested ? 'context-item-nested' : ''}`}>
135
+ <span className="context-node-icon">{icon}</span>
136
+ <input
137
+ ref={createInputRef}
138
+ className="context-draft-input"
139
+ value={createDraft.value}
140
+ placeholder={placeholder}
141
+ onChange={(e) => {
142
+ setCreateDraft((prev) => (prev ? { ...prev, value: e.target.value } : prev))
143
+ }}
144
+ onBlur={cancelCreateDraft}
145
+ onKeyDown={(e) => {
146
+ if (e.key === 'Enter') {
147
+ e.preventDefault()
148
+ void submitCreateDraft()
149
+ }
150
+ if (e.key === 'Escape') {
151
+ e.preventDefault()
152
+ cancelCreateDraft()
153
+ }
154
+ }}
155
+ />
156
+ </div>
157
+ )
158
+ }
159
+
160
+ return (
161
+ <section className="context-view">
162
+ <aside className="context-sidebar">
163
+ <div className="context-sidebar-header">
164
+ <div className="context-section-title">Pages</div>
165
+ <div className="context-sidebar-actions">
166
+ <button
167
+ className="context-top-action"
168
+ onClick={() => startCreateFile(null)}
169
+ disabled={createLoading || loading}
170
+ title="New file"
171
+ >
172
+ <FilePlus2 size={14} />
173
+ </button>
174
+ <button
175
+ className="context-icon-action"
176
+ onClick={startCreateFolder}
177
+ disabled={createLoading || loading}
178
+ title="New folder"
179
+ >
180
+ <FolderPlus size={14} />
181
+ </button>
182
+ </div>
183
+ </div>
184
+ <div className="context-subtitle">Agents automatically add context here. You can also edit manually.</div>
185
+
186
+ {loading && <div className="muted">Loading...</div>}
187
+ {error && <div className="error">Error: {error}</div>}
188
+ {createError && <div className="error">Error: {createError}</div>}
189
+
190
+ {!loading && !error && files.length === 0 && (
191
+ <div className="muted">No context files found.</div>
192
+ )}
193
+
194
+ {!loading &&
195
+ !error &&
196
+ rootFiles.map((file) => (
197
+ <button
198
+ key={file.path}
199
+ className={`context-item ${selected?.path === file.path ? 'active' : ''}`}
200
+ onClick={() => {
201
+ setSelectedPath(file.path)
202
+ setIsEditing(false)
203
+ setSaveError('')
204
+ }}
205
+ >
206
+ <span className="context-node-icon">
207
+ <FileText size={13} />
208
+ </span>
209
+ {file.title}
210
+ </button>
211
+ ))}
212
+
213
+ {createDraft?.kind === 'file' && !createDraft.folder && renderDraftInput(false)}
214
+ {createDraft?.kind === 'folder' && renderDraftInput(false)}
215
+
216
+ {!loading &&
217
+ !error &&
218
+ folders.map((folder) => (
219
+ <div key={folder.name} className="context-folder-group">
220
+ <div className="context-folder-row">
221
+ <div className="context-folder-label">
222
+ <span className="context-node-icon">
223
+ <Folder size={13} />
224
+ </span>
225
+ <span>{folder.name}</span>
226
+ </div>
227
+ <button
228
+ className="context-folder-add-file"
229
+ title={`Add file to ${folder.name}`}
230
+ onMouseDown={(e) => e.preventDefault()}
231
+ onClick={() => startCreateFile(folder.name)}
232
+ >
233
+ <FilePlus2 size={12} />
234
+ </button>
235
+ </div>
236
+
237
+ {createDraft?.kind === 'file' && createDraft.folder === folder.name &&
238
+ renderDraftInput(true)}
239
+
240
+ {files
241
+ .filter((file) => file.folder === folder.name)
242
+ .map((file) => (
243
+ <button
244
+ key={file.path}
245
+ className={`context-item context-item-nested ${selected?.path === file.path ? 'active' : ''}`}
246
+ onClick={() => {
247
+ setSelectedPath(file.path)
248
+ setIsEditing(false)
249
+ setSaveError('')
250
+ }}
251
+ >
252
+ <span className="context-node-icon">
253
+ <FileText size={13} />
254
+ </span>
255
+ {file.title}
256
+ </button>
257
+ ))}
258
+ </div>
259
+ ))}
260
+ </aside>
261
+
262
+ <article className="context-content">
263
+ {selected ? (
264
+ <>
265
+ <div className="context-header-row">
266
+ <div className="context-file-name">{selected.filename}</div>
267
+ {!isEditing ? (
268
+ <button className="context-edit-btn" onClick={() => setIsEditing(true)}>
269
+ <Pencil size={14} /> Edit
270
+ </button>
271
+ ) : (
272
+ <div className="context-edit-actions">
273
+ <button
274
+ className="context-cancel-btn"
275
+ onClick={() => {
276
+ setDraft(selected.content ?? '')
277
+ setSaveError('')
278
+ setIsEditing(false)
279
+ }}
280
+ disabled={saveLoading}
281
+ >
282
+ Cancel
283
+ </button>
284
+ <button className="context-save-btn" onClick={handleSave} disabled={saveLoading}>
285
+ {saveLoading ? 'Saving...' : 'Save'}
286
+ </button>
287
+ </div>
288
+ )}
289
+ </div>
290
+
291
+ {saveError && <div className="error">Error: {saveError}</div>}
292
+
293
+ {!isEditing ? (
294
+ <div className="context-markdown">
295
+ <ReactMarkdown remarkPlugins={[remarkGfm]}>{selected.content}</ReactMarkdown>
296
+ </div>
297
+ ) : (
298
+ <textarea
299
+ className="context-editor"
300
+ value={draft}
301
+ onChange={(e) => setDraft(e.target.value)}
302
+ spellCheck={false}
303
+ />
304
+ )}
305
+ </>
306
+ ) : (
307
+ <>
308
+ <h1>Context</h1>
309
+ <div className="context-body">Select a page to view its content.</div>
310
+ </>
311
+ )}
312
+ </article>
313
+ </section>
314
+ )
315
+ }