mcpField
D
developer-tools

developer-tools

github.com/miles990开发26217 次安装40 周前更新
代码仓库

关于 developer-tools

面向开发者的技能,汇集 CLI、SDK 和 API 文档开发示例,适用于构建开发工具与改善开发者体验。内容涵盖命令行输出格式、请求重试与限流,以及 OpenAPI 文档生成。

主要功能

  1. 01CLI 框架与交互示例
  2. 02格式化命令行输出
  3. 03TypeScript SDK 客户端示例
  4. 04重试与速率限制模式
  5. 05生成 OpenAPI 文档

适用场景

  1. 01构建命令行工具
  2. 02开发 TypeScript 服务 SDK
  3. 03为请求添加重试和限流
  4. 04生成交互式 API 文档

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/miles990/claude-software-skills --skill developer-tools

安装后在 Claude Code 中输入/developer-tools确认已加载。

附带文件templates/cli-boilerplate.tstemplates/README.md

详细介绍

开发者工具

概览

构建命令行界面、SDK 和工具,提升开发者体验。


CLI 开发

CLI 框架(Commander)
#!/usr/bin/env node
import { Command } from 'commander';
import chalk from 'chalk';
import ora from 'ora';
import inquirer from 'inquirer';

const program = new Command();

program
  .name('myctl')
  .description('CLI tool for managing resources')
  .version('1.0.0');

// Simple command
program
  .command('init')
  .description('Initialize a new project')
  .option('-t, --template <name>', 'Template to use', 'default')
  .option('-d, --directory <path>', 'Target directory', '.')
  .action(async (options) => {
    const spinner = ora('Initializing project...').start();

    try {
      await initProject(options.template, options.directory);
      spinner.succeed(chalk.green('Project initialized successfully!'));
    } catch (error) {
      spinner.fail(chalk.red(`Failed: ${error.message}`));
      process.exit(1);
    }
  });

// Interactive command
program
  .command('create <name>')
  .description('Create a new resource')
  .action(async (name) => {
    const answers = await inquirer.prompt([
      {
        type: 'list',
        name: 'type',
        message: 'Select resource type:',
        choices: ['api', 'worker', 'database'],
      },
      {
        type: 'input',
        name: 'description',
        message: 'Description:',
      },
      {
        type: 'confirm',
        name: 'public',
        message: 'Make it public?',
        default: false,
      },
    ]);

    await createResource(name, answers);
    console.log(chalk.green(`Created ${answers.type}: ${name}`));
  });

// Subcommands
const configCmd = program.command('config').description('Manage configuration');

configCmd
  .command('set <key> <value>')
  .description('Set a config value')
  .action(async (key, value) => {
    await setConfig(key, value);
    console.log(`Set ${key}=${value}`);
  });

configCmd
  .command('get <key>')
  .description('Get a config value')
  .action(async (key) => {
    const value = await getConfig(key);
    console.log(value);
  });

configCmd
  .command('list')
  .description('List all config values')
  .action(async () => {
    const config = await getAllConfig();
    console.table(config);
  });

// Global options
program
  .option('--debug', 'Enable debug mode')
  .option('--json', 'Output as JSON')
  .hook('preAction', (thisCommand) => {
    if (thisCommand.opts().debug) {
      process.env.DEBUG = 'true';
    }
  });

program.parse();
CLI 输出格式
import Table from 'cli-table3';
import boxen from 'boxen';

// Table output
function printTable(data: Array<Record<string, any>>, columns: string[]) {
  const table = new Table({
    head: columns.map((c) => chalk.bold(c)),
    style: { head: ['cyan'] },
  });

  data.forEach((row) => {
    table.push(columns.map((col) => row[col] ?? ''));
  });

  console.log(table.toString());
}

// JSON output
function printJson(data: any) {
  console.log(JSON.stringify(data, null, 2));
}

// Box output
function printBox(message: string, title?: string) {
  console.log(
    boxen(message, {
      padding: 1,
      margin: 1,
      borderStyle: 'round',
      title,
      titleAlignment: 'center',
    })
  );
}

// Progress bar
import cliProgress from 'cli-progress';

async function withProgress<T>(
  items: T[],
  fn: (item: T) => Promise<void>,
  label: string
) {
  const bar = new cliProgress.SingleBar({
    format: `${label} |{bar}| {percentage}% | {value}/{total}`,
    barCompleteChar: '\u2588',
    barIncompleteChar: '\u2591',
  });

  bar.start(items.length, 0);

  for (const item of items) {
    await fn(item);
    bar.increment();
  }

  bar.stop();
}

SDK 开发

TypeScript SDK
// sdk/index.ts
export class MyServiceClient {
  private baseUrl: string;
  private apiKey: string;

  constructor(options: { apiKey: string; baseUrl?: string }) {
    this.apiKey = options.apiKey;
    this.baseUrl = options.baseUrl || 'https://api.example.com';
  }

  private async request<T>(
    method: string,
    path: string,
    data?: any
  ): Promise<T> {
    const response = await fetch(`${this.baseUrl}${path}`, {
      method,
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${this.apiKey}`,
      },
      body: data ? JSON.stringify(data) : undefined,
    });

    if (!response.ok) {
      const error = await response.json().catch(() => ({}));
      throw new ApiError(response.status, error.message || 'Request failed');
    }

    return response.json();
  }

  // Resource: Users
  users = {
    list: (params?: ListUsersParams) =>
      this.request<PaginatedResponse<User>>('GET', '/users?' + qs(params)),

    get: (id: string) => this.request<User>('GET', `/users/${id}`),

    create: (data: CreateUserInput) =>
      this.request<User>('POST', '/users', data),

    update: (id: string, data: UpdateUserInput) =>
      this.request<User>('PUT', `/users/${id}`, data),

    delete: (id: string) => this.request<void>('DELETE', `/users/${id}`),
  };

  // Resource: Projects
  projects = {
    list: () => this.request<Project[]>('GET', '/projects'),
    get: (id: string) => this.request<Project>('GET', `/projects/${id}`),
    create: (data: CreateProjectInput) =>
      this.request<Project>('POST', '/projects', data),
  };
}

// Error class
export class ApiError extends Error {
  constructor(
    public status: number,
    message: string,
    public code?: string
  ) {
    super(message);
    this.name = 'ApiError';
  }
}

// Types
export interface User {
  id: string;
  email: string;
  name: string;
  createdAt: string;
}

export interface CreateUserInput {
  email: string;
  name: string;
}

export interface UpdateUserInput {
  name?: string;
}

export interface PaginatedResponse<T> {
  data: T[];
  meta: {
    page: number;
    limit: number;
    total: number;
  };
}

// Usage
const client = new MyServiceClient({ apiKey: 'sk_...' });

const users = await client.users.list({ page: 1, limit: 10 });
const user = await client.users.create({ email: '[email protected]', name: 'Test' });
支持重试和速率限制的 SDK
import pRetry from 'p-retry';
import pThrottle from 'p-throttle';

class RobustClient {
  private throttle = pThrottle({
    limit: 100,
    interval: 60000, // 100 requests per minute
  });

  private async requestWithRetry<T>(
    fn: () => Promise<T>,
    options?: { retries?: number }
  ): Promise<T> {
    return pRetry(
      async () => {
        try {
          return await fn();
        } catch (error) {
          if (error instanceof ApiError) {
            // Don't retry client errors
            if (error.status >= 400 && error.status < 500) {
              throw new pRetry.AbortError(error);
            }
          }
          throw error;
        }
      },
      {
        retries: options?.retries ?? 3,
        onFailedAttempt: (error) => {
          console.log(
            `Attempt ${error.attemptNumber} failed. ${error.retriesLeft} retries left.`
          );
        },
      }
    );
  }

  private request = this.throttle(async <T>(
    method: string,
    path: string,
    data?: any
  ): Promise<T> => {
    return this.requestWithRetry(async () => {
      const response = await fetch(`${this.baseUrl}${path}`, {
        method,
        headers: this.getHeaders(),
        body: data ? JSON.stringify(data) : undefined,
      });

      // Handle rate limiting
      if (response.status === 429) {
        const retryAfter = response.headers.get('Retry-After');
        const delay = retryAfter ? parseInt(retryAfter) * 1000 : 60000;
        await sleep(delay);
        throw new Error('Rate limited, retrying...');
      }

      if (!response.ok) {
        throw new ApiError(response.status, await response.text());
      }

      return response.json();
    });
  });
}

API 文档

生成 OpenAPI 规范
import { generateOpenApi } from '@ts-rest/open-api';
import { contract } from './contract';

const openApiDocument = generateOpenApi(contract, {
  info: {
    title: 'My API',
    version: '1.0.0',
    description: 'API for managing resources',
  },
  servers: [
    { url: 'https://api.example.com', description: 'Production' },
    { url: 'https://staging-api.example.com', description: 'Staging' },
  ],
});

// Export for documentation tools
export { openApiDocument };
交互式文档
// Using Swagger UI React
import SwaggerUI from 'swagger-ui-react';
import 'swagger-ui-react/swagger-ui.css';

function ApiDocs() {
  return (
    <SwaggerUI
      url="/api/openapi.json"
      docExpansion="list"
      defaultModelsExpandDepth={3}
    />
  );
}

// Or Redoc
import { RedocStandalone } from 'redoc';

function ApiDocs() {
  return <RedocStandalone specUrl="/api/openapi.json" />;
}

开发者门户

// API key management component
function ApiKeyManager() {
  const [keys, setKeys] = useState<ApiKey[]>([]);

  async function createKey(name: string) {
    const key = await api.createApiKey({ name });
    // Show the secret only once
    showModal({
      title: 'API Key Created',
      content: (
        <div>
          <p>Save this key - it won't be shown again:</p>
          <code className="bg-gray-100 p-2 block">{key.secret}</code>
        </div>
      ),
    });
    setKeys([...keys, key]);
  }

  async function revokeKey(keyId: string) {
    await api.revokeApiKey(keyId);
    setKeys(keys.filter((k) => k.id !== keyId));
  }

  return (
    <div>
      <h2>API Keys</h2>
      <table>
        <thead>
          <tr>
            <th>Name</th>
            <th>Created</th>
            <th>Last Used</th>
            <th>Actions</th>
          </tr>
        </thead>
        <tbody>
          {keys.map((key) => (
            <tr key={key.id}>
              <td>{key.name}</td>
              <td>{formatDate(key.createdAt)}</td>
              <td>{key.lastUsedAt ? formatDate(key.lastUsedAt) : 'Never'}</td>
              <td>
                <button onClick={() => revokeKey(key.id)}>Revoke</button>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
      <button onClick={() => createKey(prompt('Key name:') || 'Unnamed')}>
        Create New Key
      </button>
    </div>
  );
}

相关技能

  • [[api-design]] - API 设计模式
  • [[documentation]] - 技术写作
  • [[automation-scripts]] - 构建自动化

评论

支持 Markdown