AgentSkillsCN

clack-guidelines

全面指导如何使用Clack构建精美的交互式命令行界面。当您需要创建带有文本输入、选项选择、自动补全、进度跟踪,以及流式输出的CLI工具时,可使用此技能。

SKILL.md
--- frontmatter
name: clack-guidelines
description: Comprehensive guide for building beautiful interactive command-line interfaces using Clack. Use when creating CLI tools with text input, selections, autocomplete, progress tracking, and streaming output.

Clack Guidelines

Overview

This skill provides guidance for building beautiful interactive command-line interfaces using Clack. It covers common patterns for text input, selections, autocomplete, progress tracking, streaming output, and creating complete interactive CLI applications.

Quick Start

Installation

bash
npm install @clack/prompts

Basic Program

javascript
import { text, isCancel, cancel } from "@clack/prompts";

const projectPath = await text({
  message: "Where should we create your project?",
  placeholder: "./my-awesome-project",
  initialValue: "./sparkling-solid",
  validate: (value) => {
    if (!value) return "Please enter a path.";
    if (value[0] !== ".") return "Please enter a relative path.";
  },
});

if (isCancel(projectPath)) {
  cancel("Operation cancelled.");
  process.exit(0);
}

console.log(`Creating project at: ${projectPath}`);

Complete Interactive CLI

javascript
import * as p from "@clack/prompts";
import color from "picocolors";

async function createApp() {
  console.clear();
  p.intro(color.bgCyan(color.black(" create-myapp ")));

  const config = await p.group(
    {
      name: () =>
        p.text({
          message: "What is your project name?",
          placeholder: "my-awesome-app",
          validate: (value) => {
            if (!value) return "Project name is required";
          },
        }),

      framework: () =>
        p.select({
          message: "Choose your framework",
          options: [
            { value: "react", label: "React", hint: "Popular" },
            { value: "vue", label: "Vue" },
            { value: "svelte", label: "Svelte" },
          ],
        }),

      typescript: () =>
        p.confirm({
          message: "Use TypeScript?",
          initialValue: true,
        }),
    },
    {
      onCancel: () => {
        p.cancel("Setup cancelled");
        process.exit(0);
      },
    },
  );

  console.log(config);
}

createApp().catch(console.error);

Common Patterns

Text Input

Single-line text input with validation and placeholders.

javascript
import { text } from "@clack/prompts";

const name = await text({
  message: "Enter your name",
  placeholder: "John Doe",
  validate: (value) => {
    if (!value) return "Name is required";
  },
});

Password Input

Secure password input with masking.

javascript
import { password } from "@clack/prompts";

const userPassword = await password({
  message: "Provide a password",
  mask: "•",
  validate: (value) => {
    if (!value) return "Please enter a password.";
    if (value.length < 8) return "Password should have at least 8 characters.";
  },
});

Autocomplete Single Selection

Type-ahead search with real-time filtering.

javascript
import { autocomplete } from "@clack/prompts";

const country = await autocomplete({
  message: "Select a country",
  options: [
    { value: "us", label: "United States", hint: "NA" },
    { value: "ca", label: "Canada", hint: "NA" },
    { value: "uk", label: "United Kingdom", hint: "EU" },
  ],
  placeholder: "Type to search countries...",
  maxItems: 8,
  initialValue: "us",
});

Autocomplete Multi-Select

Type-ahead search with multi-selection.

javascript
import { autocompleteMultiselect } from "@clack/prompts";

const frameworks = await autocompleteMultiselect({
  message: "Select frameworks (type to filter)",
  options: [
    { value: "react", label: "React", hint: "Frontend/UI" },
    { value: "vue", label: "Vue.js", hint: "Frontend/UI" },
    { value: "nextjs", label: "Next.js", hint: "React Framework" },
  ],
  placeholder: "Type to filter...",
  maxItems: 8,
  initialValues: ["react", "nextjs"],
  required: true,
});

Single Selection Menu

Choose one option from a scrollable list.

javascript
import { select } from "@clack/prompts";

const projectType = await select({
  message: "Pick a project type",
  initialValue: "ts",
  maxItems: 5,
  options: [
    { value: "ts", label: "TypeScript" },
    { value: "js", label: "JavaScript" },
    { value: "rust", label: "Rust" },
    { value: "go", label: "Go" },
  ],
});

Multi-Select Menu

Select multiple options from a list.

javascript
import { multiselect } from "@clack/prompts";

const tools = await multiselect({
  message: "Select additional tools",
  initialValues: ["prettier", "eslint"],
  required: true,
  options: [
    { value: "prettier", label: "Prettier", hint: "recommended" },
    { value: "eslint", label: "ESLint", hint: "recommended" },
    { value: "stylelint", label: "Stylelint" },
  ],
});

Confirmation Dialog

Binary yes/no choice.

javascript
import { confirm } from "@clack/prompts";

const shouldInstallDeps = await confirm({
  message: "Install dependencies?",
  active: "Yes, please",
  inactive: "No, skip",
  initialValue: false,
});

Progress Bar

Visual progress indicator for long-running operations.

javascript
import { progress } from "@clack/prompts";
import { setTimeout } from "node:timers/promises";

const downloadProgress = progress({
  style: "block",
  max: 100,
  size: 40,
});

downloadProgress.start("Downloading packages");

for (let i = 0; i < 100; i += 10) {
  await setTimeout(500);
  downloadProgress.advance(10);
  downloadProgress.message(`Downloaded ${i + 10}%`);
}

downloadProgress.stop("Download complete!");

Spinner Loading Indicator

Display progress for long-running operations.

javascript
import { spinner } from "@clack/prompts";
import { setTimeout } from "node:timers/promises";

const s = spinner();

s.start("Installing packages via pnpm");
await setTimeout(2000);

s.message("Downloading dependencies");
await setTimeout(1500);

s.stop("Installation complete!");

Task Log with Live Output

Display streaming output that clears on success and persists on error.

javascript
import { taskLog } from "@clack/prompts";
import { spawn } from "node:child_process";

const log = taskLog({
  title: "Running npm install",
  limit: 10,
  retainLog: true,
  spacing: 1,
});

const npmInstall = spawn("npm", ["install"]);

npmInstall.stdout.on("data", (data) => {
  log.message(data.toString(), { raw: true });
});

npmInstall.on("close", (code) => {
  if (code === 0) {
    log.success("Installation complete!");
  } else {
    log.error("Installation failed!", { showLog: true });
  }
});

Streaming Output for LLMs

Stream dynamic content from async iterables.

javascript
import { stream } from "@clack/prompts";
import { setTimeout } from "node:timers/promises";

await stream.step(
  (async function* () {
    const words = "Building your application with modern tools".split(" ");
    for (const word of words) {
      yield word;
      yield " ";
      await setTimeout(100);
    }
  })(),
);

Grouped Prompts with Sequential Execution

Execute multiple prompts in sequence with shared state.

javascript
import * as p from "@clack/prompts";
import color from "picocolors";

p.intro(color.bgCyan(color.black(" create-app ")));

const project = await p.group(
  {
    path: () =>
      p.text({
        message: "Where should we create your project?",
        placeholder: "./sparkling-solid",
        validate: (value) => {
          if (!value) return "Please enter a path.";
          if (value[0] !== ".") return "Please enter a relative path.";
        },
      }),

    type: ({ results }) =>
      p.select({
        message: `Pick a project type within "${results.path}"`,
        initialValue: "ts",
        options: [
          { value: "ts", label: "TypeScript" },
          { value: "js", label: "JavaScript" },
          { value: "rust", label: "Rust" },
        ],
      }),

    tools: () =>
      p.multiselect({
        message: "Select additional tools",
        options: [
          { value: "prettier", label: "Prettier", hint: "recommended" },
          { value: "eslint", label: "ESLint", hint: "recommended" },
        ],
      }),

    install: () =>
      p.confirm({
        message: "Install dependencies?",
        initialValue: true,
      }),
  },
  {
    onCancel: () => {
      p.cancel("Operation cancelled.");
      process.exit(0);
    },
  },
);

console.log(project.path, project.type, project.tools, project.install);

Task Execution with Spinners

Run multiple tasks with automatic spinner management.

javascript
import * as p from "@clack/prompts";
import { setTimeout } from "node:timers/promises";

await p.tasks([
  {
    title: "Installing dependencies",
    task: async (message) => {
      message("Downloading packages");
      await setTimeout(1000);
      message("Building native modules");
      await setTimeout(500);
      return "Installed 127 packages";
    },
  },
  {
    title: "Running linter",
    task: async () => {
      // Run linter
      return "No issues found";
    },
  },
]);

Logging Messages

Display formatted status messages with different severity levels.

javascript
import { log } from "@clack/prompts";
import color from "picocolors";

log.info("Starting build process...");
log.step("Compiling TypeScript");
log.success("Build completed successfully!");
log.warn("Some dependencies are outdated");
log.error("Failed to connect to database");

// Custom symbol
log.message("Custom message", {
  symbol: color.cyan("→"),
});

Intro, Outro, and Notes

Frame your CLI sessions with beautiful headers.

javascript
import { intro, outro, note } from "@clack/prompts";
import color from "picocolors";

intro(color.bgCyan(color.black(" my-cli-tool v2.0.0 ")));

// ... prompts and operations ...

note("cd my-project\nnpm install\nnpm run dev", "Next steps");

outro(
  `Problems? ${color.underline(color.cyan("https://github.com/myorg/myproject/issues"))}`,
);

Custom Key Bindings

Configure alternative key mappings for navigation.

javascript
import { updateSettings } from "@clack/prompts";

// Enable WASD navigation
updateSettings({
  aliases: {
    w: "up",
    s: "down",
    a: "left",
    d: "right",
  },
});

Non-Interactive Mode Detection

Handle CI environments where prompts aren't supported.

javascript
import { text, block } from "@clack/prompts";

// In CI environments, prompts automatically use defaults
const name = await text({
  message: "Project name?",
  defaultValue: "default-project",
});

// block() utility prevents input in non-TTY environments
const unblock = block();
// Perform operations
unblock();

Cancellation and Error Handling

Robust error handling for user cancellations and validation errors.

javascript
import * as p from "@clack/prompts";

async function setupProject() {
  try {
    const responses = await p.group({
      name: () =>
        p.text({
          message: "Project name?",
          validate: (v) => {
            if (!v) return "Required";
          },
        }),

      confirm: ({ results }) =>
        p.confirm({
          message: `Create "${results.name}"?`,
        }),
    });

    // Check if any step was cancelled
    if (p.isCancel(responses.name) || p.isCancel(responses.confirm)) {
      p.cancel("Setup cancelled");
      return;
    }

    if (!responses.confirm) {
      p.outro("Setup aborted");
      return;
    }

    // Proceed with setup
    p.outro("Project created!");
  } catch (error) {
    p.log.error(`Setup failed: ${error.message}`);
    process.exit(1);
  }
}

setupProject();

Advanced Features

Custom Render Function with @clack/core

Build custom prompts with full control over rendering.

javascript
import { TextPrompt } from "@clack/core";
import color from "picocolors";

const customPrompt = new TextPrompt({
  validate: (value) => (value.length < 3 ? "Too short!" : undefined),
  render() {
    const title = `>>> ${color.bold("Enter your name")}:`;
    const input = this.valueWithCursor || color.dim("(empty)");

    switch (this.state) {
      case "error":
        return `${title}\n${color.red(input)}\n${color.red(this.error)}`;
      case "submit":
        return `${title} ${color.green(this.value)}`;
      case "cancel":
        return `${title} ${color.strikethrough(this.value)}`;
      default:
        return `${title}\n${color.cyan(input)}`;
    }
  },
});

const result = await customPrompt.prompt();
console.log(`Result: ${result}`);

Event-Driven Custom Prompt

Subscribe to prompt events for advanced interactions.

javascript
import { TextPrompt } from "@clack/core";

const prompt = new TextPrompt({
  render() {
    return `Input: ${this.valueWithCursor}`;
  },
});

// Listen to events
prompt.on("value", (value) => {
  console.log(`Current value: ${value}`);
});

prompt.on("submit", () => {
  console.log("User submitted the form");
});

prompt.on("cancel", () => {
  console.log("User cancelled");
});

const result = await prompt.prompt();

Complete CLI Application Example

Full-featured setup wizard combining multiple prompt types.

javascript
import { setTimeout } from "node:timers/promises";
import * as p from "@clack/prompts";
import color from "picocolors";

async function createApp() {
  console.clear();

  p.intro(color.bgCyan(color.black(" create-myapp ")));

  const config = await p.group(
    {
      name: () =>
        p.text({
          message: "What is your project name?",
          placeholder: "my-awesome-app",
          validate: (value) => {
            if (!value) return "Project name is required";
            if (!/^[a-z0-9-]+$/.test(value)) {
              return "Use only lowercase letters, numbers, and hyphens";
            }
          },
        }),

      framework: () =>
        p.select({
          message: "Choose your framework",
          options: [
            { value: "react", label: "React", hint: "Popular" },
            { value: "vue", label: "Vue" },
            { value: "svelte", label: "Svelte" },
            { value: "solid", label: "Solid" },
          ],
        }),

      features: () =>
        p.multiselect({
          message: "Select features",
          required: false,
          options: [
            { value: "router", label: "Router" },
            { value: "state", label: "State Management" },
            { value: "testing", label: "Testing Setup" },
            { value: "ci", label: "CI/CD Pipeline" },
          ],
        }),

      typescript: () =>
        p.confirm({
          message: "Use TypeScript?",
          initialValue: true,
        }),

      install: () =>
        p.confirm({
          message: "Install dependencies now?",
          initialValue: true,
        }),
    },
    {
      onCancel: () => {
        p.cancel("Setup cancelled");
        process.exit(0);
      },
    },
  );

  // Execute setup tasks
  await p.tasks([
    {
      title: "Creating project structure",
      task: async () => {
        // Create project files
        return `Created ${config.name}`;
      },
    },
    {
      title: "Installing dependencies",
      task: async (message) => {
        message("Resolving packages...");
        await setTimeout(1000);
        message("Downloading...");
        await setTimeout(2000);
        return "Installed 42 packages";
      },
      enabled: config.install,
    },
  ]);

  // Display next steps
  const nextSteps = [
    `cd ${config.name}`,
    config.install ? "" : "npm install",
    "npm run dev",
  ]
    .filter(Boolean)
    .join("\n");

  p.note(nextSteps, "Next steps");

  p.outro(
    `All set! ${color.underline(color.cyan("https://docs.example.com"))}`,
  );
}

async function createProjectFiles(config) {
  // Implementation...
}

createApp().catch(console.error);

Detailed API Reference

For comprehensive API documentation including all prompt types, configuration options, and advanced features, see the detailed API reference:

references/clack-api.md

This reference includes:

  • Complete prompt type documentation
  • Configuration options for each prompt
  • Validation and error handling patterns
  • Streaming and async patterns
  • Custom key bindings and settings
  • Integration with @clack/core
  • TypeScript support
  • And more

Resources

references/

This skill includes detailed API documentation:

  • clack-api.md - Comprehensive API reference for Clack prompts, organized by prompt type with code examples for each feature.