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