feat(electrobun): project wizard phases 1-5 (WIP)
- sanitize.ts: input sanitization (trim, control chars, path traversal) - provider-scanner.ts: detect Claude/Codex/Ollama/Gemini availability - model-fetcher.ts: live model lists from 4 provider APIs - ModelConfigPanel.svelte: per-provider config (thinking, effort, sandbox, temperature) - WizardStep1-3.svelte: split wizard into composable steps - CustomDropdown/Checkbox/Radio: themed UI components - provider-handlers.ts: provider.scan + provider.models RPC - Wire providers into wizard step 3 (live detection + model lists) - Replace native selects in 5 settings panels with CustomDropdown
This commit is contained in:
parent
b7fc3a0f9b
commit
d4014a193d
25 changed files with 2112 additions and 759 deletions
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* Git RPC handlers — branch listing, clone operations.
|
||||
* Git RPC handlers — branch listing, clone, probe, and template scaffolding.
|
||||
*/
|
||||
|
||||
import path from "path";
|
||||
|
|
@ -98,5 +98,118 @@ export function createGitHandlers() {
|
|||
});
|
||||
});
|
||||
},
|
||||
|
||||
"git.probe": async ({ url }: { url: string }) => {
|
||||
if (!url || (!url.includes("/") && !url.includes(":"))) {
|
||||
return { ok: false, branches: [], defaultBranch: '', error: "Invalid URL" };
|
||||
}
|
||||
|
||||
return new Promise<{
|
||||
ok: boolean; branches: string[]; defaultBranch: string; error?: string;
|
||||
}>((resolve) => {
|
||||
const proc = spawn("git", ["ls-remote", "--heads", "--symref", url], {
|
||||
stdio: "pipe",
|
||||
timeout: 15_000,
|
||||
});
|
||||
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
proc.stdout?.on("data", (chunk: Buffer) => { stdout += chunk.toString(); });
|
||||
proc.stderr?.on("data", (chunk: Buffer) => { stderr += chunk.toString(); });
|
||||
|
||||
proc.on("close", (code) => {
|
||||
if (code !== 0) {
|
||||
resolve({ ok: false, branches: [], defaultBranch: '', error: stderr.trim() || 'Probe failed' });
|
||||
return;
|
||||
}
|
||||
const branches: string[] = [];
|
||||
let defaultBranch = 'main';
|
||||
|
||||
for (const line of stdout.split("\n")) {
|
||||
// Parse symref for HEAD
|
||||
const symMatch = line.match(/^ref: refs\/heads\/(\S+)\s+HEAD/);
|
||||
if (symMatch) { defaultBranch = symMatch[1]; continue; }
|
||||
|
||||
// Parse branch refs
|
||||
const refMatch = line.match(/\trefs\/heads\/(.+)$/);
|
||||
if (refMatch && !branches.includes(refMatch[1])) {
|
||||
branches.push(refMatch[1]);
|
||||
}
|
||||
}
|
||||
resolve({ ok: true, branches, defaultBranch });
|
||||
});
|
||||
|
||||
proc.on("error", (err) => {
|
||||
resolve({ ok: false, branches: [], defaultBranch: '', error: err.message });
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
"project.createFromTemplate": async ({
|
||||
templateId,
|
||||
targetDir,
|
||||
projectName,
|
||||
}: {
|
||||
templateId: string;
|
||||
targetDir: string;
|
||||
projectName: string;
|
||||
}) => {
|
||||
const resolved = path.resolve(targetDir.replace(/^~/, process.env.HOME ?? ""));
|
||||
const projectDir = path.join(resolved, projectName);
|
||||
|
||||
// Don't overwrite existing directory
|
||||
try {
|
||||
fs.statSync(projectDir);
|
||||
return { ok: false, path: '', error: "Directory already exists" };
|
||||
} catch {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
fs.mkdirSync(projectDir, { recursive: true });
|
||||
|
||||
const scaffolds: Record<string, Record<string, string>> = {
|
||||
blank: {
|
||||
'README.md': `# ${projectName}\n`,
|
||||
'.gitignore': 'node_modules/\ndist/\n.env\n',
|
||||
},
|
||||
'web-app': {
|
||||
'index.html': `<!DOCTYPE html>\n<html lang="en">\n<head>\n <meta charset="UTF-8" />\n <meta name="viewport" content="width=device-width, initial-scale=1.0" />\n <title>${projectName}</title>\n <link rel="stylesheet" href="style.css" />\n</head>\n<body>\n <h1>${projectName}</h1>\n <script src="main.js"></script>\n</body>\n</html>\n`,
|
||||
'style.css': `body {\n font-family: system-ui, sans-serif;\n margin: 2rem;\n}\n`,
|
||||
'main.js': `console.log('${projectName} loaded');\n`,
|
||||
'README.md': `# ${projectName}\n\nA web application.\n`,
|
||||
'.gitignore': 'node_modules/\ndist/\n.env\n',
|
||||
},
|
||||
'api-server': {
|
||||
'index.ts': `const server = Bun.serve({\n port: 3000,\n fetch(req) {\n const url = new URL(req.url);\n if (url.pathname === '/health') {\n return Response.json({ status: 'ok' });\n }\n return Response.json({ message: 'Hello from ${projectName}' });\n },\n});\nconsole.log(\`Server running on \${server.url}\`);\n`,
|
||||
'README.md': `# ${projectName}\n\nA Bun HTTP API server.\n\n## Run\n\n\`\`\`bash\nbun run index.ts\n\`\`\`\n`,
|
||||
'.gitignore': 'node_modules/\ndist/\n.env\n',
|
||||
'package.json': `{\n "name": "${projectName}",\n "version": "0.1.0",\n "scripts": {\n "start": "bun run index.ts",\n "dev": "bun --watch index.ts"\n }\n}\n`,
|
||||
},
|
||||
'cli-tool': {
|
||||
'cli.ts': `#!/usr/bin/env bun\nconst args = process.argv.slice(2);\nif (args.includes('--help') || args.includes('-h')) {\n console.log('Usage: ${projectName} [options]');\n console.log(' --help, -h Show this help');\n console.log(' --version Show version');\n process.exit(0);\n}\nif (args.includes('--version')) {\n console.log('${projectName} 0.1.0');\n process.exit(0);\n}\nconsole.log('Hello from ${projectName}!');\n`,
|
||||
'README.md': `# ${projectName}\n\nA command-line tool.\n\n## Run\n\n\`\`\`bash\nbun run cli.ts --help\n\`\`\`\n`,
|
||||
'.gitignore': 'node_modules/\ndist/\n.env\n',
|
||||
},
|
||||
};
|
||||
|
||||
const files = scaffolds[templateId] ?? scaffolds['blank'];
|
||||
for (const [name, content] of Object.entries(files)) {
|
||||
fs.writeFileSync(path.join(projectDir, name), content);
|
||||
}
|
||||
|
||||
// Initialize git repo
|
||||
try {
|
||||
execSync('git init', { cwd: projectDir, timeout: 5000 });
|
||||
} catch {
|
||||
// Non-fatal — project created without git
|
||||
}
|
||||
|
||||
return { ok: true, path: projectDir };
|
||||
} catch (err) {
|
||||
const error = err instanceof Error ? err.message : String(err);
|
||||
return { ok: false, path: '', error };
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
30
ui-electrobun/src/bun/handlers/provider-handlers.ts
Normal file
30
ui-electrobun/src/bun/handlers/provider-handlers.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* Provider RPC handlers — scanning + model fetching.
|
||||
*/
|
||||
|
||||
import { scanAllProviders } from '../provider-scanner.ts';
|
||||
import { fetchModelsForProvider } from '../model-fetcher.ts';
|
||||
|
||||
export function createProviderHandlers() {
|
||||
return {
|
||||
'provider.scan': async () => {
|
||||
try {
|
||||
const providers = await scanAllProviders();
|
||||
return { providers };
|
||||
} catch (err) {
|
||||
console.error('[provider.scan]', err);
|
||||
return { providers: [] };
|
||||
}
|
||||
},
|
||||
|
||||
'provider.models': async ({ provider }: { provider: string }) => {
|
||||
try {
|
||||
const models = await fetchModelsForProvider(provider);
|
||||
return { models };
|
||||
} catch (err) {
|
||||
console.error('[provider.models]', err);
|
||||
return { models: [] };
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue