feat(skills): add pdf-reader skill (#772)
Thanks @glifocat! Clean skill package — good docs, solid tests, nice intent files. Pushed a small fix for path traversal on the PDF filename before merging.
This commit is contained in:
171
.claude/skills/add-pdf-reader/tests/pdf-reader.test.ts
Normal file
171
.claude/skills/add-pdf-reader/tests/pdf-reader.test.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
describe('pdf-reader skill package', () => {
|
||||
const skillDir = path.resolve(__dirname, '..');
|
||||
|
||||
it('has a valid manifest', () => {
|
||||
const manifestPath = path.join(skillDir, 'manifest.yaml');
|
||||
expect(fs.existsSync(manifestPath)).toBe(true);
|
||||
|
||||
const content = fs.readFileSync(manifestPath, 'utf-8');
|
||||
expect(content).toContain('skill: add-pdf-reader');
|
||||
expect(content).toContain('version: 1.1.0');
|
||||
expect(content).toContain('container/Dockerfile');
|
||||
});
|
||||
|
||||
it('has all files declared in adds', () => {
|
||||
const skillMd = path.join(skillDir, 'add', 'container', 'skills', 'pdf-reader', 'SKILL.md');
|
||||
const pdfReaderScript = path.join(skillDir, 'add', 'container', 'skills', 'pdf-reader', 'pdf-reader');
|
||||
|
||||
expect(fs.existsSync(skillMd)).toBe(true);
|
||||
expect(fs.existsSync(pdfReaderScript)).toBe(true);
|
||||
});
|
||||
|
||||
it('pdf-reader script is a valid Bash script', () => {
|
||||
const scriptPath = path.join(skillDir, 'add', 'container', 'skills', 'pdf-reader', 'pdf-reader');
|
||||
const content = fs.readFileSync(scriptPath, 'utf-8');
|
||||
|
||||
// Valid shell script
|
||||
expect(content).toMatch(/^#!/);
|
||||
|
||||
// Core CLI commands
|
||||
expect(content).toContain('pdftotext');
|
||||
expect(content).toContain('pdfinfo');
|
||||
expect(content).toContain('extract');
|
||||
expect(content).toContain('fetch');
|
||||
expect(content).toContain('info');
|
||||
expect(content).toContain('list');
|
||||
|
||||
// Key options
|
||||
expect(content).toContain('--layout');
|
||||
expect(content).toContain('--pages');
|
||||
});
|
||||
|
||||
it('container skill SKILL.md has correct frontmatter', () => {
|
||||
const skillMdPath = path.join(skillDir, 'add', 'container', 'skills', 'pdf-reader', 'SKILL.md');
|
||||
const content = fs.readFileSync(skillMdPath, 'utf-8');
|
||||
|
||||
expect(content).toContain('name: pdf-reader');
|
||||
expect(content).toContain('allowed-tools: Bash(pdf-reader:*)');
|
||||
expect(content).toContain('pdf-reader extract');
|
||||
expect(content).toContain('pdf-reader fetch');
|
||||
expect(content).toContain('pdf-reader info');
|
||||
});
|
||||
|
||||
it('has all files declared in modifies', () => {
|
||||
const dockerfile = path.join(skillDir, 'modify', 'container', 'Dockerfile');
|
||||
const whatsappTs = path.join(skillDir, 'modify', 'src', 'channels', 'whatsapp.ts');
|
||||
const whatsappTestTs = path.join(skillDir, 'modify', 'src', 'channels', 'whatsapp.test.ts');
|
||||
|
||||
expect(fs.existsSync(dockerfile)).toBe(true);
|
||||
expect(fs.existsSync(whatsappTs)).toBe(true);
|
||||
expect(fs.existsSync(whatsappTestTs)).toBe(true);
|
||||
});
|
||||
|
||||
it('has intent files for all modified files', () => {
|
||||
expect(
|
||||
fs.existsSync(path.join(skillDir, 'modify', 'container', 'Dockerfile.intent.md')),
|
||||
).toBe(true);
|
||||
expect(
|
||||
fs.existsSync(path.join(skillDir, 'modify', 'src', 'channels', 'whatsapp.ts.intent.md')),
|
||||
).toBe(true);
|
||||
expect(
|
||||
fs.existsSync(
|
||||
path.join(skillDir, 'modify', 'src', 'channels', 'whatsapp.test.ts.intent.md'),
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('modified Dockerfile includes poppler-utils and pdf-reader', () => {
|
||||
const content = fs.readFileSync(
|
||||
path.join(skillDir, 'modify', 'container', 'Dockerfile'),
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
expect(content).toContain('poppler-utils');
|
||||
expect(content).toContain('pdf-reader');
|
||||
expect(content).toContain('/usr/local/bin/pdf-reader');
|
||||
});
|
||||
|
||||
it('modified Dockerfile preserves core structure', () => {
|
||||
const content = fs.readFileSync(
|
||||
path.join(skillDir, 'modify', 'container', 'Dockerfile'),
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
expect(content).toContain('FROM node:22-slim');
|
||||
expect(content).toContain('chromium');
|
||||
expect(content).toContain('agent-browser');
|
||||
expect(content).toContain('WORKDIR /app');
|
||||
expect(content).toContain('COPY agent-runner/');
|
||||
expect(content).toContain('ENTRYPOINT');
|
||||
expect(content).toContain('/workspace/group');
|
||||
expect(content).toContain('USER node');
|
||||
});
|
||||
|
||||
it('modified whatsapp.ts includes PDF attachment handling', () => {
|
||||
const content = fs.readFileSync(
|
||||
path.join(skillDir, 'modify', 'src', 'channels', 'whatsapp.ts'),
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
expect(content).toContain('documentMessage');
|
||||
expect(content).toContain('application/pdf');
|
||||
expect(content).toContain('downloadMediaMessage');
|
||||
expect(content).toContain('attachments');
|
||||
expect(content).toContain('pdf-reader extract');
|
||||
});
|
||||
|
||||
it('modified whatsapp.ts preserves core structure', () => {
|
||||
const content = fs.readFileSync(
|
||||
path.join(skillDir, 'modify', 'src', 'channels', 'whatsapp.ts'),
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
// Core class and methods preserved
|
||||
expect(content).toContain('class WhatsAppChannel');
|
||||
expect(content).toContain('implements Channel');
|
||||
expect(content).toContain('async connect()');
|
||||
expect(content).toContain('async sendMessage(');
|
||||
expect(content).toContain('isConnected()');
|
||||
expect(content).toContain('ownsJid(');
|
||||
expect(content).toContain('async disconnect()');
|
||||
expect(content).toContain('async setTyping(');
|
||||
|
||||
// Core imports preserved
|
||||
expect(content).toContain('ASSISTANT_NAME');
|
||||
expect(content).toContain('STORE_DIR');
|
||||
});
|
||||
|
||||
it('modified whatsapp.test.ts includes PDF attachment tests', () => {
|
||||
const content = fs.readFileSync(
|
||||
path.join(skillDir, 'modify', 'src', 'channels', 'whatsapp.test.ts'),
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
expect(content).toContain('PDF');
|
||||
expect(content).toContain('documentMessage');
|
||||
expect(content).toContain('application/pdf');
|
||||
});
|
||||
|
||||
it('modified whatsapp.test.ts preserves all existing test sections', () => {
|
||||
const content = fs.readFileSync(
|
||||
path.join(skillDir, 'modify', 'src', 'channels', 'whatsapp.test.ts'),
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
// All existing test describe blocks preserved
|
||||
expect(content).toContain("describe('connection lifecycle'");
|
||||
expect(content).toContain("describe('authentication'");
|
||||
expect(content).toContain("describe('reconnection'");
|
||||
expect(content).toContain("describe('message handling'");
|
||||
expect(content).toContain("describe('LID to JID translation'");
|
||||
expect(content).toContain("describe('outgoing message queue'");
|
||||
expect(content).toContain("describe('group metadata sync'");
|
||||
expect(content).toContain("describe('ownsJid'");
|
||||
expect(content).toContain("describe('setTyping'");
|
||||
expect(content).toContain("describe('channel properties'");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user