@cloudgrid-io/cli 0.3.1 → 0.4.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudgrid-io/cli",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Deploy apps with a single YAML file",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -92,3 +92,149 @@ app.get('/health', (req, res) => res.json({ status: 'ok' }));
92
92
  Sibling services are accessible via auto-injected URLs:
93
93
  const apiUrl = process.env.API_URL;
94
94
  const res = await fetch(apiUrl + '/endpoint');
95
+
96
+ ## COMMON PATTERNS
97
+
98
+ ### Camera/Upload + AI Vision (TESTED, USE THIS PATTERN)
99
+
100
+ Frontend HTML for camera capture + file upload:
101
+ ```html
102
+ <input type="file" id="fileInput" accept="image/*" capture="environment">
103
+ <video id="video" autoplay playsinline></video>
104
+ <canvas id="canvas" style="display:none"></canvas>
105
+ ```
106
+
107
+ Frontend JS — capture from camera OR file upload, send as base64:
108
+ ```javascript
109
+ // File upload handler
110
+ fileInput.addEventListener('change', (e) => {
111
+ const file = e.target.files[0];
112
+ if (!file) return;
113
+ const reader = new FileReader();
114
+ reader.onload = (ev) => { capturedImage = ev.target.result; };
115
+ reader.readAsDataURL(file);
116
+ });
117
+
118
+ // Camera capture handler
119
+ async function startCamera() {
120
+ const stream = await navigator.mediaDevices.getUserMedia({
121
+ video: { facingMode: 'environment', width: { ideal: 1280 } }
122
+ });
123
+ video.srcObject = stream;
124
+ }
125
+ function capturePhoto() {
126
+ const canvas = document.getElementById('canvas');
127
+ canvas.width = video.videoWidth;
128
+ canvas.height = video.videoHeight;
129
+ canvas.getContext('2d').drawImage(video, 0, 0);
130
+ capturedImage = canvas.toDataURL('image/jpeg', 0.8);
131
+ stream.getTracks().forEach(t => t.stop());
132
+ }
133
+
134
+ // Send to backend
135
+ async function analyze() {
136
+ const res = await fetch('/api/analyze', {
137
+ method: 'POST',
138
+ headers: { 'Content-Type': 'application/json' },
139
+ body: JSON.stringify({ image: capturedImage }),
140
+ });
141
+ const data = await res.json();
142
+ // data.text contains the AI analysis
143
+ }
144
+ ```
145
+
146
+ Backend — receive image, strip data URL prefix, call AI vision:
147
+ ```javascript
148
+ app.use(express.json({ limit: '10mb' }));
149
+
150
+ app.post('/api/analyze', async (req, res) => {
151
+ const { image } = req.body;
152
+ if (!image) return res.status(400).json({ error: 'No image' });
153
+
154
+ // IMPORTANT: strip the data URL prefix before sending to AI
155
+ const base64 = image.replace(/^data:image\/\w+;base64,/, '');
156
+ const mediaType = image.match(/^data:(image\/\w+);/)?.[1] || 'image/jpeg';
157
+
158
+ const AI_URL = process.env.AI_GATEWAY_URL;
159
+ const aiRes = await fetch(AI_URL + '/vision', {
160
+ method: 'POST',
161
+ headers: {
162
+ 'Content-Type': 'application/json',
163
+ 'X-CloudGrid-App': process.env.APP_NAME,
164
+ },
165
+ body: JSON.stringify({
166
+ image: base64,
167
+ media_type: mediaType,
168
+ prompt: 'Describe what you see in this image',
169
+ max_tokens: 1024,
170
+ }),
171
+ });
172
+ const data = await aiRes.json();
173
+ res.json({ text: data.text });
174
+ });
175
+ ```
176
+
177
+ CRITICAL RULES for image + AI:
178
+ - ALWAYS set express body limit: express.json({ limit: '10mb' })
179
+ - ALWAYS strip "data:image/...;base64," prefix before sending to AI
180
+ - ALWAYS extract media_type from the data URL (image/jpeg, image/png, etc.)
181
+ - ALWAYS use { facingMode: 'environment' } for rear camera on mobile
182
+ - ALWAYS use canvas.toDataURL('image/jpeg', 0.8) for camera capture (0.8 quality = smaller)
183
+ - NEVER send the raw data URL to the AI gateway — strip the prefix first
184
+ - Supported image types: image/jpeg, image/png, image/gif, image/webp
185
+
186
+ ### AI Chat with Streaming (TESTED)
187
+ ```javascript
188
+ // Backend — SSE streaming endpoint
189
+ app.get('/api/chat-stream', async (req, res) => {
190
+ const prompt = req.query.prompt;
191
+ res.setHeader('Content-Type', 'text/event-stream');
192
+ res.setHeader('Cache-Control', 'no-cache');
193
+
194
+ const aiRes = await fetch(process.env.AI_GATEWAY_URL + '/chat', {
195
+ method: 'POST',
196
+ headers: {
197
+ 'Content-Type': 'application/json',
198
+ 'X-CloudGrid-App': process.env.APP_NAME,
199
+ },
200
+ body: JSON.stringify({ prompt, stream: true }),
201
+ });
202
+
203
+ // Pipe SSE from AI gateway to client
204
+ const reader = aiRes.body.getReader();
205
+ const decoder = new TextDecoder();
206
+ while (true) {
207
+ const { done, value } = await reader.read();
208
+ if (done) break;
209
+ res.write(decoder.decode(value));
210
+ }
211
+ res.end();
212
+ });
213
+
214
+ // Frontend — consume SSE stream
215
+ const evtSource = new EventSource('/api/chat-stream?prompt=' + encodeURIComponent(prompt));
216
+ evtSource.onmessage = (e) => {
217
+ const data = JSON.parse(e.data);
218
+ if (data.type === 'text') outputDiv.textContent += data.text;
219
+ if (data.type === 'done') evtSource.close();
220
+ };
221
+ ```
222
+
223
+ ### Using @cloudgrid-io/ai SDK (simpler alternative)
224
+ ```javascript
225
+ // If you npm install @cloudgrid-io/ai, the SDK handles everything:
226
+ const { ai } = require('@cloudgrid-io/ai');
227
+
228
+ // Chat
229
+ const result = await ai.chat('Hello');
230
+ console.log(result.text);
231
+
232
+ // Vision — pass Buffer directly
233
+ const fs = require('fs');
234
+ const photo = fs.readFileSync('photo.jpg');
235
+ const result = await ai.vision(photo, 'Describe this');
236
+ console.log(result.text);
237
+
238
+ // The SDK auto-reads AI_GATEWAY_URL and APP_NAME from env.
239
+ // Works in both cloudgrid dev and production. Zero config.
240
+ ```