Move docs to project-docs symlink structure
- Docs now point to project-docs/docs/projects/cmdforge - Old docs preserved in olddocs/ - Updated .gitignore to ignore docs symlink 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,425 @@
|
||||
# CmdForge - Design Document
|
||||
|
||||
> A lightweight personal tool builder for AI-powered CLI commands
|
||||
|
||||
## Overview
|
||||
|
||||
CmdForge lets you create custom AI-powered terminal commands. You define a tool once (name, steps, provider), then use it like any Linux command.
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
# Create a summarizer tool, then use it:
|
||||
sum -i text.txt -o summary.txt --max 512
|
||||
```
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Tool = Directory + Config
|
||||
|
||||
```
|
||||
~/.cmdforge/
|
||||
sum/
|
||||
config.yaml
|
||||
processed.py # Optional external code file
|
||||
reviewer/
|
||||
config.yaml
|
||||
translator/
|
||||
config.yaml
|
||||
```
|
||||
|
||||
### config.yaml Format
|
||||
|
||||
```yaml
|
||||
name: sum
|
||||
description: "Summarize documents"
|
||||
arguments:
|
||||
- flag: --max
|
||||
variable: max
|
||||
default: "500"
|
||||
description: "Maximum words in summary"
|
||||
steps:
|
||||
- type: prompt
|
||||
prompt: |
|
||||
Summarize the following text in {max} words or less:
|
||||
|
||||
{input}
|
||||
provider: claude
|
||||
output_var: response
|
||||
output: "{response}"
|
||||
|
||||
# Optional: declare dependencies for meta-tools
|
||||
dependencies:
|
||||
- official/summarize
|
||||
- official/translate@^1.0.0 # With version constraint
|
||||
```
|
||||
|
||||
### Step Types
|
||||
|
||||
**Prompt Step** - Calls an AI provider:
|
||||
```yaml
|
||||
- type: prompt
|
||||
prompt: "Your prompt template with {variables}"
|
||||
provider: claude
|
||||
output_var: response
|
||||
```
|
||||
|
||||
**Code Step** - Runs Python code:
|
||||
```yaml
|
||||
- type: code
|
||||
code: |
|
||||
processed = input.upper()
|
||||
count = len(processed.split())
|
||||
output_var: processed, count
|
||||
code_file: processed.py # Optional: external file storage
|
||||
```
|
||||
|
||||
**Tool Step** - Calls another CmdForge tool (meta-tools):
|
||||
```yaml
|
||||
- type: tool
|
||||
tool: official/summarize # Tool reference (owner/name or just name for local)
|
||||
input: "{input}" # Input to pass (supports variable substitution)
|
||||
args: # Optional arguments
|
||||
max_words: "100"
|
||||
output_var: summary # Variable to store the tool's output
|
||||
provider: claude # Optional: override the called tool's provider
|
||||
```
|
||||
|
||||
Steps execute in order. Each step's `output_var` becomes available to subsequent steps.
|
||||
|
||||
### Variables
|
||||
|
||||
- `{input}` - Always available, contains stdin or input file content (empty string if no input)
|
||||
- `{variable_name}` - From arguments (e.g., `{max}`)
|
||||
- `{output_var}` - From previous steps (e.g., `{response}`, `{processed}`)
|
||||
|
||||
### Output Variables
|
||||
|
||||
The `output_var` field specifies which Python variable(s) to capture from your code:
|
||||
|
||||
**Single variable:**
|
||||
```yaml
|
||||
output_var: processed
|
||||
```
|
||||
```python
|
||||
processed = input.upper() # This gets captured
|
||||
```
|
||||
|
||||
**Multiple variables (comma-separated):**
|
||||
```yaml
|
||||
output_var: processed, count, summary
|
||||
```
|
||||
```python
|
||||
processed = input.upper()
|
||||
count = len(processed.split())
|
||||
summary = f"Processed {count} words"
|
||||
# All three are captured and available as {processed}, {count}, {summary}
|
||||
```
|
||||
|
||||
## CLI Interface
|
||||
|
||||
### Running Tools
|
||||
|
||||
```bash
|
||||
# Basic usage (wrapper script in ~/.local/bin)
|
||||
sum -i document.txt -o summary.txt
|
||||
|
||||
# With custom args
|
||||
sum -i document.txt --max 200
|
||||
|
||||
# Preview prompt without calling AI
|
||||
sum -i document.txt --dry-run
|
||||
|
||||
# See what prompt gets sent
|
||||
sum -i document.txt --show-prompt
|
||||
|
||||
# Test with mock (no API call)
|
||||
sum -i document.txt --provider mock
|
||||
|
||||
# Read from stdin, write to stdout
|
||||
cat doc.txt | sum | less
|
||||
|
||||
# Interactive stdin input
|
||||
sum --stdin
|
||||
|
||||
# No input (empty string) - useful for tools using only arguments
|
||||
sum --max 100
|
||||
|
||||
# Or via cmdforge run
|
||||
cmdforge run sum -i document.txt
|
||||
```
|
||||
|
||||
### Input Handling
|
||||
|
||||
| Scenario | Behavior |
|
||||
|----------|----------|
|
||||
| Piped stdin | Automatically read (`cat file.txt \| mytool`) |
|
||||
| `-i file.txt` | Read from file |
|
||||
| `--stdin` | Interactive input (type then Ctrl+D) |
|
||||
| No input | Empty string (useful for argument-only tools) |
|
||||
|
||||
### Universal Flags (all tools)
|
||||
|
||||
| Flag | Short | Description |
|
||||
|------|-------|-------------|
|
||||
| `--input` | `-i` | Input file |
|
||||
| `--output` | `-o` | Output file (or stdout if omitted) |
|
||||
| `--stdin` | | Read input interactively (type then Ctrl+D) |
|
||||
| `--dry-run` | | Show prompt, don't call AI |
|
||||
| `--show-prompt` | | Call AI but also print prompt to stderr |
|
||||
| `--provider` | `-p` | Override provider (e.g., `--provider mock`) |
|
||||
| `--verbose` | `-v` | Show debug info |
|
||||
| `--help` | `-h` | Show help |
|
||||
|
||||
### Managing Tools
|
||||
|
||||
```bash
|
||||
# Launch the UI to manage tools
|
||||
cmdforge
|
||||
|
||||
# Or use CLI directly:
|
||||
cmdforge list # List all tools
|
||||
cmdforge create sum # Create new tool (basic)
|
||||
cmdforge edit sum # Edit tool config in $EDITOR
|
||||
cmdforge delete sum # Delete tool
|
||||
cmdforge test sum # Test with mock provider
|
||||
cmdforge run sum # Run tool for real
|
||||
cmdforge refresh # Refresh all wrapper scripts
|
||||
cmdforge check sum # Check dependencies for meta-tools
|
||||
cmdforge ui # Launch interactive UI
|
||||
```
|
||||
|
||||
## Terminal UI
|
||||
|
||||
A BIOS-style terminal UI using `urwid` with full mouse support.
|
||||
|
||||
### Main Menu
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────┐
|
||||
│ CmdForge Manager │
|
||||
├──────────────────────────────────────┤
|
||||
│ Tools: │
|
||||
│ ┌────────────────────────────────┐ │
|
||||
│ │ sum │ │
|
||||
│ │ reviewer │ │
|
||||
│ │ translator │ │
|
||||
│ └────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [Create] [Edit] [Delete] [Test] │
|
||||
│ │
|
||||
│ Tool Info: │
|
||||
│ Name: sum │
|
||||
│ Description: Summarize documents │
|
||||
│ Arguments: --max │
|
||||
│ Steps: PROMPT[claude] -> {response} │
|
||||
│ │
|
||||
│ [EXIT] │
|
||||
└──────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Tool Builder
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ New Tool │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ ┌─[ Tool Info ]──────────┐ ┌─ Arguments ─────────────────┐ │
|
||||
│ │ Name: [sum__________] │ │ --max -> {max} │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ Desc: [Summarize____] │ └─────────────────────────────┘ │
|
||||
│ │ │ [Add] [Edit] [Delete] │
|
||||
│ │ Output: [{response}_] │ │
|
||||
│ └────────────────────────┘ ┌─ Execution Steps ───────────┐ │
|
||||
│ │ P:claude -> {response} │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────┘ │
|
||||
│ [Add] [Edit] [Delete] │
|
||||
│ │
|
||||
│ [Save] [Cancel] │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Navigation:**
|
||||
- **Tab** - Cycle between sections (Tool Info -> Arguments -> Steps -> Buttons)
|
||||
- **Arrow keys** - Navigate within lists
|
||||
- **Enter/Click** - Select/activate
|
||||
- **Escape** - Cancel/back
|
||||
- **Shift + mouse** - Terminal-native text selection (bypasses UI mouse handling)
|
||||
- **Ctrl+Shift+V** - Paste
|
||||
|
||||
The current section's title is highlighted with brackets: `[ Tool Info ]`
|
||||
|
||||
**Copy/Paste tip:** Hold `Shift` while dragging with mouse to select text, then use your terminal's copy function (usually right-click or Ctrl+Shift+C).
|
||||
|
||||
### Code Step Dialog
|
||||
|
||||
```
|
||||
┌──────────────── Add Code Step ─────────────────┐
|
||||
│ Variables: input, max, response │
|
||||
│ │
|
||||
│ File: [processed.py______________] < Load > │
|
||||
│ │
|
||||
│ ┌─ Code ─────────────────────────────────────┐ │
|
||||
│ │processed = input.upper() │ │
|
||||
│ │count = len(processed.split()) │ │
|
||||
│ │ │ │
|
||||
│ └────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Output var: [processed, count____] │
|
||||
├────────────────────────────────────────────────┤
|
||||
│ < OK > < Cancel > │
|
||||
└────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Code Step Features:**
|
||||
- **Multiline editor** - Write multi-line Python code
|
||||
- **External file storage** - Code is auto-saved to `~/.cmdforge/<toolname>/<filename>` on OK
|
||||
- **Load button** - Load code from external file (with confirmation)
|
||||
- **Multiple output vars** - Capture multiple variables (comma-separated)
|
||||
|
||||
## Implementation
|
||||
|
||||
### Directory Structure
|
||||
|
||||
```
|
||||
cmdforge/
|
||||
__init__.py
|
||||
cli.py # Entry point, argument parsing
|
||||
ui.py # UI dispatcher (chooses urwid/snack/dialog)
|
||||
ui_urwid.py # Urwid-based BIOS-style UI
|
||||
ui_snack.py # Snack/newt fallback UI
|
||||
tool.py # Tool loading/saving/wrapper scripts
|
||||
runner.py # Execute tools
|
||||
providers.py # Provider abstraction
|
||||
```
|
||||
|
||||
### Provider System
|
||||
|
||||
Providers are defined in `~/.cmdforge/providers.yaml`:
|
||||
|
||||
```yaml
|
||||
providers:
|
||||
- name: claude
|
||||
command: "claude -p"
|
||||
description: "Anthropic Claude"
|
||||
- name: codex
|
||||
command: "codex -p"
|
||||
description: "OpenAI Codex"
|
||||
- name: gemini
|
||||
command: "gemini"
|
||||
description: "Google Gemini"
|
||||
```
|
||||
|
||||
The provider command receives the prompt via stdin and outputs to stdout.
|
||||
|
||||
### Wrapper Scripts
|
||||
|
||||
When you save a tool, CmdForge creates a wrapper script in `~/.local/bin/`:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# CmdForge wrapper for 'sum'
|
||||
# Auto-generated - do not edit
|
||||
exec /path/to/python -m cmdforge.runner sum "$@"
|
||||
```
|
||||
|
||||
The wrapper uses the full Python path to ensure the correct environment is used.
|
||||
|
||||
Use `cmdforge refresh` to regenerate all wrapper scripts (e.g., after changing Python environments).
|
||||
|
||||
### Tool Name Validation
|
||||
|
||||
Tool names must:
|
||||
- Start with a letter or underscore
|
||||
- Contain only letters, numbers, underscores, and dashes
|
||||
- Not contain spaces or shell-problematic characters (`/\|&;$`"'<>(){}[]!?*#~`)
|
||||
|
||||
## Tool Composition
|
||||
|
||||
CmdForge are designed to chain together like any Unix command.
|
||||
|
||||
### External Pipelines (Tool-to-Tool)
|
||||
|
||||
```bash
|
||||
# Chain multiple CmdForge
|
||||
cat logs.txt | log-errors | summarize | translate --lang Japanese
|
||||
|
||||
# Mix with standard Unix tools
|
||||
git log --oneline | head -20 | changelog | tee CHANGELOG.md
|
||||
|
||||
# Build complex workflows
|
||||
cat *.py | review-code --focus security | json-extract --fields "issue, severity, file" | json2csv
|
||||
```
|
||||
|
||||
Each tool reads stdin and writes stdout. No special integration needed.
|
||||
|
||||
### Internal Pipelines (Multi-Step)
|
||||
|
||||
Within a single tool, chain steps for preprocessing/validation:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- type: code # Preprocess
|
||||
code: |
|
||||
filtered = '\n'.join(l for l in input.split('\n') if 'ERROR' in l)
|
||||
output_var: filtered
|
||||
- type: prompt # AI processes filtered input
|
||||
prompt: "Explain these errors: {filtered}"
|
||||
provider: claude-haiku
|
||||
output_var: explanation
|
||||
- type: code # Post-process
|
||||
code: |
|
||||
result = f"Found {len(filtered.split(chr(10)))} errors:\n\n{explanation}"
|
||||
output_var: result
|
||||
output: "{result}"
|
||||
```
|
||||
|
||||
**When to use which:**
|
||||
- **External pipelines**: Reusable tools, different providers per stage, standard Unix interop
|
||||
- **Internal pipelines**: Tightly coupled steps, shared context, validation of AI output
|
||||
|
||||
## What This Design Doesn't Include
|
||||
|
||||
Intentionally omitted (not needed for personal use):
|
||||
|
||||
- Trust tiers / security levels
|
||||
- Cryptographic signing
|
||||
- Container isolation / sandboxing
|
||||
- Certification testing
|
||||
- Distribution packaging
|
||||
- PII redaction
|
||||
- Audit logging
|
||||
- Provider capability negotiation
|
||||
|
||||
**Why?** This is a personal tool builder. You write the tools, you run the tools, you accept the responsibility. Just like any bash script you write.
|
||||
|
||||
## Dependencies
|
||||
|
||||
Required:
|
||||
- Python 3.10+
|
||||
- PyYAML
|
||||
- urwid (for TUI)
|
||||
|
||||
Optional fallbacks:
|
||||
- python3-newt/snack (simpler TUI)
|
||||
- dialog/whiptail (basic TUI)
|
||||
|
||||
## Example Workflow
|
||||
|
||||
1. Run `cmdforge` to open UI
|
||||
2. Select "Create" to create a new tool
|
||||
3. Fill in: name, description, output template
|
||||
4. Add arguments (e.g., `--max` with default `500`)
|
||||
5. Add a prompt step with your prompt template and provider
|
||||
6. Click "Save"
|
||||
7. Exit UI
|
||||
8. Run `sum -i myfile.txt -o summary.txt`
|
||||
|
||||
Done. No containers, no signing, no certification. Just a tool that works.
|
||||
|
||||
## Default Values
|
||||
|
||||
| Step Type | Default Output Var | Default File |
|
||||
|-----------|-------------------|--------------|
|
||||
| Prompt | `response` | n/a |
|
||||
| Code | `processed` | `processed.py` |
|
||||
Reference in New Issue
Block a user