pit-manager
Version:
Centralized prompt management system for Human Behavior AI agents
275 lines (234 loc) • 8.59 kB
JavaScript
#\!/usr/bin/env node
/**
* PIT CLI - Node.js entry point
* This allows the CLI to work without Python
*/
const { program } = require('commander');
const path = require('path');
const fs = require('fs');
const { ExecutionTracker } = require('../storage/execution-tracker');
const { VersioningOperations } = require('../versioning/operations');
const { BranchManager } = require('../versioning/branches');
// Version from package.json
const packageJson = require('../../../package.json');
program
.name('pit')
.description('Git-like version control for AI prompts')
.version(packageJson.version);
program
.command('init')
.description('Initialize a new PIT repository')
.option('--force', 'Reinitialize existing repository')
.option('--online', 'Initialize with online storage backend')
.action(async (options) => {
try {
const repoPath = '.pit';
const versioning = new VersioningOperations(repoPath);
// Check if already initialized
if (fs.existsSync(path.join(repoPath, 'refs', 'HEAD')) && \!options.force) {
console.log(`PIT repository already exists in ${path.resolve(repoPath)}`);
console.log('Use --force to reinitialize');
process.exit(1);
}
// Create repository structure
await versioning.init(options.force);
console.log(`Initialized empty PIT repository in ${path.resolve(repoPath)}`);
console.log('\nCreated example files:');
console.log(' .pit/prompts/template.md - Example prompt template');
console.log(' example.js - JavaScript example using simplified API');
console.log(' example.ts - TypeScript example using simplified API');
if (options.online) {
console.log('\nOnline mode: Configure your .env file with:');
console.log(' PIT_REPO_KEY=your-repo-key');
console.log(' PIT_SUPABASE_URL=your-supabase-url');
console.log(' PIT_SUPABASE_KEY=your-supabase-key');
}
console.log('\nRun "pit --help" to see available commands');
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
});
program
.command('add <prompt>')
.description('Add a prompt to the staging area')
.option('-f, --file <file>', 'Read prompt from file')
.action(async (prompt, options) => {
try {
const repoPath = '.pit';
const versioning = new VersioningOperations(repoPath);
let content = prompt;
if (options.file) {
content = fs.readFileSync(options.file, 'utf8');
}
const hash = await versioning.addPrompt([{ role: 'user', content }]);
console.log(`Added prompt with hash: ${hash}`);
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
});
program
.command('commit')
.description('Commit staged prompts')
.option('-m, --message <message>', 'Commit message')
.action(async (options) => {
try {
const repoPath = '.pit';
const versioning = new VersioningOperations(repoPath);
const message = options.message || 'Commit prompts';
const commitHash = await versioning.commit(message);
console.log(`Created commit: ${commitHash}`);
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
});
program
.command('log')
.description('Show commit history')
.option('-n, --limit <number>', 'Limit number of commits', '10')
.action(async (options) => {
try {
const repoPath = '.pit';
const versioning = new VersioningOperations(repoPath);
const history = await versioning.getHistory(parseInt(options.limit));
if (history.length === 0) {
console.log('No commits yet');
return;
}
for (const commit of history) {
console.log(`commit ${commit.hash}`);
console.log(`Date: ${new Date(commit.timestamp).toLocaleString()}`);
console.log(`\n ${commit.message}\n`);
}
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
});
program
.command('status')
.description('Show repository status')
.action(async () => {
try {
const repoPath = '.pit';
const versioning = new VersioningOperations(repoPath);
const status = await versioning.status();
console.log('On branch:', status.branch || 'main');
if (status.staged && status.staged.length > 0) {
console.log('\nChanges to be committed:');
for (const item of status.staged) {
console.log(` ${item}`);
}
}
if (status.modified && status.modified.length > 0) {
console.log('\nModified files:');
for (const item of status.modified) {
console.log(` ${item}`);
}
}
if (\!status.staged?.length && \!status.modified?.length) {
console.log('Nothing to commit, working tree clean');
}
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
});
program
.command('branch [name]')
.description('List or create branches')
.option('-d, --delete', 'Delete branch')
.action(async (name, options) => {
try {
const repoPath = '.pit';
const branchManager = new BranchManager(repoPath);
if (\!name) {
// List branches
const branches = await branchManager.listBranches();
const current = await branchManager.getCurrentBranch();
for (const branch of branches) {
const prefix = branch === current ? '* ' : ' ';
console.log(`${prefix}${branch}`);
}
} else if (options.delete) {
// Delete branch
await branchManager.deleteBranch(name);
console.log(`Deleted branch ${name}`);
} else {
// Create branch
await branchManager.createBranch(name);
console.log(`Created branch ${name}`);
}
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
});
program
.command('checkout <branch>')
.description('Switch to a different branch')
.action(async (branch) => {
try {
const repoPath = '.pit';
const branchManager = new BranchManager(repoPath);
await branchManager.checkout(branch);
console.log(`Switched to branch '${branch}'`);
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
});
program
.command('analytics')
.description('Show execution analytics')
.option('--days <number>', 'Number of days to analyze', '7')
.action(async (options) => {
try {
const repoPath = '.pit';
const tracker = new ExecutionTracker(repoPath);
const executions = await tracker.getExecutions();
const days = parseInt(options.days);
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() - days);
const recent = executions.filter(e =>
new Date(e.timestamp) > cutoff
);
console.log(`Analytics for last ${days} days:`);
console.log(`Total executions: ${recent.length}`);
if (recent.length > 0) {
const totalTokens = recent.reduce((sum, e) => sum + (e.tokens || 0), 0);
const totalCost = recent.reduce((sum, e) => sum + (e.cost || 0), 0);
console.log(`Total tokens: ${totalTokens.toLocaleString()}`);
console.log(`Total cost: $${totalCost.toFixed(2)}`);
// Group by model
const byModel = {};
for (const exec of recent) {
const model = exec.model || 'unknown';
if (\!byModel[model]) {
byModel[model] = { count: 0, tokens: 0, cost: 0 };
}
byModel[model].count++;
byModel[model].tokens += exec.tokens || 0;
byModel[model].cost += exec.cost || 0;
}
console.log('\nBy model:');
for (const [model, stats] of Object.entries(byModel)) {
console.log(` ${model}:`);
console.log(` Executions: ${stats.count}`);
console.log(` Tokens: ${stats.tokens.toLocaleString()}`);
console.log(` Cost: $${stats.cost.toFixed(2)}`);
}
}
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
});
// Parse command line arguments
program.parse(process.argv);
// Show help if no command provided
if (\!process.argv.slice(2).length) {
program.outputHelp();
}