feat: implement skills system for AI agents (#16865)

## Summary
This PR introduces a Skills system for AI agents, inspired by the [Agent
Skills specification](https://agentskills.io/specification).

## Changes

### Backend
- **SkillEntity**: New database entity with migration for storing skills
- **V2 Sync Mechanism**: Implemented FlatSkill, builders, validators,
and action handlers following the v2 flat entity pattern
- **Standard Skills**: Pre-defined skills (workflow-building,
data-manipulation, dashboard-building, metadata-building, research,
code-interpreter, xlsx, pdf, docx, pptx)
- **GraphQL API**: CRUD operations for skills with proper guards and
permissions
- **Workspace Cache**: Integrated skills into the workspace cache system

### Frontend  
- **Skills Table**: Searchable table in AI settings showing all skills
- **Skill Form**: Create/edit page with Label (primary), Description,
and Content (markdown editor)
- **API Name**: Following existing patterns, name is derived from label
with advanced settings toggle for custom API names
- **Standard vs Custom**: Standard skills are read-only, custom skills
can be edited/deleted

## Key Design Decisions
- Skills are stored in the database (Salesforce-like approach) rather
than files
- Name is derived from Label by default (isLabelSyncedWithName pattern)
- Skills reference functions/files via @ mentions in markdown content
rather than explicit relations
- Standard skills are synced from code, custom skills are created via UI

## Screenshots
Skills table and form UI follow existing settings patterns.

## Testing
- [x] Lint passes
- [x] Typecheck passes
- [ ] CI tests
This commit is contained in:
Félix Malfait
2026-01-02 15:22:01 +01:00
committed by GitHub
parent 2a3fd788ae
commit 21ff42074d
146 changed files with 6964 additions and 1282 deletions
@@ -1,6 +0,0 @@
export type SkillDefinition = {
name: string;
label: string;
description: string;
content: string;
};
@@ -1,9 +0,0 @@
import { Module } from '@nestjs/common';
import { SkillsService } from './skills.service';
@Module({
providers: [SkillsService],
exports: [SkillsService],
})
export class SkillsModule {}
@@ -1,66 +0,0 @@
import { Injectable } from '@nestjs/common';
import { SkillDefinition } from 'src/engine/core-modules/skills/skill-definition.type';
import { CODE_INTERPRETER_SKILL } from 'src/engine/core-modules/skills/skills/code-interpreter.skill';
import { DASHBOARD_BUILDING_SKILL } from 'src/engine/core-modules/skills/skills/dashboard-building.skill';
import { DATA_MANIPULATION_SKILL } from 'src/engine/core-modules/skills/skills/data-manipulation.skill';
import { DOCX_SKILL } from 'src/engine/core-modules/skills/skills/docx.skill';
import { METADATA_BUILDING_SKILL } from 'src/engine/core-modules/skills/skills/metadata-building.skill';
import { PDF_SKILL } from 'src/engine/core-modules/skills/skills/pdf.skill';
import { PPTX_SKILL } from 'src/engine/core-modules/skills/skills/pptx.skill';
import { RESEARCH_SKILL } from 'src/engine/core-modules/skills/skills/research.skill';
import { WORKFLOW_BUILDING_SKILL } from 'src/engine/core-modules/skills/skills/workflow-building.skill';
import { XLSX_SKILL } from 'src/engine/core-modules/skills/skills/xlsx.skill';
const SKILL_DEFINITIONS: SkillDefinition[] = [
WORKFLOW_BUILDING_SKILL,
DATA_MANIPULATION_SKILL,
DASHBOARD_BUILDING_SKILL,
METADATA_BUILDING_SKILL,
RESEARCH_SKILL,
CODE_INTERPRETER_SKILL,
XLSX_SKILL,
PDF_SKILL,
DOCX_SKILL,
PPTX_SKILL,
];
export type Skill = {
name: string;
label: string;
description: string;
content: string;
};
@Injectable()
export class SkillsService {
getAllSkills(): Skill[] {
return SKILL_DEFINITIONS.map((skill) => ({
name: skill.name,
label: skill.label,
description: skill.description,
content: skill.content,
}));
}
getSkillByName(name: string): Skill | undefined {
const skillDef = SKILL_DEFINITIONS.find((skill) => skill.name === name);
if (!skillDef) {
return undefined;
}
return {
name: skillDef.name,
label: skillDef.label,
description: skillDef.description,
content: skillDef.content,
};
}
getSkillsByNames(names: string[]): Skill[] {
return names
.map((name) => this.getSkillByName(name))
.filter((skill): skill is Skill => skill !== undefined);
}
}
@@ -1,105 +0,0 @@
import { type SkillDefinition } from 'src/engine/core-modules/skills/skill-definition.type';
export const CODE_INTERPRETER_SKILL: SkillDefinition = {
name: 'code-interpreter',
label: 'Code Interpreter',
description:
'Python code execution for data analysis, complex multi-step operations, and efficient bulk processing via MCP bridge',
content: `# Code Interpreter Skill
You have access to the \`code_interpreter\` tool to execute Python code in a sandboxed environment.
## How to Use
Call the \`code_interpreter\` tool with your Python code. The tool will execute the code and return stdout, stderr, and any generated files.
## Capabilities
- Analyze CSV, Excel, and JSON data files
- Create charts and visualizations (matplotlib, seaborn)
- Generate reports (PDF, PPTX, Excel)
- Perform calculations and data transformations
## Pre-installed Libraries
pandas, numpy, matplotlib, seaborn, scikit-learn, openpyxl, python-pptx
## Input Files
- User-uploaded files are available at \`/home/user/{filename}\`
- Always check the file exists before processing
## Output Files
- Charts: Save to \`/home/user/output/\` directory - these are automatically returned as downloadable URLs
- For matplotlib: \`plt.savefig('/home/user/output/chart.png')\`
- Generated files: Save to \`/home/user/output/{filename}\`
## Example: Create a Bar Chart
\`\`\`python
import matplotlib.pyplot as plt
import os
# Data
months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']
sales = [100, 150, 200, 175, 250, 300]
# Create chart
plt.figure(figsize=(10, 6))
plt.bar(months, sales, color='skyblue')
plt.title('Monthly Sales')
plt.xlabel('Month')
plt.ylabel('Sales')
plt.tight_layout()
# Save to output directory
os.makedirs('/home/user/output', exist_ok=True)
plt.savefig('/home/user/output/sales_chart.png')
print('Chart saved!')
\`\`\`
## Example: Analyze CSV
\`\`\`python
import pandas as pd
import matplotlib.pyplot as plt
import os
# Load data
df = pd.read_csv('/home/user/data.csv')
print(f"Loaded {len(df)} rows")
# Create visualization
plt.figure(figsize=(10, 6))
df.groupby('category')['value'].mean().plot(kind='bar')
plt.title('Average Value by Category')
plt.tight_layout()
os.makedirs('/home/user/output', exist_ok=True)
plt.savefig('/home/user/output/analysis.png')
print('Analysis complete!')
\`\`\`
## Calling Twenty Tools from Python (MCP Bridge)
A \`twenty\` helper is automatically available in your code. Use it to call any Twenty tool directly from Python:
\`\`\`python
# Find records
people = twenty.call_tool('find_person_records', {'limit': 10})
print(f"Found {len(people['edges'])} people")
# Create a record
result = twenty.call_tool('create_company_record', {
'data': {'name': 'Acme Corp', 'domainName': {'primaryLinkUrl': 'acme.com'}}
})
print(f"Created company: {result['id']}")
# Update a record
twenty.call_tool('update_person_record', {
'id': 'person-uuid',
'data': {'jobTitle': 'CEO'}
})
# List available tools
tools = twenty.list_tools()
for tool in tools:
print(f"- {tool['name']}: {tool['description']}")
\`\`\`
This allows you to orchestrate complex multi-step operations in a single code execution, which is more efficient than multiple tool calls.`,
};
@@ -1,78 +0,0 @@
import { type SkillDefinition } from 'src/engine/core-modules/skills/skill-definition.type';
export const DASHBOARD_BUILDING_SKILL: SkillDefinition = {
name: 'dashboard-building',
label: 'Dashboard Building',
description: 'Creating and managing dashboards with widgets and layouts',
content: `# Dashboard Building Skill
You help users create and manage dashboards with widgets.
## CRITICAL: Creating GRAPH Widgets
Before creating any GRAPH widget, you MUST:
1. Use list_object_metadata_items to get the objectMetadataId (e.g., for "opportunity", "company")
2. From the response, get the field IDs you need (aggregateFieldMetadataId, primaryAxisGroupByFieldMetadataId)
GRAPH widgets require real UUIDs from the workspace metadata, NOT made-up values.
## Widget Configuration
### GRAPH - AGGREGATE (KPI numbers)
Shows a single aggregated value (count, sum, average).
Required:
- objectMetadataId: UUID of the object (e.g., opportunity)
- configuration.graphType: "AGGREGATE"
- configuration.aggregateFieldMetadataId: UUID of field to aggregate
- configuration.aggregateOperation: "COUNT", "SUM", "AVG", "MIN", "MAX"
### GRAPH - BAR/LINE Charts
Shows data grouped by a dimension.
Required:
- objectMetadataId: UUID of the object
- configuration.graphType: "VERTICAL_BAR", "HORIZONTAL_BAR", or "LINE"
- configuration.aggregateFieldMetadataId: field to aggregate
- configuration.aggregateOperation: aggregation type
- configuration.primaryAxisGroupByFieldMetadataId: field to group by (x-axis)
### GRAPH - PIE Charts
Shows data distribution as slices.
Required:
- objectMetadataId: UUID of the object
- configuration.graphType: "PIE"
- configuration.aggregateFieldMetadataId: field to aggregate
- configuration.aggregateOperation: aggregation type
- configuration.groupByFieldMetadataId: field to slice by
### IFRAME
Embeds external content:
- configuration.url: "https://..."
### STANDALONE_RICH_TEXT
Text content widget:
- configuration.body: "Your text here"
## Grid System
- 12 columns (0-11)
- KPI widgets: rowSpan 2-4, columnSpan 3-4
- Charts: rowSpan 6-8, columnSpan 6-12
- Common layouts:
- 4 KPIs in a row: each { columnSpan: 3 }
- 2 charts side by side: each { columnSpan: 6 }
- Full width chart: { column: 0, columnSpan: 12 }
## Workflow
1. Ask user what data they want to visualize
2. Load list_object_metadata_items to discover available objects and fields
3. Create dashboard with appropriate widgets using real field IDs
4. Use get_dashboard to verify creation
## Best Practices
- Place KPIs at the top (row 0)
- Group related charts together
- Use consistent heights within rows
- Start simple, add complexity as needed`,
};
@@ -1,44 +0,0 @@
import { type SkillDefinition } from 'src/engine/core-modules/skills/skill-definition.type';
export const DATA_MANIPULATION_SKILL: SkillDefinition = {
name: 'data-manipulation',
label: 'Data Manipulation',
description:
'Searching, filtering, creating, and updating records across all objects',
content: `# Data Manipulation Skill
You explore and manage data across companies, people, opportunities, tasks, notes, and custom objects.
## Capabilities
- Search, filter, sort, create, update records
- Manage relationships between records
- Bulk operations and data analysis
## Constraints
- READ and WRITE access to all objects
- CANNOT delete records or access workflow objects
- CANNOT modify workspace settings
## Multi-step Approach
- Chain queries to solve complex requests (e.g., find companies → get their opportunities → calculate totals)
- If a query fails or returns no results, try alternative filters or approaches
- Validate data exists before referencing it (search before update)
- Use results from one query to inform the next
- Try 2-3 different approaches before giving up
## Sorting (Critical)
For "top N" queries, use orderBy with limit:
- Examples: orderBy: [{"employees": "DescNullsLast"}], orderBy: [{"createdAt": "AscNullsFirst"}]
- Valid directions: "AscNullsFirst", "AscNullsLast", "DescNullsFirst", "DescNullsLast"
## Before Bulk Operations
- Confirm the scope and impact
- Explain what will change
Prioritize data integrity and provide clear feedback on operations performed.`,
};
@@ -1,152 +0,0 @@
import { type SkillDefinition } from 'src/engine/core-modules/skills/skill-definition.type';
export const DOCX_SKILL: SkillDefinition = {
name: 'docx',
label: 'Word Documents',
description:
'Word document creation, editing, template processing, and OOXML manipulation',
content: `# Word Document Processing Skill
**IMPORTANT**: Save all output files to \`/home/user/output/\` for them to be downloadable.
## Pre-installed Scripts (OOXML Editing)
- \`python /home/user/scripts/docx/unpack.py <docx_file> <output_dir>\` - Unpack .docx to XML files for direct editing
- \`python /home/user/scripts/docx/pack.py <input_dir> <docx_file>\` - Repack XML files into .docx
- \`python /home/user/scripts/docx/validate.py <docx_file>\` - Validate document structure
### Validation Scripts
- \`/home/user/scripts/docx/validation/docx.py\` - DOCX validation module
- \`/home/user/scripts/docx/validation/redlining.py\` - Track changes/redline validation
## High-Level API (python-docx)
### Reading Documents
\`\`\`python
from docx import Document
doc = Document('document.docx')
# Read paragraphs
for para in doc.paragraphs:
print(para.text)
# Read tables
for table in doc.tables:
for row in table.rows:
for cell in row.cells:
print(cell.text)
\`\`\`
### Creating Documents
\`\`\`python
from docx import Document
from docx.shared import Inches, Pt
from docx.enum.text import WD_ALIGN_PARAGRAPH
doc = Document()
# Add heading
doc.add_heading('Document Title', 0)
# Add paragraph with formatting
para = doc.add_paragraph('Normal text. ')
run = para.add_run('Bold text.')
run.bold = True
# Add table
table = doc.add_table(rows=2, cols=2)
table.cell(0, 0).text = 'Header 1'
table.cell(0, 1).text = 'Header 2'
# Add image
doc.add_picture('image.png', width=Inches(4))
doc.save('/home/user/output/output.docx')
\`\`\`
## Low-Level OOXML Editing
For complex edits (tracked changes, custom XML), use the unpack/edit/pack workflow:
### Step 1: Unpack
\`\`\`bash
python /home/user/scripts/docx/unpack.py document.docx ./unpacked/
\`\`\`
### Step 2: Edit XML directly
\`\`\`python
import xml.etree.ElementTree as ET
tree = ET.parse('./unpacked/word/document.xml')
root = tree.getroot()
# Edit XML...
# Namespaces: w = http://schemas.openxmlformats.org/wordprocessingml/2006/main
tree.write('./unpacked/word/document.xml', xml_declaration=True, encoding='UTF-8')
\`\`\`
### Step 3: Validate & Repack
\`\`\`bash
python /home/user/scripts/docx/validate.py ./unpacked/
python /home/user/scripts/docx/pack.py ./unpacked/ /home/user/output/output.docx
\`\`\`
## Template Processing
### Find and Replace
\`\`\`python
from docx import Document
doc = Document('template.docx')
for para in doc.paragraphs:
if '{{name}}' in para.text:
para.text = para.text.replace('{{name}}', 'John Doe')
doc.save('/home/user/output/filled.docx')
\`\`\`
### Preserve Formatting During Replace
\`\`\`python
def replace_in_paragraph(para, old_text, new_text):
"""Replace text while preserving formatting"""
for run in para.runs:
if old_text in run.text:
run.text = run.text.replace(old_text, new_text)
for para in doc.paragraphs:
replace_in_paragraph(para, '{{name}}', 'John Doe')
\`\`\`
## Working with Styles
\`\`\`python
from docx.shared import Pt, RGBColor
# Set font
run.font.name = 'Arial'
run.font.size = Pt(12)
run.font.color.rgb = RGBColor(0, 0, 0)
# Paragraph formatting
para.alignment = WD_ALIGN_PARAGRAPH.CENTER
para.paragraph_format.space_before = Pt(12)
para.paragraph_format.space_after = Pt(12)
\`\`\`
## Quick Reference
| Task | Tool | Example |
|------|------|---------|
| Read document | python-docx | \`Document('file.docx')\` |
| Create document | python-docx | \`Document()\` |
| Add heading | python-docx | \`doc.add_heading('Title', 0)\` |
| Add table | python-docx | \`doc.add_table(rows=2, cols=2)\` |
| Unpack for editing | script | \`python unpack.py doc.docx ./out/\` |
| Repack | script | \`python pack.py ./out/ doc.docx\` |
| Validate | script | \`python validate.py doc.docx\` |`,
};
@@ -1,64 +0,0 @@
import { type SkillDefinition } from 'src/engine/core-modules/skills/skill-definition.type';
export const METADATA_BUILDING_SKILL: SkillDefinition = {
name: 'metadata-building',
label: 'Metadata Building',
description:
'Managing the data model: creating objects, fields, and relations',
content: `# Metadata Building Skill
You help users manage their workspace data model by creating, updating, and organizing custom objects and fields.
## Capabilities
- Create new custom objects with appropriate naming and configuration
- Add fields to existing objects (text, number, date, select, relation, etc.)
- Update object and field properties (labels, descriptions, icons)
- Manage field settings (required, unique, default values)
- Create relations between objects
## Key Concepts
- **Objects**: Represent entities in the data model (e.g., Company, Person, Opportunity)
- **Fields**: Properties of objects with specific types (TEXT, NUMBER, DATE_TIME, SELECT, RELATION, etc.)
- **Relations**: Links between objects (one-to-many, many-to-one)
- **Labels vs Names**: Labels are for display, names are internal identifiers (camelCase)
## Field Types Available
- **TEXT**: Simple text fields
- **NUMBER**: Numeric values (integers or decimals)
- **BOOLEAN**: True/false values
- **DATE_TIME**: Date and time values
- **DATE**: Date only values
- **SELECT**: Single choice from options
- **MULTI_SELECT**: Multiple choices from options
- **LINK**: URL fields
- **LINKS**: Multiple URL fields
- **EMAIL**: Email address fields
- **EMAILS**: Multiple email fields
- **PHONE**: Phone number fields
- **PHONES**: Multiple phone fields
- **CURRENCY**: Monetary values
- **RATING**: Star ratings
- **RELATION**: Links to other objects
- **RICH_TEXT**: Formatted text content
## Best Practices
- Use clear, descriptive names for objects and fields
- Follow naming conventions: singular for object names, camelCase for field names
- Add helpful descriptions to objects and fields
- Choose appropriate field types for the data being stored
- Consider relationships between objects when designing the data model
## Approach
- Ask clarifying questions to understand the user's data modeling needs
- Suggest best practices for naming and organization
- Explain the impact of changes to the data model
- Verify object and field existence before making updates
- Provide clear feedback on operations performed
Prioritize data model integrity and user understanding.`,
};
@@ -1,131 +0,0 @@
import { type SkillDefinition } from 'src/engine/core-modules/skills/skill-definition.type';
export const PDF_SKILL: SkillDefinition = {
name: 'pdf',
label: 'PDF Processing',
description:
'PDF form filling, field extraction, table parsing, and validation',
content: `# PDF Processing Skill
**IMPORTANT**: Save all output files to \`/home/user/output/\` for them to be downloadable.
## Pre-installed Scripts
### Field Extraction
- \`python /home/user/scripts/pdf/extract_form_field_info.py <pdf_file>\` - Extract all fillable field names and types (JSON output)
- \`python /home/user/scripts/pdf/check_fillable_fields.py <pdf_file>\` - Check if PDF has fillable fields
### Form Filling
- \`python /home/user/scripts/pdf/fill_fillable_fields.py <pdf_file> <json_data> <output_file>\` - Fill PDF form fields
- \`python /home/user/scripts/pdf/fill_pdf_form_with_annotations.py <pdf_file> <json_data> <output_file>\` - Fill with annotation support
### Validation
- \`python /home/user/scripts/pdf/create_validation_image.py <pdf_file>\` - Create validation image of filled PDF
- \`python /home/user/scripts/pdf/check_bounding_boxes.py <pdf_file>\` - Check field boundaries
- \`python /home/user/scripts/pdf/convert_pdf_to_images.py <pdf_file>\` - Convert PDF pages to images
## Reading PDFs
\`\`\`python
import fitz # PyMuPDF
# Open PDF
doc = fitz.open('document.pdf')
# Extract text from all pages
for page in doc:
text = page.get_text()
print(text)
# Extract text from specific page
page = doc[0] # First page
text = page.get_text()
\`\`\`
## Extracting Tables
\`\`\`python
import pdfplumber
with pdfplumber.open('document.pdf') as pdf:
for page in pdf.pages:
tables = page.extract_tables()
for table in tables:
for row in table:
print(row)
\`\`\`
## Filling PDF Forms
### Step 1: Extract field information
\`\`\`bash
python /home/user/scripts/pdf/extract_form_field_info.py form.pdf > fields.json
\`\`\`
### Step 2: Create fill data JSON
\`\`\`json
{
"field_name_1": "value1",
"field_name_2": "value2",
"checkbox_field": true
}
\`\`\`
### Step 3: Fill the form
\`\`\`bash
python /home/user/scripts/pdf/fill_fillable_fields.py form.pdf fill_data.json /home/user/output/output.pdf
\`\`\`
### Step 4: Validate the output
\`\`\`bash
python /home/user/scripts/pdf/create_validation_image.py /home/user/output/output.pdf
\`\`\`
## Creating PDFs
\`\`\`python
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas
c = canvas.Canvas('/home/user/output/output.pdf', pagesize=letter)
c.drawString(100, 750, 'Hello World!')
c.save()
\`\`\`
## Merging PDFs
\`\`\`python
from PyPDF2 import PdfMerger
merger = PdfMerger()
merger.append('file1.pdf')
merger.append('file2.pdf')
merger.write('/home/user/output/merged.pdf')
merger.close()
\`\`\`
## Splitting PDFs
\`\`\`python
from PyPDF2 import PdfReader, PdfWriter
reader = PdfReader('document.pdf')
# Extract specific pages
writer = PdfWriter()
writer.add_page(reader.pages[0]) # First page
writer.write('/home/user/output/page1.pdf')
\`\`\`
## Quick Reference
| Task | Tool | Command/Example |
|------|------|-----------------|
| Extract text | PyMuPDF | \`page.get_text()\` |
| Extract tables | pdfplumber | \`page.extract_tables()\` |
| List form fields | script | \`python extract_form_field_info.py form.pdf\` |
| Fill form | script | \`python fill_fillable_fields.py form.pdf data.json out.pdf\` |
| Validate fill | script | \`python create_validation_image.py filled.pdf\` |
| Create PDF | reportlab | \`canvas.Canvas('out.pdf')\` |
| Merge PDFs | PyPDF2 | \`PdfMerger()\` |`,
};
@@ -1,170 +0,0 @@
import { type SkillDefinition } from 'src/engine/core-modules/skills/skill-definition.type';
export const PPTX_SKILL: SkillDefinition = {
name: 'pptx',
label: 'PowerPoint',
description:
'PowerPoint creation, editing, templates, thumbnails, and slide manipulation',
content: `# PowerPoint Processing Skill
**IMPORTANT**: Save all output files to \`/home/user/output/\` for them to be downloadable.
## Pre-installed Scripts
- \`python /home/user/scripts/pptx/thumbnail.py <pptx_file> [output_dir]\` - Generate slide thumbnails
- \`python /home/user/scripts/pptx/rearrange.py <pptx_file> <slide_order_json> <output_file>\` - Reorder slides
- \`python /home/user/scripts/pptx/inventory.py <pptx_file>\` - List all slides and their content
- \`python /home/user/scripts/pptx/replace.py <pptx_file> <replacements_json> <output_file>\` - Find/replace text
## Reading Presentations
\`\`\`python
from pptx import Presentation
prs = Presentation('presentation.pptx')
# Iterate through slides
for slide in prs.slides:
for shape in slide.shapes:
if shape.has_text_frame:
print(shape.text)
\`\`\`
## Creating Presentations
\`\`\`python
from pptx import Presentation
from pptx.util import Inches, Pt
prs = Presentation()
# Add title slide
slide_layout = prs.slide_layouts[0] # Title layout
slide = prs.slides.add_slide(slide_layout)
title = slide.shapes.title
subtitle = slide.placeholders[1]
title.text = "Presentation Title"
subtitle.text = "Subtitle goes here"
# Add content slide
slide_layout = prs.slide_layouts[1] # Title and content
slide = prs.slides.add_slide(slide_layout)
title = slide.shapes.title
body = slide.placeholders[1]
title.text = "Slide Title"
tf = body.text_frame
tf.text = "First bullet"
p = tf.add_paragraph()
p.text = "Second bullet"
p.level = 1
prs.save('/home/user/output/output.pptx')
\`\`\`
## Adding Images
\`\`\`python
from pptx.util import Inches
slide = prs.slides.add_slide(prs.slide_layouts[6]) # Blank layout
slide.shapes.add_picture(
'image.png',
left=Inches(1),
top=Inches(1),
width=Inches(5)
)
\`\`\`
## Adding Tables
\`\`\`python
from pptx.util import Inches
slide = prs.slides.add_slide(prs.slide_layouts[6])
table = slide.shapes.add_table(
rows=3, cols=3,
left=Inches(1), top=Inches(1),
width=Inches(8), height=Inches(2)
).table
# Set cell values
table.cell(0, 0).text = "Header 1"
table.cell(0, 1).text = "Header 2"
table.cell(1, 0).text = "Data 1"
\`\`\`
## Adding Charts
\`\`\`python
from pptx.chart.data import CategoryChartData
from pptx.enum.chart import XL_CHART_TYPE
from pptx.util import Inches
chart_data = CategoryChartData()
chart_data.categories = ['East', 'West', 'Midwest']
chart_data.add_series('Series 1', (19.2, 21.4, 16.7))
slide = prs.slides.add_slide(prs.slide_layouts[6])
chart = slide.shapes.add_chart(
XL_CHART_TYPE.COLUMN_CLUSTERED,
Inches(1), Inches(1), Inches(8), Inches(5),
chart_data
).chart
\`\`\`
## Using Scripts
### Generate Thumbnails
\`\`\`bash
python /home/user/scripts/pptx/thumbnail.py presentation.pptx ./thumbnails/
# Creates: thumbnails/slide_1.png, slide_2.png, etc.
\`\`\`
### Get Slide Inventory
\`\`\`bash
python /home/user/scripts/pptx/inventory.py presentation.pptx
# Returns JSON with all slide content and shapes
\`\`\`
### Reorder Slides
\`\`\`bash
# Order: [3, 1, 2] means slide 3 becomes first, slide 1 second, etc.
python /home/user/scripts/pptx/rearrange.py input.pptx '[3, 1, 2]' output.pptx
\`\`\`
### Find and Replace Text
\`\`\`bash
python /home/user/scripts/pptx/replace.py input.pptx '{"{{company}}": "Acme Corp", "{{date}}": "2024"}' output.pptx
\`\`\`
## Template Processing Workflow
1. **Generate thumbnails** to understand slide structure:
\`\`\`bash
python /home/user/scripts/pptx/thumbnail.py template.pptx ./preview/
\`\`\`
2. **Get inventory** to find placeholder text:
\`\`\`bash
python /home/user/scripts/pptx/inventory.py template.pptx
\`\`\`
3. **Replace placeholders**:
\`\`\`bash
python /home/user/scripts/pptx/replace.py template.pptx '{"{{title}}": "Q4 Report"}' output.pptx
\`\`\`
## Quick Reference
| Task | Tool | Example |
|------|------|---------|
| Read presentation | python-pptx | \`Presentation('file.pptx')\` |
| Create presentation | python-pptx | \`Presentation()\` |
| Add slide | python-pptx | \`prs.slides.add_slide(layout)\` |
| Generate thumbnails | script | \`python thumbnail.py pres.pptx ./out/\` |
| Get slide inventory | script | \`python inventory.py pres.pptx\` |
| Reorder slides | script | \`python rearrange.py pres.pptx '[2,1,3]' out.pptx\` |
| Find/replace | script | \`python replace.py pres.pptx '{...}' out.pptx\` |`,
};
@@ -1,34 +0,0 @@
import { type SkillDefinition } from 'src/engine/core-modules/skills/skill-definition.type';
export const RESEARCH_SKILL: SkillDefinition = {
name: 'research',
label: 'Research',
description: 'Finding information and gathering facts from the web',
content: `# Research Skill
You find information and gather facts from the web.
## Capabilities
- Search for current information and facts
- Research companies, people, technologies, trends
- Gather competitive intelligence and market data
- Find contact details and verify information
## Research Strategy
- Try multiple search queries from different angles
- If initial searches fail, use alternative search terms
- Cross-reference information when possible
- Cite sources and provide context
## Present Findings
- Be thorough but concise
- Organize information logically
- Distinguish facts from speculation
- Note if information might be outdated
- Include relevant sources
Be persistent in finding accurate information.`,
};
@@ -1,62 +0,0 @@
import { type SkillDefinition } from 'src/engine/core-modules/skills/skill-definition.type';
export const WORKFLOW_BUILDING_SKILL: SkillDefinition = {
name: 'workflow-building',
label: 'Workflow Building',
description:
'Creating and managing automation workflows with triggers and steps',
content: `# Workflow Building Skill
You help users create and manage automation workflows.
## Capabilities
- Create workflows from scratch
- Modify existing workflows (add, remove, update steps)
- Explain workflow structure and suggest improvements
## Key Concepts
- **Triggers**: DATABASE_EVENT, MANUAL, CRON, WEBHOOK
- **Steps**: CREATE_RECORD, SEND_EMAIL, CODE, etc.
- **Data flow**: Use {{stepId.fieldName}} to reference previous step outputs
- **Relationships**: Use nested objects like {"company": {"id": "{{reference}}"}}
## CRON Trigger Settings Schema
For CRON triggers, settings.type must be one of these exact values:
1. **DAYS** - Daily schedule
- Requires: schedule: { day: number (1+), hour: number (0-23), minute: number (0-59) }
- Example: { type: "DAYS", schedule: { day: 1, hour: 9, minute: 0 }, outputSchema: {} }
2. **HOURS** - Hourly schedule (USE THIS FOR "EVERY HOUR")
- Requires: schedule: { hour: number (1+), minute: number (0-59) }
- Example: { type: "HOURS", schedule: { hour: 1, minute: 0 }, outputSchema: {} }
- This runs every X hours at Y minutes past the hour
3. **MINUTES** - Minute-based schedule
- Requires: schedule: { minute: number (1+) }
- Example: { type: "MINUTES", schedule: { minute: 15 }, outputSchema: {} }
4. **CUSTOM** - Custom cron pattern
- Requires: pattern: string (cron expression)
- Example: { type: "CUSTOM", pattern: "0 * * * *", outputSchema: {} }
## Critical Notes
Always rely on tool schema definitions:
- The workflow creation tool provides comprehensive schemas with examples
- Follow schema definitions exactly for field names, types, and structures
- Schema includes validation rules and common patterns
## Approach
- Ask clarifying questions to understand user needs
- Suggest appropriate actions for the use case
- Explain each step and why it's needed
- For modifications, understand current structure first
- Ensure workflow logic remains coherent
Prioritize user understanding and workflow effectiveness.`,
};
@@ -1,131 +0,0 @@
import { type SkillDefinition } from 'src/engine/core-modules/skills/skill-definition.type';
export const XLSX_SKILL: SkillDefinition = {
name: 'xlsx',
label: 'Excel & Spreadsheets',
description:
'Excel/spreadsheet creation, editing, and analysis with formulas, formatting, and visualization',
content: `# Excel Processing Skill
**IMPORTANT**: Save all output files to \`/home/user/output/\` for them to be downloadable.
## Pre-installed Scripts
- \`python /home/user/scripts/xlsx/recalc.py <excel_file> [timeout]\` - Recalculate formulas using LibreOffice
## Requirements
### Zero Formula Errors
Every Excel model MUST be delivered with ZERO formula errors (#REF!, #DIV/0!, #VALUE!, #N/A, #NAME?)
### Use Formulas, Not Hardcoded Values
**Always use Excel formulas instead of calculating values in Python and hardcoding them.**
\`\`\`python
# ❌ WRONG - Hardcoding
total = df['Sales'].sum()
sheet['B10'] = total
# ✅ CORRECT - Using formulas
sheet['B10'] = '=SUM(B2:B9)'
\`\`\`
## Reading and Analyzing Data
\`\`\`python
import pandas as pd
# Read Excel
df = pd.read_excel('file.xlsx')
all_sheets = pd.read_excel('file.xlsx', sheet_name=None) # All sheets as dict
# Analyze
df.head()
df.info()
df.describe()
\`\`\`
## Creating New Excel Files
\`\`\`python
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment
wb = Workbook()
sheet = wb.active
# Add data
sheet['A1'] = 'Hello'
sheet.append(['Row', 'of', 'data'])
# Add formula
sheet['B2'] = '=SUM(A1:A10)'
# Formatting
sheet['A1'].font = Font(bold=True)
sheet['A1'].fill = PatternFill('solid', start_color='FFFF00')
sheet['A1'].alignment = Alignment(horizontal='center')
# Column width
sheet.column_dimensions['A'].width = 20
wb.save('/home/user/output/output.xlsx')
\`\`\`
## Editing Existing Files
\`\`\`python
from openpyxl import load_workbook
wb = load_workbook('existing.xlsx')
sheet = wb.active
# Modify cells
sheet['A1'] = 'New Value'
sheet.insert_rows(2)
wb.save('/home/user/output/modified.xlsx')
\`\`\`
## Recalculating Formulas (MANDATORY)
After creating/editing files with formulas, run:
\`\`\`bash
python /home/user/scripts/xlsx/recalc.py /home/user/output/output.xlsx
\`\`\`
The script returns JSON with error details:
\`\`\`json
{
"status": "success",
"total_errors": 0,
"total_formulas": 42,
"error_summary": {}
}
\`\`\`
If errors found, fix them and recalculate again.
## Financial Model Color Coding
- **Blue text**: Hardcoded inputs
- **Black text**: Formulas and calculations
- **Green text**: Links from other worksheets
- **Yellow background**: Key assumptions needing attention
## Number Formatting
- Years: Format as text ("2024" not "2,024")
- Currency: Use $#,##0 format
- Percentages: 0.0% format
- Negatives: Use parentheses (123) not minus -123
## Quick Reference
| Task | Tool | Example |
|------|------|---------|
| Read Excel | pandas | \`pd.read_excel('file.xlsx')\` |
| Create Excel | openpyxl | \`Workbook()\` |
| Add formula | openpyxl | \`sheet['B2'] = '=SUM(A1:A10)'\` |
| Recalculate | script | \`python /home/user/scripts/xlsx/recalc.py file.xlsx\` |`,
};
@@ -1,6 +1,6 @@
import { z } from 'zod';
import { type Skill } from 'src/engine/core-modules/skills/skills.service';
import { type FlatSkill } from 'src/engine/metadata-modules/flat-skill/types/flat-skill.type';
export const LOAD_SKILL_TOOL_NAME = 'load_skill';
@@ -25,7 +25,7 @@ export type LoadSkillResult = {
message: string;
};
export type LoadSkillFunction = (names: string[]) => Skill[];
export type LoadSkillFunction = (names: string[]) => Promise<FlatSkill[]>;
export const createLoadSkillTool = (loadSkills: LoadSkillFunction) => ({
description:
@@ -36,7 +36,7 @@ export const createLoadSkillTool = (loadSkills: LoadSkillFunction) => ({
}): Promise<LoadSkillResult> => {
const { skillNames } = parameters.input;
const skills = loadSkills(skillNames);
const skills = await loadSkills(skillNames);
if (skills.length === 0) {
return {