Skip to main content

Custom Tools

PRO ENTERPRISE

Pro and Enterprise users can create custom tools to extend the AI assistant with new capabilities. Custom tools are JavaScript files that integrate seamlessly with the existing tool system.

Overview

Custom tools allow you to:

  • Add new research sources (APIs, databases, services)
  • Create specialized node types
  • Automate repetitive tasks
  • Integrate with your organization's tools

Getting Started

Tool Location

Place custom tools in the custom tools directory:

PlatformPath
macOS~/.config/redline/custom-tools/
Windows%APPDATA%/redline/custom-tools/
Linux~/.config/redline/custom-tools/

Tool Formats

Single-file tool:

custom-tools/
my-tool.js

Folder-based tool (with dependencies):

custom-tools/
my-tool/
index.js
package.json (auto-generated)
node_modules/ (auto-installed)

Creating a Simple Tool

Here's a minimal custom tool:

// quick_note.js
export const manifest = {
name: 'quick_note',
displayName: 'Quick Note',
description: 'Create a quick note with a timestamp',
version: '1.0.0',
group: 'core',
schema: {
type: 'object',
properties: {
content: {
type: 'string',
description: 'Note content'
}
},
required: ['content']
}
};

export async function call(input, ctx) {
const timestamp = new Date().toISOString();

const node = await ctx.createNode({
type: 'note',
title: `Quick Note - ${timestamp}`,
content: input.content
});

return {
success: true,
message: `Created quick note: ${node.id}`,
created: [{ id: node.id }]
};
}

Manifest Reference

The manifest defines your tool's metadata and interface:

export const manifest = {
// Required fields
name: 'my_tool', // Unique identifier (snake_case)
displayName: 'My Tool', // Shown in UI
description: 'What the AI sees when deciding to use this tool',
version: '1.0.0',

// Optional fields
group: 'custom', // Tool tier: core, entities, research,
// social, media, narratives, custom

schema: { // JSON Schema for tool input
type: 'object',
properties: {
query: {
type: 'string',
description: 'Search query for the AI'
},
limit: {
type: 'number',
description: 'Maximum results',
default: 10
}
},
required: ['query']
},

config: { // User-configurable settings (shown in Settings)
apiKey: {
type: 'string',
label: 'API Key',
secret: true // Encrypted storage
},
baseUrl: {
type: 'string',
label: 'API Base URL',
default: 'https://api.example.com'
}
},

dependencies: { // npm packages (folder-based tools only)
'axios': '^1.6.0',
'cheerio': '^1.0.0'
}
};

Schema Types

Support JSON Schema types:

  • string
  • number
  • boolean
  • array
  • object

Include description for each property - the AI uses these to understand parameters.

Config Types

TypeUI Element
stringText input
string (secret: true)Password input (encrypted storage)
numberNumber input
booleanCheckbox

Context API

The call function receives (input, ctx):

Properties

// Access user configuration
const apiKey = ctx.config.apiKey;

// Current board ID
const boardId = ctx.boardId;

Board Operations

// Create a node
const node = await ctx.createNode({
type: 'note', // note, actor, organization, event, etc.
title: 'My Node',
content: 'Node content'
});

// Create a connection
await ctx.createConnection({
sourceId: 'node-1',
targetId: 'node-2',
label: 'relates to',
type: 'related'
});

// Search the board
const results = await ctx.searchBoard({
query: 'search term',
type: 'actor' // optional filter
});

// Get all nodes (optionally filter by type)
const actors = await ctx.getNodes({ type: 'actor' });

// Get a specific node
const node = await ctx.getNode('node-id');

// Get all narratives
const narratives = await ctx.getNarratives();

Utilities

// Scrape a URL
const content = await ctx.scrapeUrl('https://example.com');
// Returns: { title, content, url, ... }

// Summarize content with AI
const summary = await ctx.summarize({
content: 'Long text to summarize...',
maxLength: 200 // optional
});

Logging

// Log messages (appear in Settings > Custom Tools > Recent Logs)
ctx.log('Processing started');
ctx.warn('Rate limit approaching');
ctx.error('Failed to fetch data');

Return Value

Your tool should return an object:

return {
success: true, // Required: did it work?
message: 'Created 3 notes', // Required: human-readable result

// Optional: IDs of created nodes (triggers board refresh)
created: [
{ id: 'node-id-1' },
{ id: 'node-id-2' }
],

// Optional: any extra data for the AI
data: {
searchResults: [...],
metadata: {...}
}
};

A folder-based tool with npm dependencies:

hacker_news/
index.js
// hacker_news/index.js
export const manifest = {
name: 'search_hacker_news',
displayName: 'Search Hacker News',
description: 'Search Hacker News for stories and discussions',
version: '1.0.0',
group: 'research',

schema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Search query'
},
limit: {
type: 'number',
description: 'Max results',
default: 5
},
create_nodes: {
type: 'boolean',
description: 'Create embed nodes for results',
default: false
}
},
required: ['query']
},

config: {
default_limit: {
type: 'number',
label: 'Default Result Limit',
default: 5
}
},

dependencies: {
'node-fetch': '^3.3.0'
}
};

export async function call(input, ctx) {
const fetch = (await import('node-fetch')).default;

const limit = input.limit || ctx.config.default_limit || 5;

ctx.log(`Searching Hacker News for: ${input.query}`);

const response = await fetch(
`https://hn.algolia.com/api/v1/search?query=${encodeURIComponent(input.query)}&hitsPerPage=${limit}`
);

const data = await response.json();

const results = data.hits.map(hit => ({
title: hit.title,
url: hit.url || `https://news.ycombinator.com/item?id=${hit.objectID}`,
points: hit.points,
comments: hit.num_comments,
author: hit.author
}));

const created = [];

if (input.create_nodes) {
for (const result of results) {
const node = await ctx.createNode({
type: 'embed',
url: result.url,
title: result.title
});
created.push({ id: node.id });
}
}

return {
success: true,
message: `Found ${results.length} stories${input.create_nodes ? `, created ${created.length} nodes` : ''}`,
created,
data: { results }
};
}

Tool Groups

Assign your tool to a group to control when it's loaded:

GroupWhen Loaded
coreAlways available
entitiesWhen AI needs entity tools
researchWhen AI needs research tools
socialWhen AI needs social media tools
mediaWhen AI needs media tools
narrativesWhen AI needs narrative tools
customWhen AI requests custom tools

The AI can request additional tool groups with get_more_tools.

Managing Custom Tools

Settings UI

Access custom tool management in Settings > Custom Tools:

  • View all installed tools
  • Configure tool settings
  • View recent logs
  • Enable/disable individual tools

Hot Reloading

Custom tools are reloaded when files change. No restart needed.

Dependency Installation

For folder-based tools with dependencies:

  1. Define dependencies in manifest.dependencies
  2. Redline auto-generates package.json
  3. Dependencies install automatically on first use

Debugging

View Logs

Check Settings > Custom Tools > Recent Logs for:

  • ctx.log() messages
  • ctx.warn() warnings
  • ctx.error() errors
  • Execution errors

Common Issues

Tool not appearing:

  • Check file is in correct directory
  • Verify manifest.name is unique
  • Check for JavaScript syntax errors

Dependencies not installing:

  • Use folder-based tool format
  • Check manifest.dependencies syntax
  • Check network connectivity

Tool errors:

  • Use try/catch and return { success: false, message: 'error' }
  • Log errors with ctx.error()
  • Check the Recent Logs in Settings

Best Practices

  1. Clear descriptions - The AI uses your description to decide when to use the tool
  2. Validate input - Check required fields before processing
  3. Handle errors - Always return success: false with an error message on failure
  4. Log progress - Use ctx.log() for debugging
  5. Return created nodes - Include created array so the board updates
  6. Respect rate limits - Add delays when calling external APIs
  7. Secure secrets - Use secret: true for API keys