UNPKG

create-vision-app

Version:

Create a computer vision endpoint in seconds

347 lines (345 loc) 14.4 kB
#!/usr/bin/env node var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); // src/index.js var import_prompts = require("@clack/prompts"); var import_chalk = __toESM(require("chalk"), 1); var import_gradient_string = __toESM(require("gradient-string"), 1); var import_figlet = __toESM(require("figlet"), 1); var import_os = require("os"); var import_path = require("path"); var import_fs = require("fs"); var import_child_process = require("child_process"); var import_util = require("util"); var execAsync = (0, import_util.promisify)(import_child_process.exec); var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); var APP_URL = "https://app.roboflow.com"; function getConfigPath() { return process.platform === "win32" ? (0, import_path.join)((0, import_os.homedir)(), "roboflow", "config.json") : (0, import_path.join)((0, import_os.homedir)(), ".config", "roboflow", "config.json"); } function loadRoboflowConfig() { const configPath = getConfigPath(); if ((0, import_fs.existsSync)(configPath)) { try { return JSON.parse((0, import_fs.readFileSync)(configPath, "utf8")); } catch (e) { return null; } } return null; } function getRoboflowApiKey() { if (process.env.ROBOFLOW_API_KEY) { return { apiKey: process.env.ROBOFLOW_API_KEY, source: "env" }; } const config = loadRoboflowConfig(); if (config?.workspaces) { const defaultWorkspaceUrl = config.RF_WORKSPACE; const workspaces = Object.values(config.workspaces); if (workspaces.length > 0) { const workspace = workspaces.find((w) => w.url === defaultWorkspaceUrl) || workspaces[0]; return { apiKey: workspace.apiKey, workspace, allWorkspaces: workspaces, source: "config" }; } } return null; } function saveRoboflowConfig(workspacesData) { const configPath = getConfigPath(); const configDir = (0, import_path.dirname)(configPath); if (!(0, import_fs.existsSync)(configDir)) { (0, import_fs.mkdirSync)(configDir, { recursive: true }); } const workspaceIds = Object.keys(workspacesData); const defaultWorkspaceId = workspaceIds[0]; const defaultWorkspace = workspacesData[defaultWorkspaceId]; const config = { workspaces: workspacesData, RF_WORKSPACE: defaultWorkspace.url }; (0, import_fs.writeFileSync)(configPath, JSON.stringify(config, null, 2)); } function openUrl(url) { const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open"; (0, import_child_process.exec)(`${command} ${url}`); } async function authenticateRoboflow() { (0, import_prompts.note)( import_chalk.default.yellow("You need a Roboflow API Key to continue.\n") + import_chalk.default.dim("Opening browser to get your authentication token...\n") + import_chalk.default.dim("If the browser doesn't open, manually visit:\n") + import_chalk.default.cyan(`${APP_URL}/auth-cli`), "Authentication Required" ); await sleep(1e3); openUrl(`${APP_URL}/auth-cli`); (0, import_prompts.note)( import_chalk.default.dim("After authorizing, you'll see a message:\n") + import_chalk.default.cyan('"Copy the code below and paste it into your CLI"\n\n') + import_chalk.default.dim("Copy that token and paste it here."), "Next Steps" ); const token = await (0, import_prompts.text)({ message: "Paste your authentication token", placeholder: "00000000-0000-0000-0000-000000000000", validate: (value) => { if (!value) return "Token is required"; const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; if (!uuidRegex.test(value)) return "Please enter a valid UUID format"; } }); if (typeof token === "symbol") { (0, import_prompts.outro)(import_chalk.default.red("Cancelled")); process.exit(0); } const s = (0, import_prompts.spinner)(); s.start("Authenticating..."); try { const response = await fetch(`${APP_URL}/query/cliAuthToken/${token}`); if (!response.ok) { s.stop("Authentication failed"); (0, import_prompts.note)(import_chalk.default.red("Invalid authentication token. Please try again."), "Error"); process.exit(1); } const workspacesData = await response.json(); if (!workspacesData || Object.keys(workspacesData).length === 0) { s.stop("Authentication failed"); (0, import_prompts.note)(import_chalk.default.red("No workspaces found for this token."), "Error"); process.exit(1); } saveRoboflowConfig(workspacesData); s.stop("Authenticated successfully! \u2713"); const workspaces = Object.values(workspacesData); const defaultWorkspace = workspaces[0]; return { apiKey: defaultWorkspace.apiKey, workspace: defaultWorkspace, allWorkspaces: workspaces }; } catch (error) { s.stop("Authentication failed"); (0, import_prompts.note)(import_chalk.default.red(`Error: ${error.message}`), "Error"); process.exit(1); } } async function selectWorkspace(workspaces, currentWorkspace) { const options = workspaces.map((w) => ({ value: w.url, label: w.name, hint: w.url === currentWorkspace.url ? "(default)" : "" })); options.push({ value: "_add_new", label: "Create or Link a new Workspace" }); const selected = await (0, import_prompts.select)({ message: "Select Roboflow Workspace", options, initialValue: currentWorkspace.url, maxItems: 10 }); if (typeof selected === "symbol") { (0, import_prompts.outro)(import_chalk.default.red("Cancelled")); process.exit(0); } if (selected === "_add_new") { const newAuth = await authenticateRoboflow(); return await selectWorkspace(newAuth.allWorkspaces, newAuth.workspace); } return workspaces.find((w) => w.url === selected); } function getDefaultProjectName(objects, objectChoice) { const predefinedNames = { "dog, cat": "animal-vision-app", "car, cyclist, pedestrian, traffic light, cone, sign": "autonomous-vehicle-app", "car, license plate": "license-plate-app", "bone, fracture": "medical-vision-app", "logo, address, date, paragraph, signature": "document-vision-app", "house, car, pool, solar panel": "aerial-vision-app", "player, ball, referee, goal": "sports-vision-app" }; if (predefinedNames[objects]) { return predefinedNames[objects]; } const firstObject = objects.split(",")[0].trim().toLowerCase().replace(/\s+/g, "-"); return `${firstObject}-vision-app`; } async function showSplash() { console.clear(); const title = import_figlet.default.textSync("Create Vision App", { font: "Standard", horizontalLayout: "default" }); console.log(import_gradient_string.default.pastel.multiline(title)); console.log(import_chalk.default.dim(" Create a computer vision endpoint in seconds")); console.log(import_chalk.default.dim(" Powered by Roboflow and Segment Anything 3\n")); await sleep(800); } async function main() { await showSplash(); (0, import_prompts.intro)(import_chalk.default.inverse(" create-vision-app ")); let auth = getRoboflowApiKey(); if (!auth) { auth = await authenticateRoboflow(); } const workspace = await selectWorkspace(auth.allWorkspaces, auth.workspace); const suggestedObjects = [ { value: "_custom", label: "Enter custom objects...", hint: "Type your own" }, { value: "dog, cat", label: "Dog, Cat", hint: "Animals" }, { value: "car, cyclist, pedestrian, traffic light, cone, sign", label: "Car, Cyclist, Pedestrian, Traffic Light, Cone, Sign", hint: "Autonomous Vehicles" }, { value: "car, license plate", label: "Car, License Plate", hint: "Transportation" }, { value: "bone, fracture", label: "Bone, Fracture", hint: "Medical" }, { value: "logo, address, date, paragraph, signature", label: "Logo, Address, Date, Paragraph, Signature", hint: "Document" }, { value: "house, car, pool, solar panel", label: "House, Car, Pool, Solar Panel", hint: "Aerial" }, { value: "player, ball, referee, goal", label: "Player, Ball, Referee, Goal", hint: "Sports" } ]; const objectChoice = await (0, import_prompts.select)({ message: "What objects are you looking for?", options: suggestedObjects }); if (typeof objectChoice === "symbol") { (0, import_prompts.outro)(import_chalk.default.red("Cancelled")); process.exit(0); } let objects; if (objectChoice === "_custom") { objects = await (0, import_prompts.text)({ message: "Enter objects to detect", placeholder: "dogs, cats, birds", validate: (value) => { if (!value || value.trim() === "") return "Please enter at least one object"; } }); if (typeof objects === "symbol") { (0, import_prompts.outro)(import_chalk.default.red("Cancelled")); process.exit(0); } } else { objects = objectChoice; } if (typeof objects === "symbol") { (0, import_prompts.outro)(import_chalk.default.red("Cancelled")); process.exit(0); } const activeLearning = await (0, import_prompts.select)({ message: "Enable active learning so your model improves as it sees more?", options: [ { value: "yes", label: "Yes" }, { value: "no", label: "No" }, { value: "learn", label: "Learn more about active learning" } ], initialValue: "yes" }); if (typeof activeLearning === "symbol") { (0, import_prompts.outro)(import_chalk.default.red("Cancelled")); process.exit(0); } if (activeLearning === "learn") { const learnMoreUrl = "https://docs.roboflow.com"; (0, import_prompts.note)( import_chalk.default.dim("Opening documentation in your browser...\n") + import_chalk.default.dim("If the browser doesn't open, visit:\n") + import_chalk.default.cyan(learnMoreUrl), "Active Learning" ); openUrl(learnMoreUrl); await sleep(1e3); const activeLearningRetry = await (0, import_prompts.confirm)({ message: "Enable active learning?", initialValue: true }); if (typeof activeLearningRetry === "symbol") { (0, import_prompts.outro)(import_chalk.default.red("Cancelled")); process.exit(0); } } const enableActiveLearning = activeLearning === "yes"; const s = (0, import_prompts.spinner)(); s.start("Deploying your vision endpoint"); await sleep(2e3); s.message("Configuring workflow"); await sleep(1500); s.message("Generating endpoint"); await sleep(1e3); s.stop("Deployed successfully! \u{1F389}"); const defaultProjectName = getDefaultProjectName(objects, objectChoice); const endpoint = `https://serverless.roboflow.com/${workspace.url}/workflows/${defaultProjectName}`; console.log(""); console.log(import_chalk.default.green("\u2713"), "Your app is live at:", import_chalk.default.cyan(endpoint)); console.log(""); console.log(import_chalk.default.dim("Try it like this:")); console.log(import_chalk.default.yellow(` curl -X POST ${endpoint} \\`)); console.log(import_chalk.default.yellow(` -H "Content-Type: application/json" \\`)); console.log(import_chalk.default.yellow(` -H "Authorization: Bearer ${workspace.apiKey}" \\`)); console.log(import_chalk.default.yellow(` -d '{"image_url": "https://example.com/image.jpg"}'`)); console.log(""); const setupScaffolding = await (0, import_prompts.confirm)({ message: "Setup app scaffolding?", initialValue: true }); if (typeof setupScaffolding === "symbol" || !setupScaffolding) { (0, import_prompts.outro)(import_chalk.default.green("All done! Happy building! \u{1F680}")); process.exit(0); } const language = await (0, import_prompts.select)({ message: "Which language?", options: [ { value: "python", label: "Python" }, { value: "nodejs", label: "Node.js" }, { value: "webapp", label: "Web App" } ] }); if (typeof language === "symbol") { (0, import_prompts.outro)(import_chalk.default.red("Cancelled")); process.exit(0); } const projectName = await (0, import_prompts.text)({ message: "What should it be called?", placeholder: defaultProjectName, defaultValue: defaultProjectName }); if (typeof projectName === "symbol") { (0, import_prompts.outro)(import_chalk.default.red("Cancelled")); process.exit(0); } const finalName = projectName || defaultProjectName; const s2 = (0, import_prompts.spinner)(); s2.start("Creating project structure"); await sleep(1500); s2.message("Installing dependencies"); await sleep(1500); s2.message("Generating sample code"); await sleep(1e3); s2.stop("Project created! \u{1F4E6}"); console.log(""); console.log(import_chalk.default.green("Great, we've setup a sample app for you!")); if (language === "python") { console.log(import_chalk.default.dim(`Run it with: ${import_chalk.default.cyan(`python ./${finalName}/main.py`)}`)); } else if (language === "nodejs") { console.log(import_chalk.default.dim(`Run it with: ${import_chalk.default.cyan(`node ./${finalName}/index.js`)}`)); } else { console.log(import_chalk.default.dim(`Run it with: ${import_chalk.default.cyan(`cd ./${finalName} && npm run dev`)}`)); } (0, import_prompts.outro)(import_chalk.default.green("Happy building! \u{1F680}")); } main().catch(console.error);