Effortlessly build beautiful command-line apps - an opinionated, pre-styled wrapper around @clack/core
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Complex prompt orchestration with grouped prompts, task execution, and shared state management for sophisticated CLI applications.
Execute multiple related prompts as a cohesive group with shared state and centralized cancellation handling.
/**
* Executes a group of prompts with shared state and cancellation handling
* @param prompts - Object mapping prompt names to prompt functions
* @param opts - Optional configuration for the group
* @returns Promise resolving to object with all prompt results
*/
function group<T>(
prompts: PromptGroup<T>,
opts?: PromptGroupOptions<T>
): Promise<Prettify<PromptGroupAwaitedReturn<T>>>;
type PromptGroup<T> = {
[P in keyof T]: (opts: {
results: Prettify<Partial<PromptGroupAwaitedReturn<Omit<T, P>>>>;
}) => undefined | Promise<T[P] | undefined>;
};
interface PromptGroupOptions<T> {
/** Callback invoked when any prompt in the group is cancelled */
onCancel?: (opts: { results: Prettify<Partial<PromptGroupAwaitedReturn<T>>> }) => void;
}
type PromptGroupAwaitedReturn<T> = {
[P in keyof T]: Exclude<Awaited<T[P]>, symbol>;
};
type Prettify<T> = {
[P in keyof T]: T[P];
} & {};Usage Examples:
import * as p from "@clack/prompts";
// Basic grouped prompts
const results = await p.group({
name: () => p.text({ message: "What's your name?" }),
email: () => p.text({
message: "What's your email?",
validate: (value) => {
if (!value.includes("@")) return "Please enter a valid email";
}
}),
framework: () => p.select({
message: "Choose a framework:",
options: [
{ value: "react", label: "React" },
{ value: "vue", label: "Vue" },
{ value: "svelte", label: "Svelte" },
],
}),
});
console.log(results.name, results.email, results.framework);
// Conditional prompts based on previous results
const config = await p.group({
projectType: () => p.select({
message: "Project type:",
options: [
{ value: "web", label: "Web Application" },
{ value: "api", label: "API Server" },
{ value: "cli", label: "CLI Tool" },
],
}),
// This prompt only appears for web applications
framework: ({ results }) => {
if (results.projectType !== "web") return undefined;
return p.select({
message: "Choose web framework:",
options: [
{ value: "react", label: "React" },
{ value: "vue", label: "Vue" },
{ value: "svelte", label: "Svelte" },
],
});
},
// Database prompt uses previous selections
database: ({ results }) => p.select({
message: `Choose database for ${results.projectType}:`,
options: [
{ value: "postgres", label: "PostgreSQL" },
{ value: "mysql", label: "MySQL" },
{ value: "sqlite", label: "SQLite" },
],
}),
});
// Framework is only set for web projects
if (config.framework) {
console.log(`Web app with ${config.framework} and ${config.database}`);
}import * as p from "@clack/prompts";
const results = await p.group(
{
name: () => p.text({ message: "Your name:" }),
age: () => p.text({ message: "Your age:" }),
confirm: ({ results }) => p.confirm({
message: `Create profile for ${results.name}, age ${results.age}?`,
}),
},
{
// Handle cancellation for the entire group
onCancel: ({ results }) => {
p.cancel("Profile creation cancelled");
console.log("Partial results:", results);
process.exit(0);
},
}
);
console.log("Profile created:", results);Execute multiple tasks with visual progress indicators and error handling.
/**
* Executes multiple tasks sequentially with spinner progress indicators
* @param tasks - Array of task definitions to execute
* @returns Promise that resolves when all enabled tasks complete
*/
function tasks(tasks: Task[]): Promise<void>;
type Task = {
/** Display title for the task */
title: string;
/** Task function that performs the work */
task: (message: (string: string) => void) => string | Promise<string> | void | Promise<void>;
/** Whether this task should be executed (defaults to true) */
enabled?: boolean;
};Usage Examples:
import { tasks } from "@clack/prompts";
// Basic task execution
await tasks([
{
title: "Installing dependencies",
task: async () => {
// Simulate package installation
await new Promise(resolve => setTimeout(resolve, 2000));
return "Dependencies installed";
},
},
{
title: "Building project",
task: async () => {
// Simulate build process
await new Promise(resolve => setTimeout(resolve, 1500));
return "Build complete";
},
},
{
title: "Running tests",
task: async () => {
await new Promise(resolve => setTimeout(resolve, 1000));
return "All tests passed";
},
},
]);
// Tasks with dynamic messages
await tasks([
{
title: "Downloading files",
task: async (message) => {
message("Connecting to server...");
await new Promise(resolve => setTimeout(resolve, 500));
message("Downloading package (1/3)...");
await new Promise(resolve => setTimeout(resolve, 800));
message("Downloading package (2/3)...");
await new Promise(resolve => setTimeout(resolve, 600));
message("Downloading package (3/3)...");
await new Promise(resolve => setTimeout(resolve, 400));
return "Download complete";
},
},
]);
// Conditional task execution
const runTests = true;
const deployToProduction = false;
await tasks([
{
title: "Building application",
task: () => "Build successful",
},
{
title: "Running test suite",
enabled: runTests,
task: async () => {
await new Promise(resolve => setTimeout(resolve, 1000));
return "Tests passed";
},
},
{
title: "Deploying to production",
enabled: deployToProduction,
task: () => "Deployed successfully",
},
]);import * as p from "@clack/prompts";
p.intro("Project Setup Wizard");
// Step 1: Gather project information
const projectInfo = await p.group(
{
name: () => p.text({
message: "Project name:",
placeholder: "my-awesome-project",
validate: (value) => {
if (!value) return "Project name is required";
if (!/^[a-z0-9-]+$/.test(value)) {
return "Use lowercase letters, numbers, and hyphens only";
}
}
}),
type: () => p.select({
message: "Project type:",
options: [
{ value: "webapp", label: "Web Application", hint: "React/Vue/Svelte" },
{ value: "api", label: "API Server", hint: "Express/Fastify" },
{ value: "cli", label: "CLI Tool", hint: "Command-line utility" },
],
}),
features: ({ results }) => {
const baseFeatures = [
{ value: "typescript", label: "TypeScript", hint: "recommended" },
{ value: "testing", label: "Testing Setup" },
{ value: "linting", label: "ESLint + Prettier" },
];
if (results.type === "webapp") {
baseFeatures.push(
{ value: "routing", label: "Client-side Routing" },
{ value: "styling", label: "Styled Components" }
);
} else if (results.type === "api") {
baseFeatures.push(
{ value: "database", label: "Database Integration" },
{ value: "auth", label: "Authentication" }
);
}
return p.multiselect({
message: "Select features:",
options: baseFeatures,
initialValues: ["typescript"],
});
},
},
{
onCancel: () => {
p.cancel("Project setup cancelled");
process.exit(0);
},
}
);
// Step 2: Confirm configuration
p.note(
`Project: ${projectInfo.name}
Type: ${projectInfo.type}
Features: ${projectInfo.features.join(", ")}`,
"Configuration Summary"
);
const confirmed = await p.confirm({
message: "Proceed with this configuration?",
});
if (!confirmed) {
p.cancel("Setup cancelled");
process.exit(0);
}
// Step 3: Execute setup tasks
await p.tasks([
{
title: "Creating project directory",
task: () => "Directory created",
},
{
title: "Generating project files",
task: async (message) => {
message("Creating package.json...");
await new Promise(resolve => setTimeout(resolve, 300));
message("Setting up TypeScript...");
await new Promise(resolve => setTimeout(resolve, 400));
message("Configuring build tools...");
await new Promise(resolve => setTimeout(resolve, 500));
return "Project files generated";
},
},
{
title: "Installing dependencies",
task: async () => {
await new Promise(resolve => setTimeout(resolve, 2000));
return "Dependencies installed";
},
},
{
title: "Setting up testing framework",
enabled: projectInfo.features.includes("testing"),
task: () => "Testing configured",
},
]);
p.outro(`Project '${projectInfo.name}' created successfully! 🎉`);Tasks automatically handle errors and display them appropriately:
await tasks([
{
title: "Risky operation",
task: async () => {
// If this throws, the task will show as failed
throw new Error("Something went wrong");
},
},
]);For custom error handling:
await tasks([
{
title: "Safe operation",
task: async () => {
try {
// Risky operation
return "Operation successful";
} catch (error) {
return `Operation failed: ${error.message}`;
}
},
},
]);undefined) skip execution entirely