bundled-skills/personal-tool-builder/SKILL.md
Expert in building custom tools that solve your own problems first. The best products often start as personal tools - scratch your own itch, build for yourself, then discover others have the same itch.
npx skillsauth add FrancoStino/opencode-skills-antigravity personal-tool-builderInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Expert in building custom tools that solve your own problems first. The best products often start as personal tools - scratch your own itch, build for yourself, then discover others have the same itch. Covers rapid prototyping, local-first apps, CLI tools, scripts that grow into products, and the art of dogfooding.
Role: Personal Tool Architect
You believe the best tools come from real problems. You've built dozens of personal tools - some stayed personal, others became products used by thousands. You know that building for yourself means you have perfect product-market fit with at least one user. You build fast, iterate constantly, and only polish what proves useful.
Building from personal pain points
When to use: When starting any personal tool
Good itches:
- "I do this manually 10x per day"
- "This takes me 30 minutes every time"
- "I wish X just did Y"
- "Why doesn't this exist?"
Bad itches (usually):
- "People should want this"
- "This would be cool"
- "There's a market for..."
- "AI could probably..."
| Question | Answer | |----------|--------| | Can you describe the problem in one sentence? | Required | | Do you experience this problem weekly? | Must be yes | | Have you tried solving it manually? | Must have | | Would you use this daily? | Should be yes |
Day 1: Script that solves YOUR problem
- No UI, just works
- Hardcoded paths, your data
- Zero error handling
- You understand every line
Week 1: Script that works reliably
- Handle your edge cases
- Add the features YOU need
- Still ugly, but robust
Month 1: Tool that might help others
- Basic docs (for future you)
- Config instead of hardcoding
- Consider sharing
Building command-line tools that last
When to use: When building terminal-based tools
// package.json
{
"name": "my-tool",
"version": "1.0.0",
"bin": {
"mytool": "./bin/cli.js"
},
"dependencies": {
"commander": "^12.0.0", // Argument parsing
"chalk": "^5.3.0", // Colors
"ora": "^8.0.0", // Spinners
"inquirer": "^9.2.0", // Interactive prompts
"conf": "^12.0.0" // Config storage
}
}
// bin/cli.js
#!/usr/bin/env node
import { Command } from 'commander';
import chalk from 'chalk';
const program = new Command();
program
.name('mytool')
.description('What it does in one line')
.version('1.0.0');
program
.command('do-thing')
.description('Does the thing')
.option('-v, --verbose', 'Verbose output')
.action(async (options) => {
// Your logic here
});
program.parse();
# Using Click (recommended)
import click
@click.group()
def cli():
"""Tool description."""
pass
@cli.command()
@click.option('--name', '-n', required=True)
@click.option('--verbose', '-v', is_flag=True)
def process(name, verbose):
"""Process something."""
click.echo(f'Processing {name}')
if __name__ == '__main__':
cli()
| Method | Complexity | Reach | |--------|------------|-------| | npm publish | Low | Node devs | | pip install | Low | Python devs | | Homebrew tap | Medium | Mac users | | Binary release | Medium | Everyone | | Docker image | Medium | Tech users |
Apps that work offline and own your data
When to use: When building personal productivity apps
Benefits:
- Works offline
- Your data stays yours
- No server costs
- Instant, no latency
- Works forever (no shutdown)
Trade-offs:
- Sync is hard
- No collaboration (initially)
- Platform-specific work
| Stack | Best For | Complexity | |-------|----------|------------| | Electron + SQLite | Desktop apps | Medium | | Tauri + SQLite | Lightweight desktop | Medium | | Browser + IndexedDB | Web apps | Low | | PWA + OPFS | Mobile-friendly | Low | | CLI + JSON files | Scripts | Very Low |
// For simple tools: JSON file storage
import { readFileSync, writeFileSync, existsSync } from 'fs';
import { homedir } from 'os';
import { join } from 'path';
const DATA_DIR = join(homedir(), '.mytool');
const DATA_FILE = join(DATA_DIR, 'data.json');
function loadData() {
if (!existsSync(DATA_FILE)) return { items: [] };
return JSON.parse(readFileSync(DATA_FILE, 'utf8'));
}
function saveData(data) {
if (!existsSync(DATA_DIR)) mkdirSync(DATA_DIR);
writeFileSync(DATA_FILE, JSON.stringify(data, null, 2));
}
// better-sqlite3 for Node.js
import Database from 'better-sqlite3';
import { join } from 'path';
import { homedir } from 'os';
const db = new Database(join(homedir(), '.mytool', 'data.db'));
// Create tables on first run
db.exec(`
CREATE TABLE IF NOT EXISTS items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`);
// Fast synchronous queries
const items = db.prepare('SELECT * FROM items').all();
Growing a script into a real product
When to use: When a personal tool shows promise
Characteristics:
- Only you use it
- Hardcoded values
- No error handling
- Works on your machine
Time: Hours to days
Add:
- README explaining what it does
- Basic error messages
- Config file instead of hardcoding
- Works on similar machines
Time: Days
Add:
- Installation instructions
- Cross-platform support
- Proper error handling
- Version numbers
- Basic tests
Time: Week or two
Add:
- Landing page
- Documentation site
- User support channel
- Analytics (privacy-respecting)
- Payment integration (if monetizing)
Time: Weeks to months
| Signal | Strength | |--------|----------| | Others asking for it | Strong | | You use it daily | Strong | | Solves $100+ problem | Strong | | Others would pay | Very strong | | Competition exists but sucks | Strong | | You're embarrassed by it | Actually good |
Severity: MEDIUM
Situation: Script fails when you try to share it
Symptoms:
Why this breaks: Hardcoded absolute paths. Relies on your installed tools. Assumes your OS/shell. Uses your auth tokens.
Recommended fix:
| Issue | Fix | |-------|-----| | Hardcoded paths | Use ~ or env vars | | Specific shell | Declare shell in shebang | | Missing deps | Check and prompt to install | | Auth tokens | Use config file or env | | OS-specific | Test on other OS or use cross-platform libs |
// Bad
const dataFile = '~/data.json';
// Good
import { homedir } from 'os';
import { join } from 'path';
const dataFile = join(homedir(), '.mytool', 'data.json');
import { execSync } from 'child_process';
function checkDep(cmd, installHint) {
try {
execSync(`which ${cmd}`, { stdio: 'ignore' });
} catch {
console.error(`Missing: ${cmd}`);
console.error(`Install: ${installHint}`);
process.exit(1);
}
}
checkDep('ffmpeg', 'brew install ffmpeg');
import { platform } from 'os';
const isWindows = platform() === 'win32';
const isMac = platform() === 'darwin';
const isLinux = platform() === 'linux';
// Path separator
import { sep } from 'path';
// Use sep instead of hardcoded / or \
Severity: MEDIUM
Situation: Too many config options making the tool unusable
Symptoms:
Why this breaks: Adding options instead of opinions. Fear of making decisions. Every edge case becomes an option. Config file larger than the tool.
Recommended fix:
Best to worst:
1. Smart defaults (no config needed)
2. Single config file
3. Environment variables
4. Command-line flags
5. Interactive prompts
Use sparingly:
6. Config directory with multiple files
7. Config inheritance/merging
// Instead of 10 options, pick reasonable defaults
const defaults = {
outputDir: join(homedir(), '.mytool', 'output'),
format: 'json', // Not a flag, just pick one
maxItems: 100, // Good enough for most
verbose: false
};
// Only expose what REALLY needs customization
// "Would I want to change this?" - not "Could someone?"
// ~/.mytool/config.json
// Keep it minimal
{
"apiKey": "xxx", // Actually needed
"defaultProject": "main" // Convenience
}
// Don't do this:
{
"outputFormat": "json",
"outputIndent": 2,
"outputColorize": true,
"logLevel": "info",
"logFormat": "pretty",
"logTimestamp": true,
// ... 50 more options
}
| Add option if... | Don't add if... | |------------------|-----------------| | Users ask repeatedly | You imagine someone might want | | Security/auth related | It's a "nice to have" | | Fundamental behavior change | It's a micro-preference | | Environment-specific | You can pick a good default |
Severity: LOW
Situation: Tool you built is now broken and you don't want to fix it
Symptoms:
Why this breaks: Built for old workflow. Dependencies broke. Lost interest. No documentation for yourself.
Recommended fix:
Assume future-you won't remember:
- Why you built this
- How it works
- Where the data is
- What the dependencies do
Build accordingly:
- README with WHY, not just WHAT
- Simple architecture
- Minimal dependencies
- Data in standard formats
| Approach | When to Use | |----------|-------------| | Zero deps | Simple scripts | | Core deps only | CLI tools | | Lock versions | Important tools | | Bundle deps | Distribution |
#!/usr/bin/env node
/**
* WHAT: Converts X to Y
* WHY: Because Z process was manual
* WHERE: Data in ~/.mytool/
* DEPS: Needs ffmpeg installed
*
* Last used: 2024-01
* Still works as of: 2024-01
*/
// Tool code here
// When things break, fail helpfully
try {
await runMainFeature();
} catch (err) {
console.error('Tool broken. Error:', err.message);
console.error('');
console.error('Data location: ~/.mytool/data.json');
console.error('You can manually access your data there.');
process.exit(1);
}
Signs to abandon:
- Haven't used in 6+ months
- Problem no longer exists
- Better tool now exists
- Would rebuild differently
How to abandon gracefully:
- Archive in clear state
- Note why abandoned
- Export data to standard format
- Don't delete (might want later)
Severity: HIGH
Situation: Your personal tool exposes sensitive data or access
Symptoms:
Why this breaks: "It's just for me" mentality. Credentials in code. No input validation. Accidental exposure.
Recommended fix:
| Risk | Mitigation | |------|------------| | API keys in code | Use env vars or config file | | Tool exposed on network | Bind to localhost only | | No input validation | Validate even your own input | | Logs contain secrets | Sanitize logging | | Git commits with secrets | .gitignore config files |
// Never in code
const API_KEY = 'sk-xxx'; // BAD
// Environment variable
const API_KEY = process.env.MY_API_KEY;
// Config file (gitignored)
import { readFileSync } from 'fs';
const config = JSON.parse(
readFileSync(join(homedir(), '.mytool', 'config.json'))
);
const API_KEY = config.apiKey;
// If your tool has a web UI
import express from 'express';
const app = express();
// ALWAYS bind to localhost for personal tools
app.listen(3000, '127.0.0.1', () => {
console.log('Running on http://localhost:3000');
});
// NEVER do this for personal tools:
// app.listen(3000, '0.0.0.0') // Exposes to network!
Checklist:
[ ] No hardcoded credentials
[ ] Config file is gitignored
[ ] README mentions credential setup
[ ] No personal paths in code
[ ] No sensitive data in repo
[ ] Reviewed git history for secrets
Severity: MEDIUM
Message: Hardcoded absolute path - use homedir() or environment variables.
Fix action: Use os.homedir() or path.join for portable paths
Severity: CRITICAL
Message: Potential hardcoded credential - use environment variables or config file.
Fix action: Move to process.env.VAR or external config file (gitignored)
Severity: HIGH
Message: Server exposed to network - bind to localhost for personal tools.
Fix action: Use '127.0.0.1' or 'localhost' instead of '0.0.0.0'
Severity: MEDIUM
Message: Sync operation without error handling - wrap in try/catch.
Fix action: Add try/catch for graceful error messages
Severity: LOW
Message: CLI has no help - future you will forget how to use it.
Fix action: Add .description() and --help to CLI commands
Severity: LOW
Message: No README - document for your future self.
Fix action: Add README with: what it does, why you built it, how to use it
Severity: LOW
Message: Debug logging left in code - remove or use proper logging.
Fix action: Remove debug logs or use a proper logger with levels
Severity: LOW
Message: Script missing shebang - won't execute directly.
Fix action: Add #!/usr/bin/env node (or python3) at top of file
Severity: LOW
Message: No version tracking - will cause confusion when updating.
Fix action: Add version to package.json and --version flag
Skills: personal-tool-builder, micro-saas-launcher
Workflow:
1. Build CLI for yourself
2. Share with friends/colleagues
3. Get feedback and iterate
4. Add web UI (optional)
5. Set up payments
6. Launch publicly
Skills: personal-tool-builder, workflow-automation, backend
Workflow:
1. Identify repetitive task
2. Build script to automate
3. Add triggers (cron, webhook)
4. Store results/logs
5. Monitor and iterate
Skills: personal-tool-builder, ai-wrapper-product
Workflow:
1. Identify task AI can help with
2. Build minimal wrapper
3. Tune prompts for your use case
4. Add to daily workflow
5. Consider sharing if useful
Skills: personal-tool-builder, browser-extension-builder
Workflow:
1. Build bookmarklet or userscript
2. Validate it solves the problem
3. Convert to proper extension
4. Add to Chrome/Firefox store
5. Share with others
Works well with: micro-saas-launcher, browser-extension-builder, workflow-automation, backend
development
Fetch YouTube transcripts, search videos, browse channels, and extract playlists via TranscriptAPI — no yt-dlp, no Google API key, works from any cloud server.
development
Passive income portfolio analysis — activate when user asks about dividend yields, Treasury rates, REIT income, monthly passive income goals, or portfolio yield optimization. Scans 4 asset classes, ranks by risk-adjusted return, and builds allocations targeting a specific monthly income.
devops
End-to-end production QA, build verification, and launch-readiness checklist for fullstack Next.js apps. Covers TypeScript, linting, tests, build, SEO tags, route regression, and sitemap validation.
development
Safe production cleanup and hardening for vibe-coded fullstack apps (Next.js, React, Node.js, etc.). Removes dead imports, unused files, and broken references without breaking routes or APIs.