Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
36b44da
Fix naive command parsing and command injection issues
AugistineCreates Jun 19, 2026
160ca6c
Fix naive command parsing and secure file operations
AugistineCreates Jun 19, 2026
596c798
Improve PixiJS utils with full TypeScript safety
AugistineCreates Jun 19, 2026
96b89db
Fix PixiJS utils: replace DisplayObject with Container and remove @ts…
AugistineCreates Jun 19, 2026
d4ebed4
Fix PixiJS isDestroyed function for WebGPU compatibility
AugistineCreates Jun 19, 2026
28f89c8
Fix canvas type mismatch, use useCallback for recalcVisibleLines, rem…
AugistineCreates Jun 19, 2026
a4eba8e
Improve PixiJS utilities type safety: replace unsafe any casts with p…
AugistineCreates Jun 19, 2026
97f0c05
Add Stellar SDK dependencies detection to auto-install in sandbox env…
AugistineCreates Jun 19, 2026
3b53684
feat: add console log tab to sandbox preview with real‑time polling
AugistineCreates Jun 19, 2026
73be105
Add Stellar/Soroban prompt and integrate into prompts index
AugistineCreates Jun 19, 2026
b42ac3e
Fix ESLint parsing error in stellar-prompt (escape backticks) and add…
AugistineCreates Jun 19, 2026
461a00e
Fix parsing error in stellar-prompt (escape markdown fences) and wrap…
AugistineCreates Jun 19, 2026
53a7308
Fix missing dependency array for useCallback in multiple-web-browsers
AugistineCreates Jun 19, 2026
b3d0992
Remove unused eslint disables, clean up ControlPanel and fix Stellar …
AugistineCreates Jun 19, 2026
3867487
Fix temporal dead zone: declare startRotation before useEffect
AugistineCreates Jun 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions app/api/detect-and-install-packages/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,11 @@ export async function POST(request: NextRequest) {
// Remove duplicates
const uniquePackages = [...new Set(packageNames)];

console.log('[detect-and-install-packages] Packages to install:', uniquePackages);

if (uniquePackages.length === 0) {
// Add known Stellar SDK packages to ensure they are installed in sandbox environments
const stellarPackages = ['@stellar/stellar-sdk', '@stellar/freighter-api'];
const finalPackageList = [...new Set([...uniquePackages, ...stellarPackages])];
console.log('[detect-and-install-packages] Final packages to install (including Stellar SDKs):', finalPackageList);
if (finalPackageList.length === 0) {
return NextResponse.json({
success: true,
packagesInstalled: [],
Expand Down
22 changes: 17 additions & 5 deletions app/api/run-command/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
import { NextRequest, NextResponse } from 'next/server';

// Simple command parser that respects double quotes
function parseCommand(command: string): string[] {
const regex = /"([^"]*)"|'([^']*)'|\S+/g;
const args: string[] = [];
let match;
while ((match = regex.exec(command)) !== null) {
args.push(match[1] ?? match[2] ?? match[0]);
}
return args;
}



// Get active sandbox from global state (in production, use a proper state management solution)
declare global {
var activeSandbox: any;
Expand All @@ -25,11 +38,10 @@ export async function POST(request: NextRequest) {

console.log(`[run-command] Executing: ${command}`);

// Parse command and arguments
const commandParts = command.trim().split(/\s+/);
const cmd = commandParts[0];
const args = commandParts.slice(1);

// Parse command and arguments using robust shell parsing
const parts = parseCommand(command);
const cmd = parts[0];
const args = parts.slice(1);
// Execute command using Vercel Sandbox
const result = await global.activeSandbox.runCommand({
cmd,
Expand Down
60 changes: 60 additions & 0 deletions components/SandboxConsoleLogs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React, { useEffect, useState } from 'react';
import { Loader2 } from 'lucide-react';

/**
* SandboxConsoleLogs fetches logs from the sandbox-logs API and displays them.
* It uses a simple polling mechanism (default 1500ms) and renders the logs
* in a scrollable, glass‑morphism styled container.
*/
const POLL_INTERVAL_MS = 1500; // default interval, can be tweaked by the user
const MAX_LOG_LINES = 500; // prevent unbounded growth

export default function SandboxConsoleLogs() {
const [logs, setLogs] = useState<string[]>([]);
const [loading, setLoading] = useState(true);

useEffect(() => {
let isMounted = true;
const fetchLogs = async () => {
try {
const res = await fetch('/api/sandbox-logs');
if (!res.ok) throw new Error('Failed to fetch logs');
const data = await res.json();
if (isMounted) {
const newLines: string[] = Array.isArray(data.logs) ? data.logs : [];
setLogs(prev => {
const combined = [...prev, ...newLines];
// keep only the most recent MAX_LOG_LINES
return combined.slice(-MAX_LOG_LINES);
});
setLoading(false);
}
} catch (e) {
console.error(e);
if (isMounted) setLoading(false);
}
};

// Initial fetch
fetchLogs();
const interval = setInterval(fetchLogs, POLL_INTERVAL_MS);

return () => {
isMounted = false;
clearInterval(interval);
};
}, []);

return (
<div className="relative rounded-lg overflow-hidden border border-gray-700 bg-gray-900/70 backdrop-blur-sm p-4 min-h-[200px]">
{loading && (
<div className="absolute inset-0 flex items-center justify-center bg-gray-900/50">
<Loader2 className="animate-spin w-6 h-6 text-gray-400" />
</div>
)}
<pre className="font-mono text-sm text-gray-200 whitespace-pre-wrap overflow-y-auto max-h-96">
{logs.length > 0 ? logs.join('\n') : 'No logs yet...'}
</pre>
</div>
);
}
176 changes: 82 additions & 94 deletions components/SandboxPreview.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { useState } from 'react';
import { Loader2, ExternalLink, RefreshCw, Terminal } from 'lucide-react';
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/shadcn/tabs';
import SandboxConsoleLogs from '@/components/SandboxConsoleLogs';

interface SandboxPreviewProps {
type: 'vite' | 'nextjs' | 'console';
Expand All @@ -8,117 +10,103 @@ interface SandboxPreviewProps {
sandboxUrl?: string; // Real URL from Vercel Sandbox API
}

export default function SandboxPreview({
type,
export default function SandboxPreview({
type,
output,
isLoading = false,
sandboxUrl
sandboxUrl,
}: SandboxPreviewProps) {
const [showConsole, setShowConsole] = useState(false);
const [iframeKey, setIframeKey] = useState(0);
const [activeTab, setActiveTab] = useState<'preview' | 'console'>('preview');

// Use the real sandbox URL passed from the API
const previewUrl = sandboxUrl || '';

const handleRefresh = () => {
setIframeKey(prev => prev + 1);
};

if (type === 'console') {
return (
<div className="bg-gray-800 rounded-lg p-4 border border-gray-700">
<div className="font-mono text-sm whitespace-pre-wrap text-gray-300">
{output || 'No output yet...'}
</div>
</div>
);
}

return (
<div className="space-y-4">
{/* Preview Controls */}
<div className="flex items-center justify-between bg-gray-800 rounded-lg p-3 border border-gray-700">
<div className="flex items-center gap-3">
<span className="text-sm text-gray-400">
{type === 'vite' ? '⚡ Vite' : '▲ Next.js'} Preview
</span>
{previewUrl ? (
<code className="text-xs bg-gray-900 px-2 py-1 rounded text-blue-400">
{previewUrl}
</code>
) : (
<span className="text-xs text-gray-500">Waiting for sandbox URL...</span>
)}
</div>
<div className="flex items-center gap-2">
<button
onClick={() => setShowConsole(!showConsole)}
className="p-2 hover:bg-gray-700 rounded transition-colors"
title="Toggle console"
>
<Terminal className="w-4 h-4" />
</button>
<button
onClick={handleRefresh}
className="p-2 hover:bg-gray-700 rounded transition-colors"
title="Refresh preview"
>
<RefreshCw className="w-4 h-4" />
</button>
{previewUrl && (
<a
href={previewUrl}
target="_blank"
rel="noopener noreferrer"
className="p-2 hover:bg-gray-700 rounded transition-colors"
title="Open in new tab"
>
<ExternalLink className="w-4 h-4" />
</a>
)}
</div>
</div>
<Tabs defaultValue="preview" className="space-y-4">
<TabsList className="bg-gray-800 rounded-md p-1">
<TabsTrigger value="preview" onClick={() => setActiveTab('preview')} className="flex items-center gap-2">
<Terminal className="w-4 h-4" /> Preview
</TabsTrigger>
<TabsTrigger value="console" onClick={() => setActiveTab('console')} className="flex items-center gap-2">
<Terminal className="w-4 h-4" /> Console Log
</TabsTrigger>
</TabsList>

{/* Main Preview */}
<div className="relative bg-gray-900 rounded-lg overflow-hidden border border-gray-700">
{(isLoading || !previewUrl) && (
<div className="absolute inset-0 bg-gray-900/80 flex items-center justify-center z-10">
<div className="text-center">
<Loader2 className="w-8 h-8 animate-spin mx-auto mb-2" />
<p className="text-sm text-gray-400">
{!previewUrl
? 'Setting up sandbox environment...'
: type === 'vite'
? 'Starting Vite dev server...'
: 'Starting Next.js dev server...'
}
</p>
<TabsContent value="preview">
<div className="space-y-4">
{/* Preview Controls */}
<div className="flex items-center justify-between bg-gray-800 rounded-lg p-3 border border-gray-700">
<div className="flex items-center gap-3">
<span className="text-sm text-gray-400">
{type === 'vite' ? '⚡ Vite' : '▲ Next.js'} Preview
</span>
{previewUrl ? (
<code className="text-xs bg-gray-900 px-2 py-1 rounded text-blue-400">
{previewUrl}
</code>
) : (
<span className="text-xs text-gray-500">Waiting for sandbox URL...</span>
)}
</div>
<div className="flex items-center gap-2">
<button
onClick={handleRefresh}
className="p-2 hover:bg-gray-700 rounded transition-colors"
title="Refresh preview"
>
<RefreshCw className="w-4 h-4" />
</button>
{previewUrl && (
<a
href={previewUrl}
target="_blank"
rel="noopener noreferrer"
className="p-2 hover:bg-gray-700 rounded transition-colors"
title="Open in new tab"
>
<ExternalLink className="w-4 h-4" />
</a>
)}
</div>
</div>
)}

{previewUrl && (
<iframe
key={iframeKey}
src={previewUrl}
className="w-full h-[600px] bg-white"
title={`${type} preview`}
sandbox="allow-scripts allow-same-origin allow-forms"
/>
)}
</div>

{/* Console Output (Toggle) */}
{showConsole && output && (
<div className="bg-gray-800 rounded-lg p-4 border border-gray-700">
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-semibold text-gray-400">Console Output</span>
</div>
<div className="font-mono text-xs whitespace-pre-wrap text-gray-300 max-h-48 overflow-y-auto">
{output}
{/* Main Preview */}
<div className="relative bg-gray-900 rounded-lg overflow-hidden border border-gray-700">
{(isLoading || !previewUrl) && (
<div className="absolute inset-0 bg-gray-900/80 flex items-center justify-center z-10">
<div className="text-center">
<Loader2 className="w-8 h-8 animate-spin mx-auto mb-2" />
<p className="text-sm text-gray-400">
{!previewUrl
? 'Setting up sandbox environment...'
: type === 'vite'
? 'Starting Vite dev server...'
: 'Starting Next.js dev server...'}
</p>
</div>
</div>
)}

{previewUrl && (
<iframe
key={iframeKey}
src={previewUrl}
className="w-full h-[600px] bg-white"
title={`${type} preview`}
sandbox="allow-scripts allow-same-origin allow-forms"
/>
)}
</div>
</div>
)}
</div>
</TabsContent>

<TabsContent value="console">
<SandboxConsoleLogs />
</TabsContent>
</Tabs>
);
}
11 changes: 1 addition & 10 deletions components/app/(home)/sections/ai-readiness/ControlPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export default function ControlPanel({
analysisData,
onReset,
}: ControlPanelProps) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars

const [showAIAnalysis, setShowAIAnalysis] = useState(false); // Reserved for AI analysis feature
const [aiInsights, setAiInsights] = useState<CheckItem[]>([]);
const [isAnalyzingAI, setIsAnalyzingAI] = useState(false);
Expand Down Expand Up @@ -343,15 +343,6 @@ export default function ControlPanel({
}
};

// Utility function available but not used in current render
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const getScoreColor = (score: number) => {
if (score >= 80) return "text-accent-black";
if (score >= 60) return "text-accent-black";
return "text-accent-black";
};


return (
<motion.div
initial={{ opacity: 0, y: 20 }}
Expand Down
5 changes: 4 additions & 1 deletion components/shared/notifications/slack-notification.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";

import Image from "next/image";
import { useEffect, useState } from "react";

export default function SlackNotification({
Expand Down Expand Up @@ -32,10 +33,12 @@ export default function SlackNotification({
>
<div className="flex flex-row items-center max-w-xs cursor-pointer">
<div className="flex min-w-16 mr-2 mt-1">
<img
<Image
className="w-16 h-16"
src="/images/slack_logo_icon.png"
alt="Slack Logo"
width={64}
height={64}
/>
</div>
<div className="flex flex-col">
Expand Down
Loading
Loading