gensx
Version:
`GenSX command line tools.
270 lines • 13.7 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import process from "node:process";
import { fileURLToPath } from "node:url";
import { Box, Text, useApp } from "ink";
import SelectInput from "ink-select-input";
import Spinner from "ink-spinner";
import TextInput from "ink-text-input";
import { useCallback, useEffect, useState } from "react";
import { ErrorMessage } from "../components/ErrorMessage.js";
import { LoadingSpinner } from "../components/LoadingSpinner.js";
import { readConfig } from "../utils/config.js";
import { exec } from "../utils/exec.js";
import { saveProjectConfig } from "../utils/project-config.js";
import { login } from "./login.js";
const TEMPLATE_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), process.env.DENO_BINARY ? "src/templates/projects" : "../templates/projects");
export function NewProjectUI({ projectPath, options }) {
const { exit } = useApp();
const [phase, setPhase] = useState("initial");
const [error, setError] = useState(null);
const [description, setDescription] = useState("");
const [_selectedAssistants, setSelectedAssistants] = useState([]);
const [hasCopiedFiles, setHasCopiedFiles] = useState(false);
const [hasInstalledDeps, setHasInstalledDeps] = useState(false);
const [isInstallingAssistants, setIsInstallingAssistants] = useState(false);
const handleError = useCallback((err) => {
const message = err instanceof Error ? err.message : String(err);
setError(message);
setPhase("error");
setTimeout(() => {
exit();
}, 100);
}, [exit]);
const handleDescriptionSubmit = useCallback((value) => {
try {
const trimmed = value.trim();
setDescription(trimmed || "My GenSX Project");
setPhase("copyFiles");
}
catch (err) {
handleError(err);
}
}, [handleError]);
const handleAssistantSelect = useCallback((assistants) => {
setSelectedAssistants(assistants);
setIsInstallingAssistants(true);
// Filter out "all" and "none" from the assistants array
const packagesToInstall = assistants.filter((pkg) => pkg !== "all" && pkg !== "none");
// Run each assistant installation command using npx
const installPromises = packagesToInstall.map((assistantPackage) => exec(`npx ${assistantPackage}`, { cwd: projectPath }).catch(handleError));
Promise.all(installPromises)
.then(() => {
setIsInstallingAssistants(false);
setPhase("done");
})
.catch(handleError);
}, [handleError, projectPath]);
useEffect(() => {
async function initialize() {
try {
// Check if user has completed first-time setup
const { state } = await readConfig();
if (!state.hasCompletedFirstTimeSetup && !options.skipLogin) {
setPhase("login");
return;
}
// If description is provided in options, skip the createProject phase
if (options.description) {
setDescription(options.description);
setPhase("copyFiles");
}
else {
setPhase("createProject");
}
}
catch (err) {
handleError(err);
}
}
void initialize();
}, [handleError, options.skipLogin, options.description]);
useEffect(() => {
async function copyFiles() {
if (phase === "copyFiles" && !hasCopiedFiles) {
try {
await copyTemplateFiles("ts", projectPath);
// Save gensx.yaml config
const projectName = path.basename(projectPath);
await saveProjectConfig({
projectName,
description: description || "My GenSX Project",
}, projectPath);
setHasCopiedFiles(true);
setPhase("installDeps");
}
catch (err) {
handleError(err);
}
}
}
void copyFiles();
}, [phase, projectPath, handleError, hasCopiedFiles, description]);
useEffect(() => {
async function installDependencies() {
if (phase === "installDeps" && !hasInstalledDeps) {
try {
const template = await loadTemplate("ts");
if (template.dependencies.length > 0) {
await exec(`npm install ${template.dependencies.join(" ")}`, {
cwd: projectPath,
});
}
if (template.devDependencies.length > 0) {
await exec(`npm install -D ${template.devDependencies.join(" ")}`, {
cwd: projectPath,
});
}
setHasInstalledDeps(true);
if (options.skipIdeRules) {
setPhase("done");
setTimeout(() => {
exit();
}, 100);
}
else {
setPhase("selectAssistants");
}
}
catch (err) {
handleError(err);
setTimeout(() => {
exit();
}, 100);
}
}
}
void installDependencies();
}, [phase, handleError, hasInstalledDeps, options.skipIdeRules]);
if (error) {
return _jsx(ErrorMessage, { message: error });
}
if (phase === "login") {
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: _jsx(Text, { color: "yellow", children: "Welcome to GenSX! Let's get you set up first." }) }), _jsx(LoginUI, { onComplete: () => {
setPhase("createProject");
} })] }));
}
if (phase === "createProject") {
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { color: "blue", children: "\u279C" }), " Enter a project description (or press enter to skip):"] }), _jsx(TextInput, { value: description, onChange: setDescription, onSubmit: handleDescriptionSubmit })] }));
}
if (phase === "copyFiles") {
return _jsx(LoadingSpinner, { message: "Creating project..." });
}
if (phase === "installDeps") {
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { color: "green", bold: true, children: "\u2714" }), " ", "Created project"] }), _jsx(LoadingSpinner, { message: "Installing dependencies..." })] }));
}
if (phase === "selectAssistants") {
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { color: "green", bold: true, children: "\u2714" }), " ", "Created project"] }), _jsxs(Text, { children: [_jsx(Text, { color: "green", bold: true, children: "\u2714" }), " ", "Installed dependencies"] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { color: "blue", children: "\u279C" }), " Select AI assistants to integrate:"] }), isInstallingAssistants ? (_jsx(LoadingSpinner, { message: "Installing AI assistant integrations..." })) : (_jsx(AiAssistantSelector, { onSelect: handleAssistantSelect }))] })] }));
}
if (phase === "done") {
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { color: "green", bold: true, children: "\u2714" }), " ", "Created project"] }), _jsxs(Text, { children: [_jsx(Text, { color: "green", bold: true, children: "\u2714" }), " ", "Installed dependencies"] }), _jsxs(Text, { children: [_jsx(Text, { color: "green", bold: true, children: "\u2714" }), " ", "Installed ", _selectedAssistants.length, " AI assistant integration", _selectedAssistants.length !== 1 ? "s" : ""] }), _jsx(Box, { children: _jsxs(Text, { children: [_jsx(Text, { color: "green", bold: true, children: "\u2714" }), " ", "Successfully created GenSX project in", " ", _jsx(Text, { color: "cyan", children: projectPath })] }) }), _jsxs(Box, { marginTop: 2, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "NEXT STEPS:" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "1. Open the project directory:" }), projectPath !== "." && (_jsxs(Text, { children: [" ", _jsxs(Text, { color: "cyan", children: ["cd ", projectPath] })] }))] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { children: "2. Choose what you want to do:" }), _jsxs(Box, { marginTop: 1, marginLeft: 2, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "DEPLOY THE PROJECT" }), _jsx(Text, { children: _jsx(Text, { color: "cyan", children: "OPENAI_API_KEY=your_api_key npm run deploy" }) })] }), _jsxs(Box, { marginTop: 1, marginLeft: 2, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "RUN LOCALLY" }), _jsx(Text, { children: _jsx(Text, { color: "cyan", children: "OPENAI_API_KEY=your_api_key npm run dev" }) })] }), _jsxs(Box, { marginTop: 1, marginLeft: 2, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "START API SERVER" }), _jsx(Text, { children: _jsx(Text, { color: "cyan", children: "OPENAI_API_KEY=your_api_key npm run start" }) })] })] }), _jsx(Box, { marginTop: 2, children: _jsxs(Text, { children: ["Open ", _jsx(Text, { color: "cyan", children: "src/workflows.tsx" }), " to start building your workflows."] }) })] })] }));
}
return null;
}
function LoginUI({ onComplete }) {
const { skipped } = login();
useEffect(() => {
if (skipped) {
onComplete();
}
}, [skipped, onComplete]);
return (_jsx(Box, { children: _jsxs(Text, { children: [_jsx(Spinner, {}), " Logging in..."] }) }));
}
function AiAssistantSelector({ onSelect }) {
const aiAssistantOptions = [
{
name: "claude",
value: "@gensx/claude-md",
message: "Claude AI",
hint: "Adds CLAUDE.md for Anthropic Claude integration",
},
{
name: "cursor",
value: "@gensx/cursor-rules",
message: "Cursor",
hint: "Adds Cursor IDE integration rules",
},
{
name: "cline",
value: "@gensx/cline-rules",
message: "Cline",
hint: "Adds Cline VS Code extension integration rules",
},
{
name: "windsurf",
value: "@gensx/windsurf-rules",
message: "Windsurf",
hint: "Adds Windsurf Cascade AI integration rules",
},
{
name: "all",
value: "all",
message: "All",
hint: "Adds all AI assistants",
},
{
name: "none",
value: "none",
message: "None",
hint: "No AI assistants will be installed",
},
];
const items = aiAssistantOptions.map((option) => ({
label: `${option.message} (${option.hint})`,
value: option.value,
}));
return (_jsx(SelectInput, { items: items, onSelect: (item) => {
const selection = item.value;
if (selection === "none") {
onSelect([]);
}
else if (selection === "all") {
// For "all", get all the actual package values
const allPackages = aiAssistantOptions
.filter((opt) => opt.value !== "all" && opt.value !== "none")
.map((opt) => opt.value);
onSelect(allPackages);
}
else {
onSelect([selection]);
}
} }));
}
async function loadTemplate(_templateName) {
const templatePath = path.join(TEMPLATE_DIR, "typescript");
const templateConfigPath = path.join(templatePath, "template.json");
try {
const configContent = await readFile(templateConfigPath, "utf-8");
const template = JSON.parse(configContent);
return template;
}
catch {
throw new Error(`Template "typescript" not found or invalid.`);
}
}
async function copyTemplateFiles(_templateName, targetPath) {
const templatePath = path.join(TEMPLATE_DIR, "typescript");
async function copyDir(currentPath, targetBase) {
const entries = await readdir(currentPath, { withFileTypes: true });
for (const entry of entries) {
const sourcePath = path.join(currentPath, entry.name);
const targetFilePath = path
.join(targetBase, path.relative(templatePath, sourcePath))
.replace(/\.template$/, "");
if (entry.name === "template.json")
continue;
if (entry.isDirectory()) {
await mkdir(targetFilePath, { recursive: true });
await copyDir(sourcePath, targetBase);
}
else {
const content = await readFile(sourcePath, "utf-8");
await mkdir(path.dirname(targetFilePath), { recursive: true });
await writeFile(targetFilePath, content);
}
}
}
await copyDir(templatePath, targetPath);
}
//# sourceMappingURL=new.js.map