create-vision-app
Version:
Create a computer vision endpoint in seconds
347 lines (345 loc) • 14.4 kB
JavaScript
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);