or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

advanced-workflows.mdcancellation-handling.mdindex.mdinteractive-prompts.mdlogging-system.mdprogress-indicators.mdselection-prompts.mdsession-management.mdsettings-configuration.md
tile.json

advanced-workflows.mddocs/

Advanced Workflows

Complex prompt orchestration with grouped prompts, task execution, and shared state management for sophisticated CLI applications.

Capabilities

Prompt Groups

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}`);
}

Group Cancellation Handling

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);

Task Execution

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",
  },
]);

Complex Workflow Example

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! ๐ŸŽ‰`);

Error Handling in Workflows

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}`;
      }
    },
  },
]);

Performance Considerations

  • Groups execute prompts sequentially - each prompt waits for the previous one
  • Tasks run in sequence - perfect for dependent operations
  • Conditional prompts (returning undefined) skip execution entirely
  • Disabled tasks are completely bypassed