Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

vscode: replace Netlify status bar with live OTA build status

Shows AC-OS oven build status in the VSCode status bar:
- Spinning icon + stage/percent during active builds
- Green check when idle (last build succeeded)
- Red error when last build failed
- Polls every 3s during builds, 30s when idle
- Click for details: Open Oven, Pull & Flash, or Refresh

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+122 -110
+118 -110
vscode-extension/extension.ts
··· 1247 1247 // Initialize user status bar 1248 1248 updateUserStatusBar(); 1249 1249 1250 - // 🚀 Netlify Deploy Status Bar 1251 - // Polls the Netlify API to show live deploy status in the status bar. 1252 - // Reads NETLIFY_AUTH_TOKEN and NETLIFY_SITE_ID from environment. 1253 - let statusBarDeploy: vscode.StatusBarItem; 1254 - let deployPollInterval: NodeJS.Timeout | undefined; 1250 + // 🔥 AC-OS OTA Build Status Bar 1251 + // Polls oven.aesthetic.computer/native-build to show live OTA build status. 1252 + let statusBarOTA: vscode.StatusBarItem; 1253 + let otaPollInterval: NodeJS.Timeout | undefined; 1255 1254 1256 - interface NetlifyDeploy { 1257 - id: string; 1258 - state: string; // 'building' | 'enqueued' | 'uploading' | 'processing' | 'ready' | 'error' 1259 - branch: string; 1260 - title?: string; 1261 - created_at: string; 1262 - published_at?: string; 1263 - deploy_time?: number; 1264 - error_message?: string; 1265 - commit_ref?: string; 1255 + interface OTABuildState { 1256 + activeJobId: string | null; 1257 + active: { 1258 + id: string; 1259 + ref: string; 1260 + status: string; 1261 + stage: string; 1262 + percent: number; 1263 + elapsedMs: number; 1264 + error?: string; 1265 + } | null; 1266 + recent: Array<{ 1267 + id: string; 1268 + ref: string; 1269 + status: string; 1270 + stage: string; 1271 + percent: number; 1272 + elapsedMs: number; 1273 + finishedAt?: string; 1274 + error?: string; 1275 + flags?: string[]; 1276 + }>; 1266 1277 } 1267 1278 1268 - function getDeployIcon(state: string): string { 1269 - switch (state) { 1270 - case 'building': 1271 - case 'uploading': 1272 - case 'processing': 1273 - return '$(sync~spin)'; 1274 - case 'enqueued': 1275 - return '$(clock)'; 1276 - case 'ready': 1277 - return '$(check)'; 1278 - case 'error': 1279 - return '$(error)'; 1280 - default: 1281 - return '$(cloud)'; 1279 + function getOTAIcon(stage: string, status: string): string { 1280 + if (status === 'running') { 1281 + switch (stage) { 1282 + case 'binary': return '$(gear~spin)'; 1283 + case 'initramfs': return '$(package~spin)'; 1284 + case 'kernel': return '$(cpu~spin)'; 1285 + case 'smoke-test': return '$(beaker~spin)'; 1286 + case 'upload': return '$(cloud-upload~spin)'; 1287 + default: return '$(sync~spin)'; 1288 + } 1289 + } 1290 + switch (status) { 1291 + case 'success': return '$(check)'; 1292 + case 'failed': return '$(error)'; 1293 + case 'cancelled': return '$(close)'; 1294 + default: return '$(flame)'; 1282 1295 } 1283 1296 } 1284 1297 1285 - function getDeployColor(state: string): vscode.ThemeColor | undefined { 1286 - switch (state) { 1287 - case 'building': 1288 - case 'uploading': 1289 - case 'processing': 1290 - return new vscode.ThemeColor('statusBarItem.warningBackground'); 1291 - case 'error': 1292 - return new vscode.ThemeColor('statusBarItem.errorBackground'); 1293 - default: 1294 - return undefined; 1298 + function getOTAColor(status: string): vscode.ThemeColor | undefined { 1299 + switch (status) { 1300 + case 'running': return new vscode.ThemeColor('statusBarItem.warningBackground'); 1301 + case 'failed': return new vscode.ThemeColor('statusBarItem.errorBackground'); 1302 + default: return undefined; 1295 1303 } 1296 1304 } 1297 1305 1298 - async function fetchLatestDeploy(): Promise<NetlifyDeploy | null> { 1299 - const token = process.env.NETLIFY_AUTH_TOKEN; 1300 - const siteId = process.env.NETLIFY_SITE_ID; 1301 - if (!token || !siteId) return null; 1302 - 1306 + async function fetchOTAStatus(): Promise<OTABuildState | null> { 1303 1307 try { 1304 1308 const https = await import("https"); 1305 1309 return new Promise((resolve) => { 1306 1310 const req = https.request({ 1307 - hostname: 'api.netlify.com', 1308 - path: `/api/v1/sites/${siteId}/deploys?per_page=1`, 1311 + hostname: 'oven.aesthetic.computer', 1312 + path: '/native-build', 1309 1313 method: 'GET', 1310 - headers: { 'Authorization': `Bearer ${token}`, 'User-Agent': 'aesthetic-computer-vscode' }, 1314 + headers: { 'User-Agent': 'aesthetic-computer-vscode' }, 1311 1315 timeout: 5000, 1312 1316 }, (res: any) => { 1313 1317 let data = ''; 1314 1318 res.on('data', (chunk: string) => data += chunk); 1315 1319 res.on('end', () => { 1316 - try { 1317 - const deploys = JSON.parse(data); 1318 - resolve(Array.isArray(deploys) && deploys.length > 0 ? deploys[0] : null); 1319 - } catch { resolve(null); } 1320 + try { resolve(JSON.parse(data)); } 1321 + catch { resolve(null); } 1320 1322 }); 1321 1323 }); 1322 1324 req.on('error', () => resolve(null)); ··· 1328 1330 } 1329 1331 } 1330 1332 1331 - async function updateDeployStatusBar() { 1332 - if (!statusBarDeploy) { 1333 - statusBarDeploy = vscode.window.createStatusBarItem( 1333 + async function updateOTAStatusBar() { 1334 + if (!statusBarOTA) { 1335 + statusBarOTA = vscode.window.createStatusBarItem( 1334 1336 vscode.StatusBarAlignment.Left, 1335 1337 99, 1336 1338 ); 1337 - statusBarDeploy.command = "aestheticComputer.showDeployDetails"; 1338 - context.subscriptions.push(statusBarDeploy); 1339 + statusBarOTA.command = "aestheticComputer.showOTADetails"; 1340 + context.subscriptions.push(statusBarOTA); 1339 1341 } 1340 1342 1341 - const deploy = await fetchLatestDeploy(); 1342 - if (!deploy) { 1343 - statusBarDeploy.text = '$(cloud) Netlify'; 1344 - statusBarDeploy.tooltip = 'Could not fetch deploy status.\nCheck NETLIFY_AUTH_TOKEN and NETLIFY_SITE_ID.'; 1345 - statusBarDeploy.backgroundColor = undefined; 1346 - statusBarDeploy.show(); 1343 + const state = await fetchOTAStatus(); 1344 + if (!state) { 1345 + statusBarOTA.text = '$(flame) OTA'; 1346 + statusBarOTA.tooltip = 'Could not reach oven.aesthetic.computer'; 1347 + statusBarOTA.backgroundColor = undefined; 1348 + statusBarOTA.show(); 1347 1349 return; 1348 1350 } 1349 1351 1350 - const icon = getDeployIcon(deploy.state); 1351 - const branch = deploy.branch || 'main'; 1352 - const shortRef = deploy.commit_ref?.substring(0, 7) || ''; 1353 - const stateLabel = deploy.state === 'ready' ? 'live' : deploy.state; 1354 - 1355 - statusBarDeploy.text = `${icon} ${stateLabel}`; 1356 - statusBarDeploy.backgroundColor = getDeployColor(deploy.state); 1357 - 1358 - const time = deploy.published_at ? new Date(deploy.published_at).toLocaleTimeString() : ''; 1359 - const duration = deploy.deploy_time ? `${deploy.deploy_time}s` : ''; 1360 - let tip = `Netlify: ${deploy.state}\nBranch: ${branch}`; 1361 - if (shortRef) tip += `\nCommit: ${shortRef}`; 1362 - if (time) tip += `\nPublished: ${time}`; 1363 - if (duration) tip += `\nBuild time: ${duration}`; 1364 - if (deploy.title) tip += `\n${deploy.title}`; 1365 - if (deploy.error_message) tip += `\nError: ${deploy.error_message}`; 1366 - statusBarDeploy.tooltip = tip; 1367 - statusBarDeploy.show(); 1352 + if (state.active) { 1353 + const a = state.active; 1354 + const elapsed = Math.round(a.elapsedMs / 1000); 1355 + const ref = a.ref?.substring(0, 7) || ''; 1356 + statusBarOTA.text = `${getOTAIcon(a.stage, 'running')} ${a.stage} ${a.percent}%`; 1357 + statusBarOTA.backgroundColor = getOTAColor('running'); 1358 + statusBarOTA.tooltip = `AC-OS Building\nStage: ${a.stage} (${a.percent}%)\nCommit: ${ref}\nElapsed: ${elapsed}s`; 1359 + } else if (state.recent?.length > 0) { 1360 + const r = state.recent[0]; 1361 + const ref = r.ref?.substring(0, 7) || ''; 1362 + const elapsed = Math.round(r.elapsedMs / 1000); 1363 + const time = r.finishedAt ? new Date(r.finishedAt).toLocaleTimeString() : ''; 1364 + const label = r.status === 'success' ? 'OTA' : r.status; 1365 + statusBarOTA.text = `${getOTAIcon(r.stage, r.status)} ${label}`; 1366 + statusBarOTA.backgroundColor = getOTAColor(r.status); 1367 + let tip = `AC-OS: ${r.status}\nCommit: ${ref}\nBuild time: ${elapsed}s`; 1368 + if (time) tip += `\nFinished: ${time}`; 1369 + if (r.error) tip += `\nError: ${r.error}`; 1370 + statusBarOTA.tooltip = tip; 1371 + } 1368 1372 1369 - // Poll faster when a build is in progress 1370 - adjustDeployPollRate(deploy.state); 1373 + statusBarOTA.show(); 1374 + adjustOTAPollRate(state); 1371 1375 } 1372 1376 1373 - let currentPollRate = 30000; // default: 30s 1374 - function adjustDeployPollRate(state: string) { 1375 - const isActive = ['building', 'uploading', 'processing', 'enqueued'].includes(state); 1376 - const newRate = isActive ? 5000 : 30000; // 5s when building, 30s when idle 1377 - if (newRate !== currentPollRate) { 1378 - currentPollRate = newRate; 1379 - startDeployPolling(); 1377 + let otaPollRate = 30000; 1378 + function adjustOTAPollRate(state: OTABuildState | null) { 1379 + const isActive = !!state?.active; 1380 + const newRate = isActive ? 3000 : 30000; // 3s when building, 30s idle 1381 + if (newRate !== otaPollRate) { 1382 + otaPollRate = newRate; 1383 + startOTAPolling(); 1380 1384 } 1381 1385 } 1382 1386 1383 - function startDeployPolling() { 1384 - if (deployPollInterval) clearInterval(deployPollInterval); 1385 - deployPollInterval = setInterval(() => updateDeployStatusBar(), currentPollRate); 1387 + function startOTAPolling() { 1388 + if (otaPollInterval) clearInterval(otaPollInterval); 1389 + otaPollInterval = setInterval(() => updateOTAStatusBar(), otaPollRate); 1386 1390 } 1387 1391 1388 - // Initialize deploy status bar 1389 - updateDeployStatusBar(); 1390 - startDeployPolling(); 1392 + updateOTAStatusBar(); 1393 + startOTAPolling(); 1391 1394 1392 - // Command to show deploy details / open Netlify dashboard 1393 1395 context.subscriptions.push( 1394 - vscode.commands.registerCommand("aestheticComputer.showDeployDetails", async () => { 1395 - const siteId = process.env.NETLIFY_SITE_ID; 1396 - const deploy = await fetchLatestDeploy(); 1397 - const actions = ['Open Netlify Dashboard', 'Refresh']; 1398 - if (deploy) { 1399 - const msg = `Deploy: ${deploy.state} | Branch: ${deploy.branch} | ${deploy.title || deploy.commit_ref?.substring(0, 7) || ''}`; 1400 - const choice = await vscode.window.showInformationMessage(msg, ...actions); 1401 - if (choice === 'Open Netlify Dashboard') { 1402 - vscode.env.openExternal(vscode.Uri.parse(`https://app.netlify.com/sites/aesthetic-computer/deploys`)); 1403 - } else if (choice === 'Refresh') { 1404 - updateDeployStatusBar(); 1405 - } 1406 - } else { 1407 - vscode.window.showWarningMessage('Could not fetch Netlify deploy status. Check env vars.'); 1396 + vscode.commands.registerCommand("aestheticComputer.showOTADetails", async () => { 1397 + const state = await fetchOTAStatus(); 1398 + if (!state) { 1399 + vscode.window.showWarningMessage('Could not reach oven.aesthetic.computer'); 1400 + return; 1401 + } 1402 + const actions = ['Open Oven', 'Pull & Flash', 'Refresh']; 1403 + const recent = state.recent?.[0]; 1404 + const msg = state.active 1405 + ? `OTA Building: ${state.active.stage} ${state.active.percent}% (${Math.round(state.active.elapsedMs / 1000)}s)` 1406 + : `OTA: ${recent?.status || 'unknown'} | ${recent?.ref?.substring(0, 7) || ''} | ${Math.round((recent?.elapsedMs || 0) / 1000)}s`; 1407 + const choice = await vscode.window.showInformationMessage(msg, ...actions); 1408 + if (choice === 'Open Oven') { 1409 + vscode.env.openExternal(vscode.Uri.parse('https://oven.aesthetic.computer/native-build')); 1410 + } else if (choice === 'Pull & Flash') { 1411 + const terminal = vscode.window.createTerminal('AC-OS Flash'); 1412 + terminal.show(); 1413 + terminal.sendText('fedac/native/ac-os pull'); 1414 + } else if (choice === 'Refresh') { 1415 + updateOTAStatusBar(); 1408 1416 } 1409 1417 }) 1410 1418 );
+4
vscode-extension/package.json
··· 192 192 { 193 193 "command": "aestheticComputer.welcome", 194 194 "title": "Aesthetic Computer: Welcome ✨" 195 + }, 196 + { 197 + "command": "aestheticComputer.showOTADetails", 198 + "title": "AC-OS: OTA Build Status" 195 199 } 196 200 ], 197 201 "grammars": [