ugly-app 0.1.221 → 0.1.222

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 (84) hide show
  1. package/.superpowers/brainstorm/96907-1775344576/content/panel-layout.html +159 -0
  2. package/.superpowers/brainstorm/96907-1775344576/content/updated-architecture.html +116 -0
  3. package/.superpowers/brainstorm/96907-1775344576/state/server-stopped +1 -0
  4. package/README.md +8 -12
  5. package/dist/cli/bootstrapMigration.d.ts +18 -0
  6. package/dist/cli/bootstrapMigration.d.ts.map +1 -0
  7. package/dist/cli/bootstrapMigration.js +44 -0
  8. package/dist/cli/bootstrapMigration.js.map +1 -0
  9. package/dist/cli/cloudCleanup.d.ts +4 -0
  10. package/dist/cli/cloudCleanup.d.ts.map +1 -0
  11. package/dist/cli/cloudCleanup.js +62 -0
  12. package/dist/cli/cloudCleanup.js.map +1 -0
  13. package/dist/cli/dev.d.ts +1 -0
  14. package/dist/cli/dev.d.ts.map +1 -1
  15. package/dist/cli/dev.js +39 -31
  16. package/dist/cli/dev.js.map +1 -1
  17. package/dist/cli/index.js +17 -28
  18. package/dist/cli/index.js.map +1 -1
  19. package/dist/cli/initDb.d.ts.map +1 -1
  20. package/dist/cli/initDb.js +1 -0
  21. package/dist/cli/initDb.js.map +1 -1
  22. package/dist/cli/scaffold.d.ts.map +1 -1
  23. package/dist/cli/scaffold.js +18 -2
  24. package/dist/cli/scaffold.js.map +1 -1
  25. package/dist/cli/schemaGen.d.ts +3 -1
  26. package/dist/cli/schemaGen.d.ts.map +1 -1
  27. package/dist/cli/schemaGen.js +55 -3
  28. package/dist/cli/schemaGen.js.map +1 -1
  29. package/dist/cli/sidecar.d.ts +21 -0
  30. package/dist/cli/sidecar.d.ts.map +1 -0
  31. package/dist/cli/sidecar.js +286 -0
  32. package/dist/cli/sidecar.js.map +1 -0
  33. package/dist/cli/sidecarHealth.d.ts +13 -0
  34. package/dist/cli/sidecarHealth.d.ts.map +1 -0
  35. package/dist/cli/sidecarHealth.js +42 -0
  36. package/dist/cli/sidecarHealth.js.map +1 -0
  37. package/dist/cli/sidecarProtocol.d.ts +41 -0
  38. package/dist/cli/sidecarProtocol.d.ts.map +1 -0
  39. package/dist/cli/sidecarProtocol.js +61 -0
  40. package/dist/cli/sidecarProtocol.js.map +1 -0
  41. package/dist/cli/version.d.ts +1 -1
  42. package/dist/cli/version.js +1 -1
  43. package/dist/server/SchemaCheck.d.ts.map +1 -1
  44. package/dist/server/SchemaCheck.js +2 -2
  45. package/dist/server/SchemaCheck.js.map +1 -1
  46. package/dist/server/index.d.ts +1 -1
  47. package/dist/server/index.d.ts.map +1 -1
  48. package/dist/server/index.js +1 -1
  49. package/dist/server/index.js.map +1 -1
  50. package/dist/shared/DB.d.ts +18 -32
  51. package/dist/shared/DB.d.ts.map +1 -1
  52. package/dist/shared/DB.js +6 -3
  53. package/dist/shared/DB.js.map +1 -1
  54. package/dist/shared/SchemaDiff.d.ts +1 -1
  55. package/dist/shared/SchemaDiff.d.ts.map +1 -1
  56. package/dist/shared/SchemaDiff.js +42 -1
  57. package/dist/shared/SchemaDiff.js.map +1 -1
  58. package/dist/shared/SchemaSerializer.d.ts +10 -1
  59. package/dist/shared/SchemaSerializer.d.ts.map +1 -1
  60. package/dist/shared/SchemaSerializer.js +6 -2
  61. package/dist/shared/SchemaSerializer.js.map +1 -1
  62. package/package.json +2 -1
  63. package/shared/schema-snapshot.json +1 -0
  64. package/src/cli/bootstrapMigration.ts +66 -0
  65. package/src/cli/cloudCleanup.ts +73 -0
  66. package/src/cli/dev.ts +39 -34
  67. package/src/cli/index.ts +18 -31
  68. package/src/cli/scaffold.ts +21 -2
  69. package/src/cli/schemaGen.ts +76 -3
  70. package/src/cli/sidecar.ts +341 -0
  71. package/src/cli/sidecarHealth.ts +58 -0
  72. package/src/cli/sidecarProtocol.ts +68 -0
  73. package/src/cli/version.ts +1 -1
  74. package/src/server/SchemaCheck.ts +9 -3
  75. package/src/server/index.ts +0 -1
  76. package/src/shared/DB.ts +12 -52
  77. package/src/shared/SchemaDiff.ts +51 -2
  78. package/src/shared/SchemaSerializer.ts +10 -2
  79. package/templates/.claude/skills/bot-swarm/SKILL.md +1 -1
  80. package/templates/CLAUDE.md +1 -2
  81. package/templates/package.json +0 -1
  82. package/templates/shared/collections.ts +2 -13
  83. package/.superpowers/brainstorm/96907-1775344576/state/server-info +0 -1
  84. package/src/cli/initDb.ts +0 -125
@@ -0,0 +1,159 @@
1
+ <h2>Panel Architecture</h2>
2
+ <p class="subtitle">Two types of panels — ugly.bot webviews vs local tools</p>
3
+
4
+ <div class="section">
5
+ <h3>IDE Layout</h3>
6
+ <div class="mockup">
7
+ <div class="mockup-header">ugly-studio — Default Layout</div>
8
+ <div class="mockup-body" style="display: flex; flex-direction: column; height: 500px; gap: 2px; background: #1a1a2e; padding: 0;">
9
+
10
+ <!-- Top bar -->
11
+ <div style="display: flex; align-items: center; background: #16162a; padding: 8px 16px; gap: 12px; flex-shrink: 0;">
12
+ <span style="font-weight: 600; color: #a78bfa;">ugly-studio</span>
13
+ <span style="color: #666; font-size: 12px;">my-project</span>
14
+ <span style="margin-left: auto; font-size: 11px; background: #22c55e33; color: #22c55e; padding: 2px 8px; border-radius: 4px;">● Connected to ugly.bot</span>
15
+ </div>
16
+
17
+ <!-- Tab bar -->
18
+ <div style="display: flex; background: #16162a; padding: 0 8px; gap: 2px; flex-shrink: 0;">
19
+ <div style="padding: 8px 16px; font-size: 12px; background: #1e1e3a; color: #a78bfa; border-radius: 6px 6px 0 0;">📋 Plans</div>
20
+ <div style="padding: 8px 16px; font-size: 12px; color: #888; border-radius: 6px 6px 0 0;">📝 Code</div>
21
+ <div style="padding: 8px 16px; font-size: 12px; color: #888; border-radius: 6px 6px 0 0;">📊 Feedback</div>
22
+ <div style="padding: 8px 16px; font-size: 12px; color: #888; border-radius: 6px 6px 0 0;">🔴 Errors</div>
23
+ <div style="padding: 8px 16px; font-size: 12px; color: #888; border-radius: 6px 6px 0 0;">⚡ Perf</div>
24
+ <div style="padding: 8px 16px; font-size: 12px; color: #888; border-radius: 6px 6px 0 0;">📈 Events</div>
25
+ <div style="padding: 8px 16px; font-size: 12px; color: #888; border-radius: 6px 6px 0 0;">🧪 Experiments</div>
26
+ <div style="padding: 8px 16px; font-size: 12px; color: #888; border-radius: 6px 6px 0 0;">👥 Users</div>
27
+ <div style="padding: 8px 16px; font-size: 12px; color: #888; border-radius: 6px 6px 0 0;">🗄️ DB</div>
28
+ <div style="padding: 8px 16px; font-size: 12px; color: #888; border-radius: 6px 6px 0 0;">🧪 Tests</div>
29
+ <div style="padding: 8px 16px; font-size: 12px; color: #888; border-radius: 6px 6px 0 0;">📱 Preview</div>
30
+ </div>
31
+
32
+ <!-- Main content area -->
33
+ <div style="display: flex; flex: 1; gap: 2px; min-height: 0;">
34
+
35
+ <!-- Left sidebar: File tree -->
36
+ <div style="width: 200px; background: #1e1e3a; padding: 12px; flex-shrink: 0; overflow: hidden;">
37
+ <div style="font-size: 11px; color: #666; text-transform: uppercase; margin-bottom: 8px; letter-spacing: 0.5px;">Files</div>
38
+ <div style="font-size: 12px; color: #ccc; margin-bottom: 4px;">📁 client/</div>
39
+ <div style="font-size: 12px; color: #888; margin-bottom: 4px; padding-left: 16px;">📁 pages/</div>
40
+ <div style="font-size: 12px; color: #888; margin-bottom: 4px; padding-left: 32px;">Home.tsx</div>
41
+ <div style="font-size: 12px; color: #888; margin-bottom: 4px; padding-left: 32px;">Login.tsx</div>
42
+ <div style="font-size: 12px; color: #ccc; margin-bottom: 4px;">📁 server/</div>
43
+ <div style="font-size: 12px; color: #888; margin-bottom: 4px; padding-left: 16px;">index.ts</div>
44
+ <div style="font-size: 12px; color: #ccc; margin-bottom: 4px;">📁 shared/</div>
45
+ <div style="font-size: 12px; color: #888; margin-bottom: 4px; padding-left: 16px;">api.ts</div>
46
+ <div style="font-size: 12px; color: #888; margin-bottom: 4px; padding-left: 16px;">collections.ts</div>
47
+ <div style="font-size: 12px; color: #888; margin-bottom: 4px; padding-left: 16px;">pages.ts</div>
48
+ <div style="font-size: 12px; color: #ccc; margin-bottom: 4px;">📁 tests/</div>
49
+ <div style="font-size: 12px; color: #888; margin-bottom: 4px;">.env</div>
50
+ <div style="font-size: 12px; color: #888; margin-bottom: 4px;">package.json</div>
51
+ </div>
52
+
53
+ <!-- Center: Plan editor (active tab) -->
54
+ <div style="flex: 1; display: flex; flex-direction: column; background: #1e1e3a; min-width: 0;">
55
+ <!-- Plan header -->
56
+ <div style="padding: 12px 16px; border-bottom: 1px solid #2a2a4a; display: flex; align-items: center; gap: 8px; flex-shrink: 0;">
57
+ <span style="font-size: 14px; font-weight: 600; color: #e2e8f0;">Auth System</span>
58
+ <span style="font-size: 11px; background: #f59e0b33; color: #f59e0b; padding: 2px 8px; border-radius: 4px;">Implementation</span>
59
+ <span style="margin-left: auto; font-size: 11px; color: #666;">
60
+ <span style="color: #22c55e;">●</span> Sarah
61
+ <span style="color: #3b82f6; margin-left: 8px;">●</span> You
62
+ </span>
63
+ </div>
64
+ <!-- Plan content (ugly.bot webview) -->
65
+ <div style="flex: 1; padding: 16px; overflow: auto; font-size: 13px; color: #c4c4d4; line-height: 1.7;">
66
+ <div style="font-size: 11px; color: #a78bfa; background: #a78bfa15; padding: 4px 8px; border-radius: 4px; display: inline-block; margin-bottom: 12px;">ugly.bot webview</div>
67
+ <div style="font-size: 18px; font-weight: 600; color: #e2e8f0; margin-bottom: 8px;">Auth System</div>
68
+ <div style="color: #888; margin-bottom: 16px;">JWT authentication with HttpOnly cookies and ugly.bot OAuth.</div>
69
+ <div style="font-size: 15px; font-weight: 600; color: #e2e8f0; margin-bottom: 8px;">Requirements</div>
70
+ <div style="margin-bottom: 4px;">✅ OAuth via ugly.bot</div>
71
+ <div style="margin-bottom: 4px;">☐ Login page with email/password</div>
72
+ <div style="margin-bottom: 4px;">☐ Password reset flow</div>
73
+ <div style="margin-bottom: 4px;">☐ Session management</div>
74
+ <div style="margin-top: 16px; font-size: 15px; font-weight: 600; color: #e2e8f0; margin-bottom: 8px;">Design Notes</div>
75
+ <div style="color: #888;">Use bcrypt for password hashing. Token refresh...</div>
76
+ <div style="margin-top: 16px; background: #3b82f620; border-left: 3px solid #3b82f6; padding: 8px 12px; border-radius: 0 4px 4px 0;">
77
+ <div style="font-size: 11px; color: #3b82f6; margin-bottom: 4px;">Sarah — 2 min ago</div>
78
+ <div style="font-size: 12px; color: #c4c4d4;">Should we also support magic links? That would simplify the mobile flow.</div>
79
+ </div>
80
+ </div>
81
+ </div>
82
+
83
+ <!-- Right: Agent chat -->
84
+ <div style="width: 320px; background: #1e1e3a; display: flex; flex-direction: column; flex-shrink: 0;">
85
+ <div style="padding: 8px 12px; border-bottom: 1px solid #2a2a4a; font-size: 12px; color: #888; display: flex; align-items: center; gap: 8px;">
86
+ <span style="font-weight: 600; color: #ccc;">Agent</span>
87
+ <select style="background: #16162a; border: 1px solid #2a2a4a; color: #888; font-size: 11px; padding: 2px 6px; border-radius: 4px;">
88
+ <option>Claude Code</option>
89
+ <option>Codex CLI</option>
90
+ <option>Gemini CLI</option>
91
+ </select>
92
+ <span style="margin-left: auto; font-size: 11px; background: #22c55e33; color: #22c55e; padding: 2px 6px; border-radius: 4px;">● Running</span>
93
+ </div>
94
+ <!-- Terminal output -->
95
+ <div style="flex: 1; padding: 12px; font-family: monospace; font-size: 11px; color: #888; overflow: auto; line-height: 1.6;">
96
+ <div style="color: #a78bfa;">Claude Code</div>
97
+ <div style="color: #666; margin-bottom: 8px;">$ claude</div>
98
+ <div style="color: #ccc; margin-bottom: 4px;">I'll implement the auth system based on the plan. Let me start by reading the current code...</div>
99
+ <div style="color: #666; margin-bottom: 4px;">Read server/index.ts</div>
100
+ <div style="color: #666; margin-bottom: 4px;">Read shared/api.ts</div>
101
+ <div style="color: #ccc; margin-bottom: 4px;">I see the project uses ugly-app's built-in auth. I'll add the email/password flow...</div>
102
+ <div style="color: #22c55e; margin-bottom: 4px;">Write client/pages/Login.tsx</div>
103
+ <div style="color: #22c55e; margin-bottom: 4px;">Edit shared/api.ts</div>
104
+ <div style="color: #888; margin-bottom: 4px;">Added login and register requests...</div>
105
+ </div>
106
+ <!-- Input -->
107
+ <div style="padding: 8px 12px; border-top: 1px solid #2a2a4a;">
108
+ <div style="background: #16162a; border: 1px solid #2a2a4a; border-radius: 6px; padding: 8px 12px; font-size: 12px; color: #666;">Type a message...</div>
109
+ </div>
110
+ </div>
111
+ </div>
112
+
113
+ <!-- Bottom bar -->
114
+ <div style="display: flex; align-items: center; background: #16162a; padding: 4px 16px; gap: 16px; flex-shrink: 0; font-size: 11px; color: #666;">
115
+ <span>main</span>
116
+ <span>● 3 modified</span>
117
+ <span style="margin-left: auto;">Port 3000</span>
118
+ <span>TypeScript</span>
119
+ </div>
120
+ </div>
121
+ </div>
122
+ </div>
123
+
124
+ <div class="section" style="margin-top: 24px;">
125
+ <h3>Panel Types</h3>
126
+ <div class="split">
127
+ <div style="flex: 1;">
128
+ <div style="background: #a78bfa20; border: 1px solid #a78bfa40; border-radius: 8px; padding: 16px;">
129
+ <h4 style="color: #a78bfa; margin-top: 0;">ugly.bot Webviews</h4>
130
+ <p style="font-size: 13px; color: #999; margin-bottom: 12px;">Authenticated iframes into ugly.bot. Already built — just embed them.</p>
131
+ <ul style="font-size: 13px; color: #ccc; line-height: 1.8;">
132
+ <li>Plan editor (collab markdown)</li>
133
+ <li>Feedback dashboard</li>
134
+ <li>Error logs</li>
135
+ <li>Performance logs</li>
136
+ <li>Event viewer</li>
137
+ <li>Experiment manager</li>
138
+ <li>User metrics</li>
139
+ <li>DB query runner</li>
140
+ <li>Bot testing & feedback</li>
141
+ <li>Unit test runner</li>
142
+ <li>Playwright test runner</li>
143
+ </ul>
144
+ </div>
145
+ </div>
146
+ <div style="flex: 1;">
147
+ <div style="background: #22c55e20; border: 1px solid #22c55e40; border-radius: 8px; padding: 16px;">
148
+ <h4 style="color: #22c55e; margin-top: 0;">Local Panels</h4>
149
+ <p style="font-size: 13px; color: #999; margin-bottom: 12px;">Rendered by the sidecar's ugly-app client. Need to be built.</p>
150
+ <ul style="font-size: 13px; color: #ccc; line-height: 1.8;">
151
+ <li>Code editor (CodeMirror 6)</li>
152
+ <li>File tree (sidecar filesystem)</li>
153
+ <li>Agent terminal (xterm.js)</li>
154
+ <li>Embedded preview (iframe + logs)</li>
155
+ </ul>
156
+ </div>
157
+ </div>
158
+ </div>
159
+ </div>
@@ -0,0 +1,116 @@
1
+ <h2>Updated Architecture</h2>
2
+ <p class="subtitle">Final design with LSP integration and AI preview capture</p>
3
+
4
+ <div class="section">
5
+ <h3>System Architecture</h3>
6
+ <div class="mockup">
7
+ <div class="mockup-header">ugly-studio — Component Map</div>
8
+ <div class="mockup-body" style="padding: 24px; font-size: 13px; color: #ccc; line-height: 1.6;">
9
+
10
+ <!-- Electron -->
11
+ <div style="border: 1px solid #666; border-radius: 8px; padding: 16px; margin-bottom: 16px;">
12
+ <div style="font-weight: 600; color: #e2e8f0; margin-bottom: 8px;">Electron Shell</div>
13
+ <div style="color: #888; font-size: 12px;">App lifecycle, auto-update, spawns sidecar, opens BrowserWindow to localhost:PORT</div>
14
+ </div>
15
+
16
+ <!-- Sidecar -->
17
+ <div style="border: 1px solid #a78bfa; border-radius: 8px; padding: 16px; margin-bottom: 16px;">
18
+ <div style="font-weight: 600; color: #a78bfa; margin-bottom: 12px;">ugly-app Sidecar Server</div>
19
+
20
+ <div style="display: flex; gap: 12px;">
21
+ <!-- Main thread -->
22
+ <div style="flex: 1; background: #1a1a2e; border-radius: 6px; padding: 12px;">
23
+ <div style="font-weight: 600; color: #e2e8f0; margin-bottom: 8px; font-size: 12px;">Main Thread</div>
24
+ <div style="font-size: 11px; color: #888; line-height: 1.8;">
25
+ Express + WebSocket (serves UI)<br>
26
+ Agent process manager (PTY)<br>
27
+ fs.watch (file changes → UI)<br>
28
+ Preview screenshot capture<br>
29
+ LSP process manager (tsserver)
30
+ </div>
31
+ </div>
32
+
33
+ <!-- Workers -->
34
+ <div style="flex: 1; background: #1a1a2e; border-radius: 6px; padding: 12px;">
35
+ <div style="font-weight: 600; color: #e2e8f0; margin-bottom: 8px; font-size: 12px;">Worker Threads</div>
36
+ <div style="font-size: 11px; color: #888; line-height: 1.8;">
37
+ Git ops (isomorphic-git)<br>
38
+ npm ops (bundled node fallback)<br>
39
+ File search / indexing
40
+ </div>
41
+ </div>
42
+
43
+ <!-- Bundled -->
44
+ <div style="flex: 1; background: #1a1a2e; border-radius: 6px; padding: 12px;">
45
+ <div style="font-weight: 600; color: #e2e8f0; margin-bottom: 8px; font-size: 12px;">Bundled Fallbacks</div>
46
+ <div style="font-size: 11px; color: #888; line-height: 1.8;">
47
+ Portable git<br>
48
+ Portable Node.js<br>
49
+ (used if system versions missing/old)
50
+ </div>
51
+ </div>
52
+ </div>
53
+ </div>
54
+
55
+ <!-- UI -->
56
+ <div style="border: 1px solid #22c55e; border-radius: 8px; padding: 16px;">
57
+ <div style="font-weight: 600; color: #22c55e; margin-bottom: 12px;">IDE UI (React client)</div>
58
+
59
+ <div style="display: flex; gap: 12px;">
60
+ <div style="flex: 1; background: #22c55e15; border-radius: 6px; padding: 12px;">
61
+ <div style="font-weight: 600; color: #22c55e; margin-bottom: 8px; font-size: 12px;">Local Panels (build these)</div>
62
+ <div style="font-size: 11px; color: #ccc; line-height: 1.8;">
63
+ Code editor (CodeMirror 6 + LSP)<br>
64
+ File tree<br>
65
+ Agent terminal (xterm.js + PTY)<br>
66
+ Embedded preview (iframe + log capture)
67
+ </div>
68
+ </div>
69
+
70
+ <div style="flex: 1; background: #a78bfa15; border-radius: 6px; padding: 12px;">
71
+ <div style="font-weight: 600; color: #a78bfa; margin-bottom: 8px; font-size: 12px;">ugly.bot Webviews (already built)</div>
72
+ <div style="font-size: 11px; color: #ccc; line-height: 1.8;">
73
+ Plan editor (collab markdown)<br>
74
+ Feedback dashboard<br>
75
+ Error logs / Perf logs<br>
76
+ Event viewer<br>
77
+ Experiment manager<br>
78
+ User metrics<br>
79
+ DB query runner<br>
80
+ Unit / Playwright / Bot testing
81
+ </div>
82
+ </div>
83
+ </div>
84
+ </div>
85
+
86
+ </div>
87
+ </div>
88
+ </div>
89
+
90
+ <div class="section" style="margin-top: 24px;">
91
+ <h3>New Features Added</h3>
92
+ <div class="split">
93
+ <div class="mockup">
94
+ <div class="mockup-header">LSP Integration</div>
95
+ <div class="mockup-body" style="padding: 16px; font-size: 12px; color: #ccc; line-height: 1.8;">
96
+ <div style="color: #a78bfa; font-weight: 600; margin-bottom: 4px;">Sidecar spawns tsserver</div>
97
+ <div style="color: #888;">TypeScript language server runs as a child process managed by the sidecar. Communicates via LSP JSON-RPC over stdio.</div>
98
+ <div style="margin-top: 12px; color: #22c55e; font-weight: 600; margin-bottom: 4px;">CodeMirror 6 receives LSP messages</div>
99
+ <div style="color: #888;">Sidecar bridges LSP ↔ WebSocket. The CodeMirror client gets: autocomplete, go-to-definition, hover types, error diagnostics, find references.</div>
100
+ <div style="margin-top: 12px; color: #f59e0b; font-weight: 600; margin-bottom: 4px;">Lifecycle</div>
101
+ <div style="color: #888;">tsserver starts when a project opens, restarts on crash, shuts down when project closes. One instance per open project.</div>
102
+ </div>
103
+ </div>
104
+ <div class="mockup">
105
+ <div class="mockup-header">AI Preview Capture</div>
106
+ <div class="mockup-body" style="padding: 16px; font-size: 12px; color: #ccc; line-height: 1.8;">
107
+ <div style="color: #a78bfa; font-weight: 600; margin-bottom: 4px;">Screenshot the preview iframe</div>
108
+ <div style="color: #888;">Electron's webContents.capturePage() captures the preview panel. Triggered on: agent request, manual button, or after dev server hot-reload.</div>
109
+ <div style="margin-top: 12px; color: #22c55e; font-weight: 600; margin-bottom: 4px;">Exposed via MCP tool</div>
110
+ <div style="color: #888;">ugly.bot's MCP server exposes a "get_screenshot" tool. Agents call it to see what the app looks like, enabling visual iteration.</div>
111
+ <div style="margin-top: 12px; color: #f59e0b; font-weight: 600; margin-bottom: 4px;">Console logs included</div>
112
+ <div style="color: #888;">Screenshot is paired with recent console output so the agent gets both visual and runtime context.</div>
113
+ </div>
114
+ </div>
115
+ </div>
116
+ </div>
@@ -0,0 +1 @@
1
+ {"reason":"idle timeout","timestamp":1775349316299}
package/README.md CHANGED
@@ -639,27 +639,24 @@ await userHelper.set(db, { id: userId, ...dbDefaults(), email });
639
639
 
640
640
  ### Indexes
641
641
 
642
- ```typescript
643
- // shared/dbIndexes.ts
644
- import { defineDbIndexes } from 'ugly-app/shared';
642
+ Indexes are defined on the collection definition in `shared/collections.ts`:
645
643
 
646
- export const dbIndexes = defineDbIndexes({
644
+ ```typescript
645
+ export const collections = defineCollections({
647
646
  note: {
647
+ type: {} as Note,
648
+ meta: { cache: true, trackable: true, public: false, cascadeFrom: null },
648
649
  indexes: [
649
650
  { fields: { userId: 1, created: -1 } },
650
651
  { fields: { title: 1 }, unique: true },
651
652
  ],
652
- searchIndexes: [
653
- { name: 'note_search', fields: { title: 'string', body: 'string' } },
654
- ],
655
- vectorIndexes: [
656
- { name: 'note_embedding', field: 'embedding', dimensions: 1536, similarity: 'cosine' },
657
- ],
658
653
  },
659
654
  });
660
655
  ```
661
656
 
662
- Run `npm run db:init` to create/update indexes.
657
+ After adding or modifying indexes:
658
+ 1. Run `npm run db:schema-gen` to generate a migration file
659
+ 2. Run `npm run db:migrate` to apply the migration
663
660
 
664
661
  ---
665
662
 
@@ -1163,7 +1160,6 @@ Run with `npm run db:migrate`. Use `npm run db:migrate -- --status` to preview p
1163
1160
  | `ugly-app dev` | Start all dev services (Docker, server, Vite, tsc, eslint) |
1164
1161
  | `ugly-app build` | Production build |
1165
1162
  | `ugly-app login` | Login to ugly.bot |
1166
- | `ugly-app db:init` | Create/update MongoDB indexes |
1167
1163
  | `ugly-app db:migrate` | Run pending migrations (`--status` to preview) |
1168
1164
  | `ugly-app deploy:single` | Deploy to a single server |
1169
1165
  | `ugly-app deploy:multi` | Deploy to multiple servers |
@@ -0,0 +1,18 @@
1
+ interface CollectionBootstrapConfig {
2
+ indexes?: Array<{
3
+ fields: Record<string, 1 | -1>;
4
+ unique?: boolean;
5
+ ttl?: number;
6
+ }>;
7
+ search?: {
8
+ fields: string[];
9
+ language?: string;
10
+ };
11
+ vector?: {
12
+ dimensions: number;
13
+ source: string;
14
+ };
15
+ }
16
+ export declare function generateBootstrapMigration(collections: Record<string, CollectionBootstrapConfig>): string;
17
+ export {};
18
+ //# sourceMappingURL=bootstrapMigration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bootstrapMigration.d.ts","sourceRoot":"","sources":["../../src/cli/bootstrapMigration.ts"],"names":[],"mappings":"AAAA,UAAU,yBAAyB;IACjC,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpF,MAAM,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACjD,MAAM,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CACjD;AAED,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,GACrD,MAAM,CAyDR"}
@@ -0,0 +1,44 @@
1
+ export function generateBootstrapMigration(collections) {
2
+ // Sort collection names for deterministic output
3
+ const tableNames = Object.keys(collections).sort();
4
+ // Build CREATE TABLE statements
5
+ const createTableStatements = tableNames.map((name) => ` await query(\`CREATE TABLE IF NOT EXISTS "${name}" (\n` +
6
+ ` _id TEXT PRIMARY KEY,\n` +
7
+ ` data JSONB NOT NULL,\n` +
8
+ ` created TIMESTAMPTZ NOT NULL DEFAULT now(),\n` +
9
+ ` updated TIMESTAMPTZ NOT NULL DEFAULT now(),\n` +
10
+ ` version INTEGER NOT NULL DEFAULT 1\n` +
11
+ ` )\`);` +
12
+ `\n await query(\`CREATE INDEX IF NOT EXISTS "idx_${name}_data" ON "${name}" USING GIN (data)\`);`);
13
+ // Build per-collection index SQL statements
14
+ const indexStatements = [];
15
+ for (const name of tableNames) {
16
+ const config = collections[name];
17
+ for (const idx of config.indexes ?? []) {
18
+ const fields = Object.keys(idx.fields);
19
+ if (idx.unique && fields.length === 1) {
20
+ const field = fields[0];
21
+ indexStatements.push(` await query(\`CREATE UNIQUE INDEX IF NOT EXISTS "idx_${name}_${field}_unique" ON "${name}" ((data->>'${field}'))\`);`);
22
+ }
23
+ if (idx.ttl !== undefined) {
24
+ const field = fields[0];
25
+ indexStatements.push(` await query(\`CREATE INDEX IF NOT EXISTS "idx_${name}_${field}" ON "${name}" ((data->>'${field}'))\`);`);
26
+ }
27
+ }
28
+ if (config.search) {
29
+ indexStatements.push(` await query(\`ALTER TABLE "${name}" ADD COLUMN IF NOT EXISTS search TSVECTOR\`);`);
30
+ indexStatements.push(` await query(\`CREATE INDEX IF NOT EXISTS "idx_${name}_search" ON "${name}" USING GIN (search)\`);`);
31
+ }
32
+ }
33
+ const allStatements = [...createTableStatements, ...indexStatements];
34
+ return `import type { query as pgQuery } from 'ugly-app/server';
35
+
36
+ // Bootstrap migration — creates all tables and indexes.
37
+ // Generated automatically. Safe to run on existing databases.
38
+
39
+ export async function up(query: typeof pgQuery): Promise<void> {
40
+ ${allStatements.join('\n')}
41
+ }
42
+ `;
43
+ }
44
+ //# sourceMappingURL=bootstrapMigration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bootstrapMigration.js","sourceRoot":"","sources":["../../src/cli/bootstrapMigration.ts"],"names":[],"mappings":"AAMA,MAAM,UAAU,0BAA0B,CACxC,WAAsD;IAEtD,iDAAiD;IACjD,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE,CAAC;IAEnD,gCAAgC;IAChC,MAAM,qBAAqB,GAAG,UAAU,CAAC,GAAG,CAC1C,CAAC,IAAI,EAAE,EAAE,CACP,+CAA+C,IAAI,OAAO;QAC1D,kCAAkC;QAClC,gCAAgC;QAChC,oDAAoD;QACpD,oDAAoD;QACpD,2CAA2C;QAC3C,SAAS;QACT,qDAAqD,IAAI,cAAc,IAAI,wBAAwB,CACtG,CAAC;IAEF,4CAA4C;IAC5C,MAAM,eAAe,GAAa,EAAE,CAAC;IACrC,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QACjC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;YACvC,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACvC,IAAI,GAAG,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACtC,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;gBACxB,eAAe,CAAC,IAAI,CAClB,0DAA0D,IAAI,IAAI,KAAK,gBAAgB,IAAI,eAAe,KAAK,SAAS,CACzH,CAAC;YACJ,CAAC;YACD,IAAI,GAAG,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;gBAC1B,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;gBACxB,eAAe,CAAC,IAAI,CAClB,mDAAmD,IAAI,IAAI,KAAK,SAAS,IAAI,eAAe,KAAK,SAAS,CAC3G,CAAC;YACJ,CAAC;QACH,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAClB,eAAe,CAAC,IAAI,CAClB,gCAAgC,IAAI,gDAAgD,CACrF,CAAC;YACF,eAAe,CAAC,IAAI,CAClB,mDAAmD,IAAI,gBAAgB,IAAI,0BAA0B,CACtG,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,aAAa,GAAG,CAAC,GAAG,qBAAqB,EAAE,GAAG,eAAe,CAAC,CAAC;IAErE,OAAO;;;;;;EAMP,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC;;CAEzB,CAAC;AACF,CAAC"}
@@ -0,0 +1,4 @@
1
+ export declare function runCloudCleanup(opts: {
2
+ all?: boolean;
3
+ }): Promise<void>;
4
+ //# sourceMappingURL=cloudCleanup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudCleanup.d.ts","sourceRoot":"","sources":["../../src/cli/cloudCleanup.ts"],"names":[],"mappings":"AAQA,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAAE,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAqD5E"}
@@ -0,0 +1,62 @@
1
+ import { readUglyBotAuth, isTokenValid } from './uglyBotAuth.js';
2
+ export async function runCloudCleanup(opts) {
3
+ const auth = readUglyBotAuth();
4
+ if (!auth?.token || !isTokenValid(auth)) {
5
+ console.error('[ugly-app] Not logged in. Run `npx ugly-app login` first.');
6
+ process.exit(1);
7
+ }
8
+ const serverUrl = auth.serverUrl || 'https://ugly.bot';
9
+ // List resources
10
+ const listRes = await fetch(`${serverUrl}/api/dev-resources`, {
11
+ headers: { 'Authorization': `Bearer ${auth.token}` },
12
+ });
13
+ if (!listRes.ok) {
14
+ console.error(`[ugly-app] Failed to list resources: ${listRes.status} ${listRes.statusText}`);
15
+ process.exit(1);
16
+ }
17
+ const resources = await listRes.json();
18
+ if (resources.length === 0) {
19
+ console.log('[ugly-app] No cloud dev resources found.');
20
+ return;
21
+ }
22
+ console.log('\nCloud dev resources:\n');
23
+ for (const r of resources) {
24
+ const ago = timeSince(new Date(r.lastActiveAt));
25
+ console.log(` ${r.projectId} (last active: ${ago} ago) [${r.services.join(', ')}]`);
26
+ }
27
+ console.log();
28
+ if (!opts.all) {
29
+ console.log('Run with --all to delete all resources:');
30
+ console.log(' npx ugly-app cloud:cleanup --all');
31
+ return;
32
+ }
33
+ // Delete all
34
+ console.log('[ugly-app] Deleting all cloud dev resources...');
35
+ for (const r of resources) {
36
+ const delRes = await fetch(`${serverUrl}/api/dev-resources/${r.projectId}`, {
37
+ method: 'DELETE',
38
+ headers: { 'Authorization': `Bearer ${auth.token}` },
39
+ });
40
+ if (delRes.ok) {
41
+ console.log(` Deleted: ${r.projectId}`);
42
+ }
43
+ else {
44
+ console.error(` Failed to delete ${r.projectId}: ${delRes.status}`);
45
+ }
46
+ }
47
+ console.log('\n[ugly-app] Done.');
48
+ }
49
+ function timeSince(date) {
50
+ const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
51
+ if (seconds < 60)
52
+ return `${seconds}s`;
53
+ const minutes = Math.floor(seconds / 60);
54
+ if (minutes < 60)
55
+ return `${minutes}m`;
56
+ const hours = Math.floor(minutes / 60);
57
+ if (hours < 24)
58
+ return `${hours}h`;
59
+ const days = Math.floor(hours / 24);
60
+ return `${days}d`;
61
+ }
62
+ //# sourceMappingURL=cloudCleanup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudCleanup.js","sourceRoot":"","sources":["../../src/cli/cloudCleanup.ts"],"names":[],"mappings":"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"}
package/dist/cli/dev.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export declare function runDev(opts?: {
2
2
  watch: boolean;
3
3
  verbose: boolean;
4
+ local: boolean;
4
5
  }): Promise<void>;
5
6
  //# sourceMappingURL=dev.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/cli/dev.ts"],"names":[],"mappings":"AAsFA,wBAAsB,MAAM,CAAC,IAAI,GAAE;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAqC,GAAG,OAAO,CAAC,IAAI,CAAC,CAoLzH"}
1
+ {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/cli/dev.ts"],"names":[],"mappings":"AAuEA,wBAAsB,MAAM,CAAC,IAAI,GAAE;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAmD,GAAG,OAAO,CAAC,IAAI,CAAC,CAwMvJ"}
package/dist/cli/dev.js CHANGED
@@ -21,7 +21,6 @@ function waitForPort(port, retries = 30, intervalMs = 1000) {
21
21
  tryConnect();
22
22
  });
23
23
  }
24
- import { runInitDb } from './initDb.js';
25
24
  import { runMigrations } from './migrate.js';
26
25
  import { computeEnvVars, readUglyAppConfig, validatePortStart, } from './uglyappConfig.js';
27
26
  async function waitForPostgres(url, opts) {
@@ -59,21 +58,7 @@ async function ensureProjectDatabase(projectId) {
59
58
  await client.end();
60
59
  }
61
60
  }
62
- async function isDbUninitialized(url) {
63
- const client = new pg.Client({ connectionString: url });
64
- try {
65
- await client.connect();
66
- const result = await client.query(`SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'user') AS exists`);
67
- return !result.rows[0].exists;
68
- }
69
- catch {
70
- return true;
71
- }
72
- finally {
73
- await client.end();
74
- }
75
- }
76
- export async function runDev(opts = { watch: false, verbose: false }) {
61
+ export async function runDev(opts = { watch: false, verbose: false, local: false }) {
77
62
  // Read .uglyapp config and inject computed env vars before spawning any child process.
78
63
  // Vars set here take precedence over .env values because dotenv (used by tsx --env-file)
79
64
  // skips vars already present in process.env (override: false behavior).
@@ -116,27 +101,50 @@ export async function runDev(opts = { watch: false, verbose: false }) {
116
101
  catch {
117
102
  // best-effort — port may simply not be in use
118
103
  }
119
- // ── 1. Auto-start shared Docker if not already running ─────────────────────
120
- const { isDockerRunning, dockerStart } = await import('./docker.js');
121
- if (!(await isDockerRunning())) {
122
- await dockerStart();
104
+ // ── 1. Start infrastructure (cloud sidecar or local Docker) ───────────────
105
+ let useDocker = opts.local;
106
+ if (!useDocker) {
107
+ const { readUglyBotAuth, isTokenValid } = await import('./uglyBotAuth.js');
108
+ const auth = readUglyBotAuth();
109
+ if (!auth?.token || !isTokenValid(auth)) {
110
+ console.warn('[ugly-app] No ugly.bot auth — falling back to Docker (run `npx ugly-app login` for cloud dev)');
111
+ useDocker = true;
112
+ }
113
+ else if (config) {
114
+ const { startSidecar } = await import('./sidecar.js');
115
+ try {
116
+ const ports = await startSidecar({ projectId: config.projectId, token: auth.token });
117
+ process.env['POSTGRES_URL'] = `postgresql://app:app@localhost:${ports.postgres}/${config.projectId}`;
118
+ process.env['NATS_URL'] = `nats://localhost:${ports.nats}`;
119
+ process.env['MINIO_ENDPOINT'] = `http://localhost:${ports.s3}`;
120
+ process.env['QDRANT_URL'] = `http://localhost:${ports.qdrantHttp}`;
121
+ }
122
+ catch (err) {
123
+ console.error(`[ugly-app] Sidecar failed: ${err.message}`);
124
+ console.error('[ugly-app] Start with --local for offline Docker mode');
125
+ process.exit(1);
126
+ }
127
+ }
128
+ }
129
+ if (useDocker) {
130
+ const { isDockerRunning, dockerStart } = await import('./docker.js');
131
+ if (!(await isDockerRunning())) {
132
+ await dockerStart();
133
+ }
123
134
  }
124
135
  // ── 2. Wait for PostgreSQL to be reachable (use admin DB) ──────────────────
125
- const adminPgUrl = 'postgresql://app:app@localhost:5432/app';
136
+ // Use POSTGRES_URL for sidecar (ephemeral port) or admin URL for Docker (fixed port)
137
+ const pgWaitUrl = useDocker
138
+ ? 'postgresql://app:app@localhost:5432/app'
139
+ : (process.env['POSTGRES_URL'] ?? 'postgresql://app:app@localhost:5432/app');
126
140
  process.stdout.write('[ugly-app] Waiting for PostgreSQL');
127
- await waitForPostgres(adminPgUrl, { retries: 30, intervalMs: 1000 });
141
+ await waitForPostgres(pgWaitUrl, { retries: 30, intervalMs: 1000 });
128
142
  process.stdout.write(' ✓\n');
129
- // ── 3. Ensure per-project database exists ──────────────────────────────────
130
- if (config) {
143
+ // ── 3. Ensure per-project database exists (Docker only — cloud manages its own DBs)
144
+ if (config && useDocker) {
131
145
  await ensureProjectDatabase(config.projectId);
132
146
  }
133
- const pgUrl = process.env['POSTGRES_URL'] ?? adminPgUrl;
134
- // ── 4. Init indexes on first run ───────────────────────────────────────────
135
- if (await isDbUninitialized(pgUrl)) {
136
- console.log('[ugly-app] First run — initialising database tables...');
137
- await runInitDb();
138
- }
139
- // ── 5. Run any pending migrations ─────────────────────────────────────────
147
+ // ── 4. Run any pending migrations ─────────────────────────────────────────
140
148
  await runMigrations();
141
149
  // ── 6. Spawn dev processes (docker already running detached) ───────────────
142
150
  const verbose = opts.verbose;