@antigenic-oss/paint 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 (158) hide show
  1. package/LICENSE +178 -0
  2. package/NOTICE +4 -0
  3. package/README.md +180 -0
  4. package/bin/paint.js +266 -0
  5. package/next-env.d.ts +6 -0
  6. package/next.config.ts +19 -0
  7. package/package.json +81 -0
  8. package/postcss.config.mjs +8 -0
  9. package/public/dev-editor-inspector.js +1872 -0
  10. package/src/app/api/claude/analyze/route.ts +319 -0
  11. package/src/app/api/claude/apply/route.ts +185 -0
  12. package/src/app/api/claude/pick-folder/route.ts +64 -0
  13. package/src/app/api/claude/scan/route.ts +221 -0
  14. package/src/app/api/claude/status/route.ts +55 -0
  15. package/src/app/api/project/scan/route.ts +634 -0
  16. package/src/app/api/project-scan/css-variables/route.ts +238 -0
  17. package/src/app/api/project-scan/route.ts +40 -0
  18. package/src/app/api/project-scan/tailwind-config/route.ts +172 -0
  19. package/src/app/api/proxy/[[...path]]/route.ts +2400 -0
  20. package/src/app/docs/DocsClient.tsx +322 -0
  21. package/src/app/docs/layout.tsx +7 -0
  22. package/src/app/docs/page.tsx +855 -0
  23. package/src/app/globals.css +176 -0
  24. package/src/app/layout.tsx +19 -0
  25. package/src/app/page.tsx +46 -0
  26. package/src/bridge/api-handlers.ts +885 -0
  27. package/src/bridge/proxy-handler.ts +329 -0
  28. package/src/bridge/server.ts +113 -0
  29. package/src/components/BreakpointTabs.tsx +72 -0
  30. package/src/components/ChangeSummaryModal.tsx +267 -0
  31. package/src/components/ConnectModal.tsx +994 -0
  32. package/src/components/Editor.tsx +90 -0
  33. package/src/components/PageSelector.tsx +208 -0
  34. package/src/components/PreviewFrame.tsx +299 -0
  35. package/src/components/ProjectFolderBanner.tsx +91 -0
  36. package/src/components/ResponsiveToolbar.tsx +222 -0
  37. package/src/components/TargetSelector.tsx +243 -0
  38. package/src/components/TopBar.tsx +315 -0
  39. package/src/components/common/CollapsibleSection.tsx +36 -0
  40. package/src/components/common/ColorPicker.tsx +920 -0
  41. package/src/components/common/EditablePre.tsx +136 -0
  42. package/src/components/common/ErrorBoundary.tsx +65 -0
  43. package/src/components/common/ResizablePanel.tsx +83 -0
  44. package/src/components/common/ScanAnimation.tsx +76 -0
  45. package/src/components/common/ToastContainer.tsx +97 -0
  46. package/src/components/common/UnitInput.tsx +77 -0
  47. package/src/components/common/VariableColorPicker.tsx +622 -0
  48. package/src/components/left-panel/AddElementPanel.tsx +237 -0
  49. package/src/components/left-panel/ComponentsPanel.tsx +609 -0
  50. package/src/components/left-panel/IconSidebar.tsx +99 -0
  51. package/src/components/left-panel/LayerNode.tsx +874 -0
  52. package/src/components/left-panel/LayerSearch.tsx +23 -0
  53. package/src/components/left-panel/LayersPanel.tsx +52 -0
  54. package/src/components/left-panel/LeftPanel.tsx +122 -0
  55. package/src/components/left-panel/PagesPanel.tsx +114 -0
  56. package/src/components/left-panel/icons.tsx +162 -0
  57. package/src/components/left-panel/terminal/ScanOverlay.tsx +66 -0
  58. package/src/components/left-panel/terminal/TerminalPanel.tsx +217 -0
  59. package/src/components/right-panel/ElementLogBox.tsx +248 -0
  60. package/src/components/right-panel/PanelTabs.tsx +83 -0
  61. package/src/components/right-panel/RightPanel.tsx +41 -0
  62. package/src/components/right-panel/changes/AiScanResultPanel.tsx +285 -0
  63. package/src/components/right-panel/changes/ChangeEntry.tsx +59 -0
  64. package/src/components/right-panel/changes/ChangelogActions.tsx +105 -0
  65. package/src/components/right-panel/changes/ChangesPanel.tsx +1474 -0
  66. package/src/components/right-panel/claude/ApplyConfirmModal.tsx +376 -0
  67. package/src/components/right-panel/claude/ClaudeErrorState.tsx +125 -0
  68. package/src/components/right-panel/claude/ClaudeIntegrationPanel.tsx +482 -0
  69. package/src/components/right-panel/claude/ClaudeProgressIndicator.tsx +76 -0
  70. package/src/components/right-panel/claude/DiffCard.tsx +130 -0
  71. package/src/components/right-panel/claude/DiffViewer.tsx +54 -0
  72. package/src/components/right-panel/claude/ProjectRootSelector.tsx +275 -0
  73. package/src/components/right-panel/claude/ResultsSummary.tsx +119 -0
  74. package/src/components/right-panel/claude/SetupFlow.tsx +315 -0
  75. package/src/components/right-panel/console/ConsolePanel.tsx +209 -0
  76. package/src/components/right-panel/design/AppearanceSection.tsx +703 -0
  77. package/src/components/right-panel/design/BackgroundSection.tsx +516 -0
  78. package/src/components/right-panel/design/BorderSection.tsx +161 -0
  79. package/src/components/right-panel/design/CSSRawView.tsx +412 -0
  80. package/src/components/right-panel/design/DesignCSSTabToggle.tsx +51 -0
  81. package/src/components/right-panel/design/DesignPanel.tsx +275 -0
  82. package/src/components/right-panel/design/ElementBreadcrumb.tsx +51 -0
  83. package/src/components/right-panel/design/GradientEditor.tsx +726 -0
  84. package/src/components/right-panel/design/LayoutSection.tsx +1948 -0
  85. package/src/components/right-panel/design/PositionSection.tsx +865 -0
  86. package/src/components/right-panel/design/PropertiesSection.tsx +86 -0
  87. package/src/components/right-panel/design/SVGSection.tsx +361 -0
  88. package/src/components/right-panel/design/ShadowBlurSection.tsx +227 -0
  89. package/src/components/right-panel/design/SizeSection.tsx +183 -0
  90. package/src/components/right-panel/design/TextSection.tsx +719 -0
  91. package/src/components/right-panel/design/icons.tsx +948 -0
  92. package/src/components/right-panel/design/inputs/BoxModelPreview.tsx +467 -0
  93. package/src/components/right-panel/design/inputs/ColorInput.tsx +43 -0
  94. package/src/components/right-panel/design/inputs/CompactInput.tsx +333 -0
  95. package/src/components/right-panel/design/inputs/DraggableLabel.tsx +118 -0
  96. package/src/components/right-panel/design/inputs/IconToggleGroup.tsx +54 -0
  97. package/src/components/right-panel/design/inputs/LinkedInputPair.tsx +174 -0
  98. package/src/components/right-panel/design/inputs/SectionHeader.tsx +79 -0
  99. package/src/components/right-panel/variables/VariablesPanel.tsx +388 -0
  100. package/src/hooks/useBridge.ts +95 -0
  101. package/src/hooks/useChangeTracker.ts +563 -0
  102. package/src/hooks/useClaudeAPI.ts +118 -0
  103. package/src/hooks/useDOMTree.ts +25 -0
  104. package/src/hooks/useKeyboardShortcuts.ts +76 -0
  105. package/src/hooks/usePostMessage.ts +589 -0
  106. package/src/hooks/useProjectScan.ts +204 -0
  107. package/src/hooks/useResizable.ts +20 -0
  108. package/src/hooks/useSelectedElement.ts +51 -0
  109. package/src/hooks/useTargetUrl.ts +81 -0
  110. package/src/inspector/DOMTraverser.ts +71 -0
  111. package/src/inspector/ElementSelector.ts +23 -0
  112. package/src/inspector/HoverHighlighter.ts +54 -0
  113. package/src/inspector/SelectionHighlighter.ts +27 -0
  114. package/src/inspector/StyleExtractor.ts +19 -0
  115. package/src/inspector/inspector.ts +17 -0
  116. package/src/inspector/messaging.ts +30 -0
  117. package/src/lib/apiBase.ts +15 -0
  118. package/src/lib/classifyElement.ts +430 -0
  119. package/src/lib/claude-bin.ts +197 -0
  120. package/src/lib/claude-stream.ts +158 -0
  121. package/src/lib/clientProjectScanner.ts +344 -0
  122. package/src/lib/componentMatcher.ts +156 -0
  123. package/src/lib/constants.ts +573 -0
  124. package/src/lib/cssVariableUtils.ts +409 -0
  125. package/src/lib/diffParser.ts +206 -0
  126. package/src/lib/folderPicker.ts +84 -0
  127. package/src/lib/gradientParser.ts +160 -0
  128. package/src/lib/projectScanner.ts +355 -0
  129. package/src/lib/promptBuilder.ts +402 -0
  130. package/src/lib/shadowParser.ts +124 -0
  131. package/src/lib/tailwindClassParser.ts +248 -0
  132. package/src/lib/textShadowUtils.ts +106 -0
  133. package/src/lib/utils.ts +299 -0
  134. package/src/lib/validatePath.ts +40 -0
  135. package/src/proxy.ts +92 -0
  136. package/src/server/terminal-server.ts +104 -0
  137. package/src/store/changeSlice.ts +288 -0
  138. package/src/store/claudeSlice.ts +222 -0
  139. package/src/store/componentSlice.ts +90 -0
  140. package/src/store/consoleSlice.ts +51 -0
  141. package/src/store/cssVariableSlice.ts +94 -0
  142. package/src/store/elementSlice.ts +78 -0
  143. package/src/store/index.ts +35 -0
  144. package/src/store/terminalSlice.ts +30 -0
  145. package/src/store/treeSlice.ts +69 -0
  146. package/src/store/uiSlice.ts +327 -0
  147. package/src/types/changelog.ts +49 -0
  148. package/src/types/claude.ts +131 -0
  149. package/src/types/component.ts +49 -0
  150. package/src/types/cssVariables.ts +18 -0
  151. package/src/types/element.ts +21 -0
  152. package/src/types/file-system-access.d.ts +27 -0
  153. package/src/types/gradient.ts +12 -0
  154. package/src/types/messages.ts +392 -0
  155. package/src/types/shadow.ts +8 -0
  156. package/src/types/tree.ts +9 -0
  157. package/tsconfig.json +42 -0
  158. package/tsconfig.server.json +12 -0
@@ -0,0 +1,221 @@
1
+ import { NextResponse } from 'next/server'
2
+ import path from 'node:path'
3
+ import { stripControlChars } from '@/lib/utils'
4
+ import {
5
+ spawnClaude,
6
+ spawnClaudeStreaming,
7
+ isAuthError,
8
+ } from '@/lib/claude-bin'
9
+ import { validateProjectRoot } from '@/lib/validatePath'
10
+ import { buildScanPrompt } from '@/lib/promptBuilder'
11
+ import type {
12
+ ClaudeScanRequest,
13
+ ClaudeScanResponse,
14
+ ScanGroup,
15
+ ProjectScanResult,
16
+ } from '@/types/claude'
17
+
18
+ const MAX_CHANGELOG_BYTES = 50 * 1024 // 50KB
19
+ const TIMEOUT_MS = 300_000 // 5 minutes — scan reads project files + analyzes
20
+
21
+ /**
22
+ * Parse Claude's output to extract the structured scan result.
23
+ * Falls back to raw output as the smartPrompt if JSON parsing fails.
24
+ */
25
+ function parseScanOutput(stdout: string): ClaudeScanResponse {
26
+ const jsonMatch = stdout.match(
27
+ /--- SCAN RESULT JSON ---\s*([\s\S]*?)\s*--- END SCAN RESULT ---/,
28
+ )
29
+
30
+ if (jsonMatch) {
31
+ try {
32
+ const parsed = JSON.parse(jsonMatch[1])
33
+ return {
34
+ smartPrompt:
35
+ typeof parsed.smartPrompt === 'string'
36
+ ? parsed.smartPrompt
37
+ : stdout.trim(),
38
+ intent:
39
+ typeof parsed.intent === 'string'
40
+ ? parsed.intent
41
+ : 'Visual styling updates',
42
+ groups: Array.isArray(parsed.groups)
43
+ ? (parsed.groups as ScanGroup[])
44
+ : [],
45
+ warnings: Array.isArray(parsed.warnings)
46
+ ? (parsed.warnings as string[])
47
+ : [],
48
+ }
49
+ } catch {
50
+ // JSON parse failed — fall through to raw fallback
51
+ }
52
+ }
53
+
54
+ // Fallback: use entire output as the prompt
55
+ return {
56
+ smartPrompt: stdout.trim(),
57
+ intent: 'Visual styling updates',
58
+ groups: [],
59
+ warnings: [],
60
+ }
61
+ }
62
+
63
+ /** Format an SSE event. */
64
+ function sseEvent(event: string, data: Record<string, unknown>): string {
65
+ return `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`
66
+ }
67
+
68
+ /**
69
+ * Validate request body and return parsed inputs or an error response.
70
+ */
71
+ function parseAndValidateScan(
72
+ body: ClaudeScanRequest & { projectScan?: ProjectScanResult },
73
+ ): { error: NextResponse } | { prompt: string; resolvedRoot: string } {
74
+ const { changelog, projectRoot, projectScan } = body
75
+
76
+ if (!changelog || typeof changelog !== 'string' || changelog.length === 0) {
77
+ return {
78
+ error: NextResponse.json(
79
+ { error: 'changelog is required and must be a non-empty string' },
80
+ { status: 400 },
81
+ ),
82
+ }
83
+ }
84
+
85
+ if (!projectRoot || typeof projectRoot !== 'string') {
86
+ return {
87
+ error: NextResponse.json(
88
+ { error: 'projectRoot is required and must be a string' },
89
+ { status: 400 },
90
+ ),
91
+ }
92
+ }
93
+
94
+ const rootError = validateProjectRoot(projectRoot)
95
+ if (rootError) {
96
+ return { error: NextResponse.json({ error: rootError }, { status: 400 }) }
97
+ }
98
+
99
+ const resolvedRoot = path.resolve(projectRoot)
100
+ const sanitizedChangelog = stripControlChars(changelog).slice(
101
+ 0,
102
+ MAX_CHANGELOG_BYTES,
103
+ )
104
+ const prompt = buildScanPrompt(sanitizedChangelog, resolvedRoot, projectScan)
105
+
106
+ return { prompt, resolvedRoot }
107
+ }
108
+
109
+ export async function POST(request: Request): Promise<Response> {
110
+ let body: ClaudeScanRequest & { projectScan?: ProjectScanResult }
111
+ try {
112
+ body = await request.json()
113
+ } catch {
114
+ return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })
115
+ }
116
+
117
+ const validated = parseAndValidateScan(body)
118
+ if ('error' in validated) return validated.error
119
+ const { prompt, resolvedRoot } = validated
120
+
121
+ const wantsStream = request.headers
122
+ .get('accept')
123
+ ?.includes('text/event-stream')
124
+
125
+ // ── SSE streaming path ──
126
+ if (wantsStream) {
127
+ const encoder = new TextEncoder()
128
+ const stream = new ReadableStream({
129
+ start(controller) {
130
+ const enqueue = (event: string, data: Record<string, unknown>) => {
131
+ try {
132
+ controller.enqueue(encoder.encode(sseEvent(event, data)))
133
+ } catch {
134
+ /* closed */
135
+ }
136
+ }
137
+
138
+ spawnClaudeStreaming(
139
+ ['--print', '--allowedTools', 'Read', '-p', prompt],
140
+ {
141
+ cwd: resolvedRoot,
142
+ timeout: TIMEOUT_MS,
143
+ onStderr: (line) => enqueue('stderr', { line }),
144
+ },
145
+ )
146
+ .then((result) => {
147
+ if (result.exitCode !== 0) {
148
+ enqueue('error', {
149
+ code: 'CLI_ERROR',
150
+ message: 'Claude CLI exited with an error',
151
+ })
152
+ } else {
153
+ const response = parseScanOutput(result.stdout)
154
+ enqueue('result', response as unknown as Record<string, unknown>)
155
+ }
156
+ })
157
+ .catch((err) => {
158
+ const message = err instanceof Error ? err.message : 'Unknown error'
159
+ const code = message === 'TIMEOUT' ? 'TIMEOUT' : 'SPAWN_ERROR'
160
+ enqueue('error', { code, message })
161
+ })
162
+ .finally(() => {
163
+ enqueue('done', {})
164
+ controller.close()
165
+ })
166
+ },
167
+ })
168
+
169
+ return new Response(stream, {
170
+ headers: {
171
+ 'Content-Type': 'text/event-stream',
172
+ 'Cache-Control': 'no-cache',
173
+ Connection: 'keep-alive',
174
+ },
175
+ })
176
+ }
177
+
178
+ // ── JSON fallback path (backward compatible) ──
179
+ try {
180
+ const result = await spawnClaude(
181
+ ['--print', '--allowedTools', 'Read', '-p', prompt],
182
+ { cwd: resolvedRoot, timeout: TIMEOUT_MS },
183
+ )
184
+
185
+ if (result.exitCode !== 0) {
186
+ const stderr = result.stderr.trim()
187
+ if (isAuthError(stderr)) {
188
+ return NextResponse.json(
189
+ {
190
+ error:
191
+ 'Claude CLI is not authenticated. Run `claude login` in your terminal.',
192
+ authRequired: true,
193
+ },
194
+ { status: 401 },
195
+ )
196
+ }
197
+ return NextResponse.json(
198
+ {
199
+ error: 'Claude CLI exited with an error',
200
+ details: stderr || 'Unknown CLI error',
201
+ },
202
+ { status: 500 },
203
+ )
204
+ }
205
+
206
+ const response = parseScanOutput(result.stdout)
207
+ return NextResponse.json(response)
208
+ } catch (error) {
209
+ const message = error instanceof Error ? error.message : 'Unknown error'
210
+ if (message === 'TIMEOUT') {
211
+ return NextResponse.json(
212
+ { error: 'Claude CLI timed out after 5 minutes' },
213
+ { status: 504 },
214
+ )
215
+ }
216
+ return NextResponse.json(
217
+ { error: 'Failed to run Claude CLI', details: message },
218
+ { status: 500 },
219
+ )
220
+ }
221
+ }
@@ -0,0 +1,55 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { execFile } from 'node:child_process'
3
+ import { promisify } from 'node:util'
4
+ import path from 'node:path'
5
+ import { getClaudeBin } from '@/lib/claude-bin'
6
+ import { validateProjectRoot } from '@/lib/validatePath'
7
+ import type { ClaudeStatusResponse } from '@/types/claude'
8
+
9
+ const execFileAsync = promisify(execFile)
10
+
11
+ export async function GET(): Promise<NextResponse<ClaudeStatusResponse>> {
12
+ try {
13
+ const claudeBin = getClaudeBin()
14
+ const { stdout } = await execFileAsync(claudeBin, ['--version'], {
15
+ timeout: 10_000,
16
+ })
17
+
18
+ return NextResponse.json({
19
+ available: true,
20
+ version: stdout.trim(),
21
+ })
22
+ } catch (err) {
23
+ const msg = err instanceof Error ? err.message : String(err)
24
+ return NextResponse.json({
25
+ available: false,
26
+ error: `claude CLI not found: ${msg.slice(0, 200)}`,
27
+ })
28
+ }
29
+ }
30
+
31
+ /** Validate that projectRoot is absolute, exists, and is under HOME. */
32
+ export async function POST(request: Request): Promise<NextResponse> {
33
+ let body: { projectRoot?: string }
34
+ try {
35
+ body = await request.json()
36
+ } catch {
37
+ return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })
38
+ }
39
+
40
+ const { projectRoot } = body
41
+ if (!projectRoot || typeof projectRoot !== 'string') {
42
+ return NextResponse.json(
43
+ { error: 'projectRoot is required' },
44
+ { status: 400 },
45
+ )
46
+ }
47
+
48
+ const rootError = validateProjectRoot(projectRoot)
49
+ if (rootError) {
50
+ return NextResponse.json({ error: rootError }, { status: 400 })
51
+ }
52
+
53
+ const resolved = path.resolve(projectRoot)
54
+ return NextResponse.json({ valid: true, resolved })
55
+ }