@meldscience/meld
Version:
pipeable one-shot prompt scripting toolkit
331 lines (279 loc) • 8.34 kB
text/typescript
import { spawn } from 'child_process';
import { join } from 'path';
import { writeFileSync, unlinkSync, existsSync } from 'fs';
import { beforeEach, afterEach, describe, it, expect } from 'vitest';
import { vi } from 'vitest';
const CLI_PATH = join(__dirname, '../../bin/oneshot.ts');
const TEST_PROMPT = 'test-prompt.md';
const TEST_OUTPUT = 'test-output.md';
const TEST_SYSTEM = 'test-system.md';
const TEST_VARIATIONS_JSON = 'test-variations.json';
const TEST_VARIATIONS_YAML = 'test-variations.yaml';
// Mock environment variables
process.env.ANTHROPIC_API_KEY = 'test-anthropic-key';
process.env.OPENAI_API_KEY = 'test-openai-key';
describe('oneshot CLI', () => {
beforeEach(() => {
// Create test files
writeFileSync(TEST_PROMPT, '# Test Prompt\n\nThis is a test prompt.');
writeFileSync(TEST_SYSTEM, 'You are a helpful assistant.');
writeFileSync(TEST_VARIATIONS_JSON, JSON.stringify(['Perspective 1', 'Perspective 2']));
writeFileSync(TEST_VARIATIONS_YAML, 'architect: "Review as architect"\ndeveloper: "Review as developer"');
});
afterEach(() => {
// Cleanup test files
[TEST_PROMPT, TEST_OUTPUT, TEST_SYSTEM, TEST_VARIATIONS_JSON, TEST_VARIATIONS_YAML].forEach(file => {
if (existsSync(file)) unlinkSync(file);
});
});
it('should send a simple prompt to Claude', (done) => {
const oneshot = spawn('ts-node', [CLI_PATH, 'claude-3', TEST_PROMPT]);
let output = '';
oneshot.stdout.on('data', (data) => {
output += data.toString();
});
oneshot.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
oneshot.on('close', (code) => {
expect(code).toBe(0);
expect(output).toContain('🤖 Sending prompt to claude-3');
done();
});
});
it('should support multiple models', (done) => {
const oneshot = spawn('ts-node', [CLI_PATH, 'claude-3', 'gpt-4', TEST_PROMPT]);
let output = '';
oneshot.stdout.on('data', (data) => {
output += data.toString();
});
oneshot.on('close', (code) => {
expect(code).toBe(0);
expect(output).toContain('🤖 Sending prompt to claude-3');
expect(output).toContain('🤖 Sending prompt to gpt-4');
done();
});
});
it('should support inline system prompt', (done) => {
const oneshot = spawn('ts-node', [
CLI_PATH,
'claude-3',
TEST_PROMPT,
'--system',
'You are a helpful assistant'
]);
let output = '';
oneshot.stdout.on('data', (data) => {
output += data.toString();
});
oneshot.on('close', (code) => {
expect(code).toBe(0);
expect(output).toContain('🤖 Sending prompt to claude-3');
done();
});
});
it('should support system prompts from file', (done) => {
const oneshot = spawn('ts-node', [
CLI_PATH,
'claude-3',
TEST_PROMPT,
'--system-file',
TEST_SYSTEM
]);
let output = '';
oneshot.stdout.on('data', (data) => {
output += data.toString();
});
oneshot.on('close', (code) => {
expect(code).toBe(0);
expect(output).toContain('🤖 Sending prompt to claude-3');
done();
});
});
it('should support inline variations', (done) => {
const oneshot = spawn('ts-node', [
CLI_PATH,
'claude-3',
TEST_PROMPT,
'--variations',
'["View 1", "View 2"]'
]);
let output = '';
oneshot.stdout.on('data', (data) => {
output += data.toString();
});
oneshot.on('close', (code) => {
expect(code).toBe(0);
expect(output).toContain('🤖 Sending prompt to claude-3');
done();
});
});
it('should support variations from JSON file', (done) => {
const oneshot = spawn('ts-node', [
CLI_PATH,
'claude-3',
TEST_PROMPT,
'--variations-file',
TEST_VARIATIONS_JSON
]);
let output = '';
oneshot.stdout.on('data', (data) => {
output += data.toString();
});
oneshot.on('close', (code) => {
expect(code).toBe(0);
expect(output).toContain('🤖 Sending prompt to claude-3');
done();
});
});
it('should support variations from YAML file', (done) => {
const oneshot = spawn('ts-node', [
CLI_PATH,
'claude-3',
TEST_PROMPT,
'--variations-file',
TEST_VARIATIONS_YAML
]);
let output = '';
oneshot.stdout.on('data', (data) => {
output += data.toString();
});
oneshot.on('close', (code) => {
expect(code).toBe(0);
expect(output).toContain('🤖 Sending prompt to claude-3');
done();
});
});
it('should support multiple iterations', (done) => {
const oneshot = spawn('ts-node', [
CLI_PATH,
'claude-3',
TEST_PROMPT,
'--iterations',
'3'
]);
let output = '';
oneshot.stdout.on('data', (data) => {
output += data.toString();
});
oneshot.on('close', (code) => {
expect(code).toBe(0);
expect(output).toContain('🤖 Sending prompt to claude-3');
done();
});
});
it('should support custom output file', (done) => {
const oneshot = spawn('ts-node', [
CLI_PATH,
'claude-3',
TEST_PROMPT,
'-o',
TEST_OUTPUT
]);
let output = '';
oneshot.stdout.on('data', (data) => {
output += data.toString();
});
oneshot.on('close', (code) => {
expect(code).toBe(0);
expect(existsSync(TEST_OUTPUT)).toBe(true);
expect(output).toContain('📝 Response written to');
done();
});
});
it('should handle missing prompt file error', (done) => {
const oneshot = spawn('ts-node', [CLI_PATH, 'claude-3', 'nonexistent.md']);
let stderr = '';
oneshot.stderr.on('data', (data) => {
stderr += data.toString();
});
oneshot.on('close', (code) => {
expect(code).toBe(1);
expect(stderr).toContain('ENOENT');
done();
});
});
it('should handle missing system file error', (done) => {
const oneshot = spawn('ts-node', [
CLI_PATH,
'claude-3',
TEST_PROMPT,
'--system-file',
'nonexistent.md'
]);
let stderr = '';
oneshot.stderr.on('data', (data) => {
stderr += data.toString();
});
oneshot.on('close', (code) => {
expect(code).toBe(1);
expect(stderr).toContain('ENOENT');
done();
});
});
it('should handle missing variations file error', (done) => {
const oneshot = spawn('ts-node', [
CLI_PATH,
'claude-3',
TEST_PROMPT,
'--variations-file',
'nonexistent.yaml'
]);
let stderr = '';
oneshot.stderr.on('data', (data) => {
stderr += data.toString();
});
oneshot.on('close', (code) => {
expect(code).toBe(1);
expect(stderr).toContain('ENOENT');
done();
});
});
it('should handle invalid JSON variations error', (done) => {
const oneshot = spawn('ts-node', [
CLI_PATH,
'claude-3',
TEST_PROMPT,
'--variations',
'invalid json'
]);
let stderr = '';
oneshot.stderr.on('data', (data) => {
stderr += data.toString();
});
oneshot.on('close', (code) => {
expect(code).toBe(1);
expect(stderr).toContain('JSON');
done();
});
});
it('should handle missing API key error', (done) => {
// Temporarily unset API key
const oldKey = process.env.ANTHROPIC_API_KEY;
delete process.env.ANTHROPIC_API_KEY;
const oneshot = spawn('ts-node', [CLI_PATH, 'claude-3', TEST_PROMPT]);
let stderr = '';
oneshot.stderr.on('data', (data) => {
stderr += data.toString();
});
oneshot.on('close', (code) => {
// Restore API key
process.env.ANTHROPIC_API_KEY = oldKey;
expect(code).toBe(1);
expect(stderr).toContain('Anthropic API key is required');
done();
});
});
it('should handle unsupported model error', (done) => {
const oneshot = spawn('ts-node', [CLI_PATH, 'unsupported-model', TEST_PROMPT]);
let stderr = '';
oneshot.stderr.on('data', (data) => {
stderr += data.toString();
});
oneshot.on('close', (code) => {
expect(code).toBe(1);
expect(stderr).toContain('Unsupported model');
done();
});
});
});