From hudu
Manages Hudu knowledge base articles: create, search, update, organize in folders, handle drafts, HTML content, versioning, sharing, and search patterns for documentation.
npx claudepluginhub wyre-technology/msp-claude-plugins --plugin huduThis skill uses the workspace's default tool permissions.
Articles in Hudu serve as the knowledge base, providing a place for runbooks, procedures, network diagrams, SOPs, and general documentation. Articles support rich HTML content, can be organized into folders, and can be scoped to specific companies or kept as global (shared across all companies). MSP technicians rely on articles to quickly find procedures and reference documentation during troub...
Fetches up-to-date documentation from Context7 for libraries and frameworks like React, Next.js, Prisma. Use for setup questions, API references, and code examples.
Retrieves current documentation, API references, and code examples for libraries, frameworks, SDKs, CLIs, and services via Context7 CLI. Ideal for API syntax, configs, migrations, and setup queries.
Uses ctx7 CLI to fetch current library docs, manage AI coding skills (install/search/generate), and configure Context7 MCP for AI editors.
Articles in Hudu serve as the knowledge base, providing a place for runbooks, procedures, network diagrams, SOPs, and general documentation. Articles support rich HTML content, can be organized into folders, and can be scoped to specific companies or kept as global (shared across all companies). MSP technicians rely on articles to quickly find procedures and reference documentation during troubleshooting.
Articles can be scoped in two ways:
| Scope | Description | Use Case |
|---|---|---|
| Company-specific | Tied to a single company | Network diagram for Acme Corp |
| Global | Available across all companies | Standard new user setup procedure |
Folders organize articles within a company or globally:
Company: Acme Corporation
+-- Articles
+-- Procedures
| +-- Backup Procedure
| +-- Disaster Recovery Plan
+-- Network
| +-- Network Overview
| +-- IP Addressing Scheme
+-- Onboarding
+-- New User Setup
+-- Hardware Deployment
Article content is stored as HTML. Hudu's editor supports:
Articles can be saved as drafts before publishing:
| State | Description |
|---|---|
| Draft | Work in progress, not visible to all users |
| Published | Visible to users with appropriate permissions |
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | System | Auto-generated unique identifier |
company_id | integer | No | Parent company (null for global articles) |
name | string | Yes | Article title |
content | string | No | Rich HTML content |
folder_id | integer | No | Folder location |
draft | boolean | No | Whether article is a draft |
slug | string | System | URL-friendly identifier |
| Field | Type | Description |
|---|---|---|
company_name | string | Parent company name (read-only) |
folder_name | string | Folder name (read-only) |
| Field | Type | Description |
|---|---|---|
created_at | datetime | Creation timestamp |
updated_at | datetime | Last update timestamp |
url | string | Full URL to article in Hudu |
object_type | string | Always "Article" |
GET /api/v1/articles
x-api-key: YOUR_API_KEY
Content-Type: application/json
By Company:
GET /api/v1/articles?company_id=123
By Name:
GET /api/v1/articles?name=backup
With Pagination:
GET /api/v1/articles?company_id=123&page=1
GET /api/v1/articles/456
x-api-key: YOUR_API_KEY
Response:
{
"article": {
"id": 456,
"name": "Backup Procedure - Daily Operations",
"content": "<h1>Backup Procedure</h1><h2>Overview</h2><p>The daily backup runs at 10PM...</p>",
"company_id": 123,
"company_name": "Acme Corporation",
"folder_id": 15,
"folder_name": "Procedures",
"draft": false,
"slug": "backup-procedure-daily-operations",
"created_at": "2024-06-15T10:30:00.000Z",
"updated_at": "2025-12-01T14:22:00.000Z",
"url": "https://your-company.huducloud.com/a/backup-procedure-daily-operations-abcdef"
}
}
POST /api/v1/articles
Content-Type: application/json
x-api-key: YOUR_API_KEY
Company-specific article:
{
"article": {
"name": "New User Setup Procedure",
"company_id": 123,
"folder_id": 20,
"content": "<h1>New User Setup Procedure</h1><h2>Overview</h2><p>This procedure covers setting up a new user account for Acme Corporation.</p><h2>Prerequisites</h2><ul><li>Active Directory access</li><li>Microsoft 365 admin access</li></ul><h2>Steps</h2><ol><li>Create AD account with naming convention: first.last</li><li>Assign Microsoft 365 E3 license</li><li>Configure email signature using company template</li><li>Add to appropriate security groups</li></ol>"
}
}
Global article (no company_id):
{
"article": {
"name": "Standard Password Policy",
"content": "<h1>Standard Password Policy</h1><p>All managed client accounts must follow these requirements...</p><ul><li>Minimum 14 characters</li><li>Must include uppercase, lowercase, numbers, and symbols</li><li>Rotate every 90 days</li></ul>"
}
}
PUT /api/v1/articles/456
Content-Type: application/json
x-api-key: YOUR_API_KEY
{
"article": {
"content": "<h1>Backup Procedure - Updated</h1><p>Updated backup schedule...</p>",
"draft": false
}
}
DELETE /api/v1/articles/456
x-api-key: YOUR_API_KEY
PUT /api/v1/articles/456/archive
x-api-key: YOUR_API_KEY
GET /api/v1/folders
x-api-key: YOUR_API_KEY
By Company:
GET /api/v1/folders?company_id=123
POST /api/v1/folders
Content-Type: application/json
x-api-key: YOUR_API_KEY
{
"folder": {
"name": "Procedures",
"company_id": 123,
"parent_folder_id": null,
"description": "Standard operating procedures for Acme Corporation"
}
}
{
"folder": {
"name": "Disaster Recovery",
"company_id": 123,
"parent_folder_id": 15,
"description": "DR plans and procedures"
}
}
async function createRunbook(companyId, runbookData) {
// Ensure folder exists
const folder = await ensureFolder(companyId, runbookData.folderPath);
// Build content
let content = `<h1>${runbookData.title}</h1>`;
content += `<h2>Overview</h2><p>${runbookData.overview}</p>`;
if (runbookData.prerequisites?.length) {
content += `<h2>Prerequisites</h2><ul>`;
content += runbookData.prerequisites.map(p => `<li>${p}</li>`).join('');
content += `</ul>`;
}
if (runbookData.steps?.length) {
content += `<h2>Procedure</h2><ol>`;
content += runbookData.steps.map(s => `<li>${s}</li>`).join('');
content += `</ol>`;
}
// Create the article
return await createArticle({
name: runbookData.title,
company_id: companyId,
folder_id: folder?.id,
content: content
});
}
async function searchArticles(companyId, query) {
const articles = await fetchArticles({ company_id: companyId });
const queryLower = query.toLowerCase();
return articles.filter(article =>
article.name.toLowerCase().includes(queryLower) ||
article.content?.toLowerCase().includes(queryLower)
);
}
async function documentationHealthCheck(companyId) {
const articles = await fetchArticles({ company_id: companyId });
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
const yearAgo = new Date();
yearAgo.setFullYear(yearAgo.getFullYear() - 1);
return {
totalArticles: articles.length,
drafts: articles.filter(a => a.draft).length,
recentlyUpdated: articles.filter(a =>
new Date(a.updated_at) > thirtyDaysAgo
).length,
stale: articles.filter(a =>
new Date(a.updated_at) < yearAgo
).map(a => ({
name: a.name,
lastUpdated: a.updated_at
})),
empty: articles.filter(a =>
!a.content || a.content.trim().length < 50
).map(a => a.name)
};
}
async function cloneArticle(articleId, targetCompanyId, newName) {
const template = await getArticle(articleId);
return await createArticle({
name: newName || template.name,
company_id: targetCompanyId,
content: template.content
});
}
<h1>Network Overview</h1>
<h2>Network Topology</h2>
<p>[Network diagram image here]</p>
<h2>IP Addressing</h2>
<table>
<tr><th>Subnet</th><th>VLAN</th><th>Purpose</th></tr>
<tr><td>192.168.1.0/24</td><td>1</td><td>Servers</td></tr>
<tr><td>192.168.10.0/24</td><td>10</td><td>Workstations</td></tr>
<tr><td>192.168.20.0/24</td><td>20</td><td>Guest WiFi</td></tr>
</table>
<h2>Core Infrastructure</h2>
<p>[Asset references here]</p>
<h2>Firewall Rules Summary</h2>
<p>[Rule overview]</p>
<h2>Related Credentials</h2>
<p>[Embedded passwords]</p>
<h1>Disaster Recovery Plan</h1>
<h2>Emergency Contacts</h2>
<table>
<tr><th>Role</th><th>Name</th><th>Phone</th></tr>
<tr><td>Primary Contact</td><td>John Smith</td><td>555-123-4567</td></tr>
<tr><td>IT Manager</td><td>Jane Doe</td><td>555-987-6543</td></tr>
</table>
<h2>Critical Systems (Recovery Priority)</h2>
<ol>
<li>Domain Controller - RTO: 1 hour</li>
<li>Email Server - RTO: 2 hours</li>
<li>File Server - RTO: 4 hours</li>
<li>Line of Business App - RTO: 8 hours</li>
</ol>
<h2>Recovery Procedures</h2>
<h3>Complete Site Failure</h3>
<ol>
<li>Activate backup site / cloud DR</li>
<li>Restore domain controller from latest backup</li>
<li>Verify DNS failover</li>
<li>Restore email services</li>
<li>Restore file server from backup</li>
</ol>
<h2>Required Credentials</h2>
<p>[Embedded password references]</p>
<h2>Vendor Support Contacts</h2>
<table>
<tr><th>Vendor</th><th>Support Number</th><th>Account Number</th></tr>
<tr><td>ISP</td><td>800-555-1234</td><td>ACCT-12345</td></tr>
<tr><td>Backup Vendor</td><td>800-555-5678</td><td>ACCT-67890</td></tr>
</table>
| Code | Message | Resolution |
|---|---|---|
| 400 | Name can't be blank | Provide article name |
| 401 | Invalid API key | Check HUDU_API_KEY |
| 404 | Article not found | Verify article ID |
| 422 | Validation failed | Check required fields |
| Error | Cause | Fix |
|---|---|---|
| Name required | Missing name | Add name to request |
| Invalid folder | Bad folder_id | Query /folders first |
| Invalid company | Bad company_id | Query /companies first |
async function safeCreateArticle(data) {
try {
return await createArticle(data);
} catch (error) {
if (error.status === 422) {
// Handle invalid folder
if (error.message?.includes('folder')) {
console.log('Invalid folder. Creating at root level.');
delete data.folder_id;
return await createArticle(data);
}
}
throw error;
}
}