feat: simplify interactive CLI to only ask for package name
- Removed all prompts except package name - Auto-generate description from package name - Use sensible defaults: 2 example tools, ai-ml category, MIT license - Generate exampleTool and anotherTool that users can customize - Much faster UX - no more 10+ prompts for basic usage - Updated README with simplified flow example - Bumped version to 1.0.3 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
83afeef2dc
commit
3219da5a03
3 changed files with 74 additions and 100 deletions
|
|
@ -1,15 +1,15 @@
|
|||
# @tpmjs/create-basic-tools
|
||||
|
||||
CLI generator for scaffolding production-ready TPMJS tool packages with 2-3 tools by default.
|
||||
CLI generator for scaffolding production-ready TPMJS tool packages. Just enter your package name and you're done!
|
||||
|
||||
## Features
|
||||
|
||||
- 🎯 **Multi-tool packages**: Generates packages with minimum 2 tools (ideally 2-3)
|
||||
- ⚡ **Super fast**: Just asks for your package name, generates everything else
|
||||
- 🎯 **2 example tools**: Start with working examples you can customize
|
||||
- 🔧 **Zod 4 schemas**: Uses Zod directly (not jsonSchema wrapper)
|
||||
- ⚡ **AI SDK v6**: Full compatibility with the latest AI SDK
|
||||
- 📦 **One file per tool**: Clean `src/tools/<toolName>.ts` structure
|
||||
- ✅ **TPMJS validated**: Auto-validates against official TPMJS schemas
|
||||
- 🎨 **Beautiful CLI**: Interactive prompts with @clack/prompts
|
||||
- 📝 **Complete setup**: Generates package.json, tsconfig, tsup config, README, and more
|
||||
- 🚀 **Publish ready**: Generated packages are ready to publish to npm immediately
|
||||
|
||||
|
|
@ -21,14 +21,13 @@ CLI generator for scaffolding production-ready TPMJS tool packages with 2-3 tool
|
|||
pnpmx @tpmjs/create-basic-tools
|
||||
```
|
||||
|
||||
This will guide you through an interactive wizard that asks:
|
||||
The CLI asks for just your package name and uses sensible defaults for everything else:
|
||||
|
||||
1. **Package info**: name, description, author, license
|
||||
2. **Tool definitions**: At least 2 tools (export name + description)
|
||||
3. **Category**: Choose from 12 TPMJS categories
|
||||
4. **Mode**: Simple (basic Zod schemas) or Advanced (full control)
|
||||
5. **Output path**: Where to create the package
|
||||
6. **Confirmation**: Review and confirm
|
||||
- **Description**: Auto-generated from package name
|
||||
- **Tools**: 2 example tools you can customize
|
||||
- **Category**: `ai-ml` (generic)
|
||||
- **License**: MIT
|
||||
- **Output**: Derived from package name
|
||||
|
||||
### Example Session
|
||||
|
||||
|
|
@ -40,54 +39,34 @@ $ pnpmx @tpmjs/create-basic-tools
|
|||
◇ Package name
|
||||
│ @myorg/content-tools
|
||||
│
|
||||
◇ Package description
|
||||
│ AI SDK tools for content processing
|
||||
◆ Generating package...
|
||||
│
|
||||
◇ Tool #1 export name
|
||||
│ summarizeText
|
||||
│
|
||||
◇ Tool #1 description
|
||||
│ Summarize a block of text into a concise overview.
|
||||
│
|
||||
◇ Tool #2 export name
|
||||
│ extractKeywords
|
||||
│
|
||||
◇ Tool #2 description
|
||||
│ Extract important keywords from text.
|
||||
│
|
||||
◇ Add tool #3? (already have 2)
|
||||
│ Yes
|
||||
│
|
||||
◇ Tool #3 export name
|
||||
│ classifySentiment
|
||||
│
|
||||
◇ Tool #3 description
|
||||
│ Classify the sentiment of text as positive, negative, or neutral.
|
||||
│
|
||||
◇ Category
|
||||
│ text-analysis
|
||||
│
|
||||
◇ Mode
|
||||
│ Simple Mode - Basic Zod schemas
|
||||
│
|
||||
◇ Where should we create the package?
|
||||
│ ./content-tools
|
||||
│
|
||||
◇ Ready to generate?
|
||||
│ Yes
|
||||
│
|
||||
└ Success! Created @myorg/content-tools at ./content-tools
|
||||
└ ✓ Success! Created @myorg/content-tools at ./content-tools
|
||||
|
||||
Files created:
|
||||
src/tools/exampleTool.ts
|
||||
src/tools/anotherTool.ts
|
||||
src/index.ts
|
||||
package.json
|
||||
|
||||
Next steps:
|
||||
cd ./content-tools
|
||||
pnpm install
|
||||
pnpm build
|
||||
pnpm type-check
|
||||
pnpm publish
|
||||
```
|
||||
|
||||
That's it! The generator creates 2 example tools you can rename and customize for your use case.
|
||||
|
||||
## Generated Package Structure
|
||||
|
||||
```
|
||||
content-tools/
|
||||
├── src/
|
||||
│ ├── tools/ # One file per tool
|
||||
│ │ ├── summarizeText.ts
|
||||
│ │ ├── extractKeywords.ts
|
||||
│ │ └── classifySentiment.ts
|
||||
│ │ ├── exampleTool.ts
|
||||
│ │ └── anotherTool.ts
|
||||
│ └── index.ts # Re-exports all tools
|
||||
├── dist/ # Build output (after pnpm build)
|
||||
│ ├── index.js
|
||||
|
|
@ -101,6 +80,8 @@ content-tools/
|
|||
└── LICENSE
|
||||
```
|
||||
|
||||
Simply rename `exampleTool.ts` and `anotherTool.ts` to match your use case, then customize the implementation.
|
||||
|
||||
## Generated Tool File Example
|
||||
|
||||
Each tool file follows this Zod-first pattern:
|
||||
|
|
@ -109,7 +90,7 @@ Each tool file follows this Zod-first pattern:
|
|||
import { tool } from 'ai';
|
||||
import { z } from 'zod';
|
||||
|
||||
const SummarizeTextSchema = z.object({
|
||||
const ExampleToolSchema = z.object({
|
||||
text: z.string().min(1, 'Text cannot be empty').describe('The input text to process.'),
|
||||
options: z.object({
|
||||
language: z.string().default('en').describe('Language code (e.g., en, es, fr).'),
|
||||
|
|
@ -117,12 +98,12 @@ const SummarizeTextSchema = z.object({
|
|||
}).default({ language: 'en', maxLength: 100 }).describe('Optional configuration.'),
|
||||
});
|
||||
|
||||
export const summarizeText = tool({
|
||||
description: 'Summarize a block of text into a concise overview.',
|
||||
inputSchema: SummarizeTextSchema,
|
||||
async execute(input: z.infer<typeof SummarizeTextSchema>) {
|
||||
export const exampleTool = tool({
|
||||
description: 'An example tool - customize this for your use case',
|
||||
inputSchema: ExampleToolSchema,
|
||||
async execute(input: z.infer<typeof ExampleToolSchema>) {
|
||||
// TODO: Implement the tool logic here
|
||||
console.log('summarizeText called with:', input);
|
||||
console.log('exampleTool called with:', input);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@tpmjs/create-basic-tools",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.3",
|
||||
"description": "CLI generator for scaffolding production-ready TPMJS tool packages",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
|
|
|
|||
|
|
@ -4,9 +4,6 @@ import { generateTsConfig, generateTsupConfig } from './generators/config-files.
|
|||
import { generatePackageJson } from './generators/package-json.js';
|
||||
import { generateReadme } from './generators/readme.js';
|
||||
import { generateIndexFile, generateToolFile } from './generators/source-code.js';
|
||||
import { promptCategoryAndMode, promptConfirmation, promptOutputPath } from './prompts/advanced.js';
|
||||
import { promptBasicInfo } from './prompts/basic.js';
|
||||
import { promptTools } from './prompts/tools.js';
|
||||
import type { GenerationResult, GeneratorConfig } from './types.js';
|
||||
import {
|
||||
copyTemplate,
|
||||
|
|
@ -23,41 +20,25 @@ import * as logger from './utils/logger.js';
|
|||
export async function runInteractiveCLI(): Promise<GenerationResult> {
|
||||
clack.intro(logger.bold('create-tpmjs-tool'));
|
||||
|
||||
// Step 1: Get basic package info
|
||||
const packageInfo = await promptBasicInfo();
|
||||
if (!packageInfo) {
|
||||
// Only ask for package name
|
||||
const name = await clack.text({
|
||||
message: 'Package name',
|
||||
placeholder: '@yourname/my-tools',
|
||||
validate: (value) => {
|
||||
if (!value) return 'Package name is required';
|
||||
if (!value.includes('/')) return 'Package name should be scoped (e.g., @yourname/my-tools)';
|
||||
},
|
||||
});
|
||||
|
||||
if (clack.isCancel(name)) {
|
||||
clack.cancel('Operation cancelled');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Step 2: Get tool definitions (minimum 2)
|
||||
const tools = await promptTools();
|
||||
if (!tools || tools.length < 2) {
|
||||
clack.cancel('Operation cancelled');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Step 3: Get category and mode
|
||||
const categoryAndMode = await promptCategoryAndMode();
|
||||
|
||||
if (!categoryAndMode) {
|
||||
clack.cancel('Operation cancelled');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
packageInfo.category = categoryAndMode.category;
|
||||
|
||||
// Step 4: Get output path
|
||||
const packageNameWithoutScope = packageInfo.name.split('/').pop() || packageInfo.name;
|
||||
// Use defaults for everything else
|
||||
const packageNameWithoutScope = (name as string).split('/').pop() || (name as string);
|
||||
const defaultPath = `./${packageNameWithoutScope}`;
|
||||
|
||||
const outputPath = await promptOutputPath(defaultPath);
|
||||
if (!outputPath) {
|
||||
clack.cancel('Operation cancelled');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const absoluteOutputPath = getAbsolutePath(outputPath);
|
||||
const absoluteOutputPath = getAbsolutePath(defaultPath);
|
||||
|
||||
// Check if directory exists
|
||||
if (await pathExists(absoluteOutputPath)) {
|
||||
|
|
@ -66,22 +47,34 @@ export async function runInteractiveCLI(): Promise<GenerationResult> {
|
|||
process.exit(1);
|
||||
}
|
||||
|
||||
// Step 5: Confirm generation
|
||||
const confirmed = await promptConfirmation(packageInfo.name, tools.length, outputPath);
|
||||
if (!confirmed) {
|
||||
clack.cancel('Operation cancelled');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Step 6: Generate the package
|
||||
// Generate with sensible defaults
|
||||
const spinner = clack.spinner();
|
||||
spinner.start('Generating package...');
|
||||
|
||||
const packageInfo = {
|
||||
name: name as string,
|
||||
description: `AI SDK tools for ${packageNameWithoutScope}`,
|
||||
author: '',
|
||||
license: 'MIT',
|
||||
category: 'ai-ml',
|
||||
};
|
||||
|
||||
const tools = [
|
||||
{
|
||||
exportName: 'exampleTool',
|
||||
description: 'An example tool - customize this for your use case',
|
||||
},
|
||||
{
|
||||
exportName: 'anotherTool',
|
||||
description: 'Another example tool - add your implementation here',
|
||||
},
|
||||
];
|
||||
|
||||
const config: GeneratorConfig = {
|
||||
packageInfo,
|
||||
tools,
|
||||
outputPath: absoluteOutputPath,
|
||||
mode: categoryAndMode.mode,
|
||||
mode: 'simple',
|
||||
};
|
||||
|
||||
try {
|
||||
|
|
@ -90,13 +83,13 @@ export async function runInteractiveCLI(): Promise<GenerationResult> {
|
|||
spinner.stop('Package generated successfully!');
|
||||
|
||||
clack.outro(`
|
||||
${logger.green('✓')} Success! Created ${logger.bold(packageInfo.name)} at ${logger.cyan(outputPath)}
|
||||
${logger.green('✓')} Success! Created ${logger.bold(packageInfo.name)} at ${logger.cyan(defaultPath)}
|
||||
|
||||
Files created:
|
||||
${result.filesCreated.map((f) => ` ${f}`).join('\n')}
|
||||
|
||||
Next steps:
|
||||
${logger.cyan(`cd ${outputPath}`)}
|
||||
${logger.cyan(`cd ${defaultPath}`)}
|
||||
${logger.cyan('pnpm install')}
|
||||
${logger.cyan('pnpm build')}
|
||||
${logger.cyan('pnpm type-check')}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue