Woo Agent
AI agent designed to automate WooCommerce tasks.
Install
Customization
1. Install OpenKBS CLI:
npm install -g openkbs
2. Create and enter project directory:
mkdir woo-agent && cd woo-agent
3. Clone your app locally:
openkbs login
openkbs ls
openkbs clone <id-from-ls-output>
4. Initialize Git repository:
git init && git stage . && git commit -m "First commit"
5. Add new features:
Examples:
openkbs modify "Implement new command getBitcoinPrice(days) that returns price history from CoinGecko API" src/Events/actions.js
openkbs modify "Add instructions for getBitcoinPrice" src/Events/actions.js app/instructions.txt
6. Review changes and deploy to OpenKBS:
git diff
openkbs push
7. Test your customization:
Go to chat and ask: "Analyze the price of Bitcoin"
Disclaimer
The applications provided through OpenKBS are developmental blueprints and are intended solely as starting points for software engineers and developers. These open-source templates are not production-ready solutions.
Before any production deployment, developers must:
- Conduct comprehensive code reviews and security audits
- Implement robust security measures and safeguards
- Perform extensive testing procedures
- Ensure full compliance with applicable regulations
- Adapt and enhance the codebase for specific use cases
NO WARRANTY DISCLAIMER: These blueprints are provided "as-is" without any warranties, whether express or implied. By using these blueprints, you assume full responsibility for all aspects of development, implementation, and maintenance of any derived applications. OpenKBS shall not be liable for any damages or consequences arising from the use of these blueprints.
Instructions and Source Code
You are an AI agent designed to assist with WordPress and WooCommerce tasks using API commands below. Your name is Woo. General Guidelines: - To execute a command, generate the command in the chat - Don't make assumptions - Don't guess - Do only what was asked of you. - To open websites use webpageToText command - Avoid large responses, instead call few commands + /metaAction(execute_and_callback) Flow 1: WordPress Event Processing: - You could receive WordPress events in JSON format with injected prompts (e.g., {"desc": "iPhone 16 Pro {woo generate more}"}). - Automatically process the event to completion. - Avoid user interactions, as the user does not monitor the chat. - After completing the task, call /jobCompleted({"post_id": 123, "message": "short message"}) (e.g., for editing product 123). Flow 2: Direct User Instructions: - You could receive instructions in a free chat session. - Possible user interaction if needed fakestoreapi.com Flow 3: Develop Plugins: - break large plugins implementation to multiple stages - after each stage wait the user to confirm next features before continue - use the /suggestion("some feature") command to suggest features to be implemented in the next stage - use writeFile MULTILINE_COMMAND to write/edit plugins source code - use FUNCTION_COMMANDS for any additional actions - always deactivate the plugin before code modifications - when requested to modify an existing plugin use read-recursive to check the current state - you can mix different command types in the same message MULTILINE_COMMANDS: /writeFile my-plugin/my-plugin.php ```php <?php echo "Hello, World" ?> ``` /writeFile my-plugin/style.css ```css body { margin: 0; } ``` Description: """ writeFile command creates or updates files by given {path} followed by code block - Missing folders are created automatically - Avoid code placeholders like "Previous code remains the same" - Always provide complete source code, as it is deployed automatically (as it is) """ FUNCTION_COMMANDS: Generate Node.js code to perform WordPress related tasks using the provided template functions: Function Guidelines: - Use axios to call wp-json APIs like wp/v2, wc/v3 or openkbs/v1 to assist the user - Generate only openkbs/v1 endpoints listed below - The secrets placeholders are replaced automatically by the system. - export the handler function at the end Avoid huge responses and long chats: - Create targeted scripts for specific data lookups instead of fetching entire datasets - Try to complete the Task in less than 10 iterations Template Functions: ```javascript const axios = require('axios'); const handler = async () => { // Generate code here const url = '{{secrets.wpUrl}}'; const headers = { 'WP-API-KEY': '{{secrets.wpapiKey}}' }; try { // WooCommerce API Examples: const response = await axios.get(`${url}/wp-json/wc/v3/products/123`, { headers }); // Get product // Plugins API Examples: const pluginsUrl = `${url}/wp-json/openkbs/v1/plugins` const response = await axios.get(`${pluginsUrl}/list`, headers); // Lists all WordPress plugins with their status const response = await axios.post(`${pluginsUrl}/activate`, {plugin_path: 'my-plugin/my-plugin.php'}, {headers}); const response = await axios.post(`${pluginsUrl}/deactivate`, {plugin_path: 'my-plugin/my-plugin.php'}, {headers}); // Filesystem API Examples: // All filesystem operations are scoped to the WordPress plugins directory // Paths provided should be relative to wp-content/plugins/ const fsUrl = `${url}/wp-json/openkbs/v1/filesystem` const response = await axios.get(`${fsUrl}/list`, {headers}); // Lists all plugins directories const response = await axios.post(`${fsUrl}/write`, { path: 'my-plugin/my-plugin.php', content:`<?php echo 'Hello' ?>` }, {headers}); // deprecated, use multiline writeFile syntax instead const response = await axios.post(`${fsUrl}/mkdir`, { path: 'my-plugin' }, {headers}); // Creates new directory const response = await axios.get(`${fsUrl}/read-recursive`, { params: { path: 'my-plugin' }, headers}) // Gets all content from all files recursively const response = await axios.get(`${fsUrl}/read`, { params: { path: 'my-plugin/my-plugin.php' }, headers}); // Gets contents of a single file const response = await axios.post(`${fsUrl}/copy`, { source: 'my-plugin/my-plugin.php', destination: 'my-plugin/utils.php' }, {headers}); // Copies a file or directory const response = await axios.get(`${fsUrl}/list`, { params: { path: 'my-plugin' }, headers}); // Lists files and directories of a specific plugin directory const response = await axios.delete(`${fsUrl}/delete`, { headers, data: { path: 'my-plugin' } }); // Deletes a specified file or directory // Other API Examples: // const { title, price, description, image, category } = (await axios.get('https://fakestoreapi.com/products/category/electronics'))[0]; // free e-commerce data API return response.data; } catch (error) { return error.response ? error.response.data : error.message; } }; module.exports = { handler }; ``` SINGLE_LINE_COMMANDS: Use API commands below to assist the user Guidelines: - If an API call fails, so that you can't extract the required data, retry with a different website or search query - Multiple commands can be processed (one per line) /googleSearch("query") Description: """ Get results from Google Search API. """ /webpageToText("URL") Description: """ Use this API to extract text, images, products and other content from website (extracting data from website) When multiple web pages are provided generate multiple commands on new lines """ /viewImage("imageURL") Description: """ Use this API to view the image at the specified URL """ /metaAction(execute_and_callback) Description: """ Executes all commands in this message (if any) and calls back with the responses automatically (without waiting for user interaction) Always last command """ /metaAction(execute_and_wait) Description: """ Executes last commands (if any) and waits for user interaction to continue Always last command """ /suggestion("free text") Description: """ Renders the suggestion as clickable UI Button, so that the user can easily select next features """ /jobCompleted({"post_id": 123, "message": ""}) Description: """ Sets this job as completed """ /jobFailed({"post_id": 123, "message": ""}) Description: """ Sets this job as failed """
Events Files (Node.js Backend)
Events/actions.js
import vm from 'vm';
import axios from "axios";
// Updated regex to include language and new format
const batchRegex = /(?:writeFile\s+([^\s]+)\s*```(\w+)\s*([\s\S]*?)```|``javascript\s*([\s\S]*?)\s*``|\/?(googleSearch|webpageToText|viewImage|metaAction|suggestion|jobCompleted|jobFailed)\(([^()]*)\))/g;
function detectLazyOutput(text) {
return text.split('\n').some(line => {
const commentContent = line.trim().substring(2).trim().toLowerCase();
return line.trim().startsWith('//') && ['...', 'same'].some(pattern => commentContent.includes(pattern));
});
}
export const getActions = (meta) => [
[batchRegex, async (match, event) => {
// Get the full message content
const lastMessage = event.payload.messages[event.payload.messages.length - 1].content;
let disableAutoCallback = meta?._meta_actions?.includes('REQUEST_CHAT_MODEL_EXCEEDED')
// Find all blocks and commands in order
let blocks = Array.from(lastMessage.matchAll(batchRegex))
.map(([full, filePath, language, fileContent, jsContent, commandType, commandArg]) => {
if (filePath && language && fileContent) {
return {
type: 'writeFile',
path: filePath.trim(),
language: language.trim(),
content: fileContent.trim()
};
} else if (jsContent) {
return {
type: 'javascript',
content: jsContent.trim()
};
} else if (commandType) {
if (commandType === 'suggestion') {
// require human confirmation
meta._meta_actions = meta._meta_actions.filter(action => action !== 'REQUEST_CHAT_MODEL');
disableAutoCallback = true;
}
if (commandArg?.startsWith('"') && commandArg?.endsWith('"')) {
commandArg = commandArg.slice(1, -1); // remove quotes if any
}
let arg = commandArg.trim();
let isJSON = false;
// Attempt to parse the argument as JSON
try {
arg = JSON.parse(commandArg);
isJSON = true;
} catch (e) {
// Argument is not JSON; proceed with the trimmed string
}
return {
type: commandType,
arg: arg
};
}
});
// Filter out all execute_and_callback except the last one
const lastExecuteCallback = blocks.findLast(
block => block?.type === 'metaAction' && block?.arg === 'execute_and_callback'
);
blocks = [
...blocks.filter(block => !(block?.type === 'metaAction' && block?.arg === 'execute_and_callback')),
...(lastExecuteCallback ? [lastExecuteCallback] : [])
];
if (blocks.length === 0) {
return {
error: "No valid blocks or commands found",
...meta
};
}
try {
// Process blocks sequentially in order
const results = [];
let stop;
for (const block of blocks) {
if (stop) break;
if (block.type === 'writeFile' && detectLazyOutput(block.content)) {
results.push({
type: 'writeFile',
path: block.path,
success: false,
error: `Lazy comment detected in writeFile block, please provide complete source code for path: ${block.path}`
});
continue; // Skip processing this block further to avoid saving broken code
}
const url = '{{secrets.wpUrl}}';
const headers = { 'WP-API-KEY': '{{secrets.wpapiKey}}' };
const handleJobFinished = async (block, url, headers, results) => {
const { arg: data } = block;
const { post_id, message } = data;
const encryptedTitle = await openkbs.encrypt(message);
const promises = [
openkbs.chats({
action: "updateChat",
title: encryptedTitle,
chatIcon: block?.type === 'jobCompleted' ? '🟢' : '🔴',
chatId: event?.payload?.chatId
})
];
if (post_id) {
promises.push(
axios.post(`${url}/wp-json/openkbs/v1/callback`, { post_id, message, type: "reload" }, { headers })
);
}
await Promise.all(promises);
results.push({ type: block.type, success: true, data });
};
switch (block.type) {
case 'writeFile': {
const fsUrl = `${url}/wp-json/openkbs/v1/filesystem`;
const response = await axios.post(
`${fsUrl}/write`,
{ path: block.path, content: block.content },
{ headers }
);
results.push({
type: 'writeFile',
path: block.path,
language: block.language,
success: response.status === 200
});
break;
}
case 'javascript': {
let sourceCode = block.content
.replace(`\{\{secrets.wpapiKey\}\}`, '{{secrets.wpapiKey}}')
.replace(`\{\{secrets.wpUrl\}\}`, '{{secrets.wpUrl}}');
// Add the export statement if it's not already present
if (!sourceCode.includes('module.exports')) sourceCode += '\nmodule.exports = { handler };'
const script = new vm.Script(sourceCode);
const context = {
require: (id) => rootContext.require(id),
...rootContext,
console,
module: { exports: {} }
};
vm.createContext(context);
script.runInContext(context);
const { handler } = context.module.exports;
const data = await handler();
results.push({
type: 'javascript',
success: !data?.error,
data
});
break;
}
case 'googleSearch': {
const noSecrets = '{{secrets.googlesearch_api_key}}'.includes('secrets.googlesearch_api_key');
const params = {
q: block.arg,
...(noSecrets ? {} : {
key: '{{secrets.googlesearch_api_key}}',
cx: '{{secrets.googlesearch_engine_id}}'
})
};
const response = noSecrets
? await openkbs.googleSearch(params.q, params)
: (await axios.get('https://www.googleapis.com/customsearch/v1', { params }))?.data?.items;
const data = response?.map(({ title, link, snippet, pagemap }) => ({
title, link, snippet, image: pagemap?.metatags?.[0]?.["og:image"]
}));
results.push({
type: 'googleSearch',
success: !!data?.length,
data: data || { error: "No results found" }
});
break;
}
case 'webpageToText': {
const response = await openkbs.webpageToText(block.arg);
if (response?.content?.length > 5000) {
response.content = response.content.substring(0, 5000);
}
results.push({
type: 'webpageToText',
success: !!response?.url,
data: response?.url ? response : { error: "Unable to read website" }
});
break;
}
case 'viewImage': {
results.push({
type: 'viewImage',
success: true,
data: [
{ type: "text", text: "Image URL: " + block.arg },
{ type: "image_url", image_url: { url: block.arg } }
]
});
break;
}
case 'metaAction': {
stop = true; // always last command
if (block.arg === 'execute_and_callback') {
if (!disableAutoCallback && !meta._meta_actions.includes('REQUEST_CHAT_MODEL')) {
meta._meta_actions.push('REQUEST_CHAT_MODEL');
}
} else if (block.arg === 'execute_and_wait') {
meta._meta_actions = meta._meta_actions.filter(action => action !== 'REQUEST_CHAT_MODEL');
}
results.push({
type: 'metaAction',
success: true,
data: block.arg
});
break;
}
case 'jobCompleted':
case 'jobFailed':
stop = true;
await handleJobFinished(block, url, headers, results);
break;
}
}
const allSuccessful = results.every(r => r.success);
if (allSuccessful) {
return {
data: {
message: "All operations completed successfully",
results
},
...meta
};
} else {
if (!disableAutoCallback) meta._meta_actions = ["REQUEST_CHAT_MODEL"]
return {
data: {
error: "Some operations failed",
results
},
...meta,
};
}
} catch (e) {
if (!disableAutoCallback) meta._meta_actions = ["REQUEST_CHAT_MODEL"]
return {
error: e.response?.data || e.message,
...meta
};
}
}]
];
Events/onRequest.js
import {getActions} from './actions.js';
export const handler = async (event) => {
const actions = getActions({ _meta_actions: [] });
for (let [regex, action] of actions) {
const lastMessage = event.payload.messages[event.payload.messages.length - 1].content;
const match = lastMessage?.match(regex);
if (match) return await action(match, event);
}
return { type: 'CONTINUE' }
};
Events/onResponse.js
import {getActions} from './actions.js';
export const handler = async (event) => {
const maxSelfInvokeMessagesCount = 50;
const actions = getActions({
_meta_actions: event?.payload?.messages?.length > maxSelfInvokeMessagesCount
? ["REQUEST_CHAT_MODEL_EXCEEDED"]
: ["REQUEST_CHAT_MODEL"]
});
for (let [regex, action] of actions) {
const lastMessage = event.payload.messages[event.payload.messages.length - 1].content;
const match = lastMessage?.match(regex);
if (match) return await action(match, event);
}
return { type: 'CONTINUE' }
};
Frontend Files (React Frontend)
Frontend/contentRender.js
import React, { useEffect, useState } from "react";
import {Button, Chip, Tooltip, ThemeProvider, createTheme} from '@mui/material';
import {Autorenew, TravelExplore, Preview, HourglassEmpty, CallMade, EditNote, Check} from '@mui/icons-material';
const style = document.createElement('style');
style.innerHTML = `
.codeContainer, .codeContainer code {
background-color: #0d0d0d !important;
color: white !important;
text-shadow: none !important;
border-radius: 10px !important;
font-size: 13px !important;
}
.codeContainer * {
background-color: #0d0d0d !important;
}
`;
document.head.appendChild(style);
const Header = ({ setRenderSettings }) => {
useEffect(() => {
setRenderSettings({
disableCodeExecuteButton: true,
inputLabelsQuickSend: true,
});
}, [setRenderSettings]);
};
const isMobile = window?.openkbs?.isMobile;
const ChatMessageRenderer = ({ content, CodeViewer, setInputValue, sendButtonRippleRef }) => {
const [addedSuggestions, setAddedSuggestions] = useState([]);
const handleSuggestionClick = (suggestion) => {
setInputValue((prev) => prev + (prev ? '\n' : '') + suggestion);
setAddedSuggestions((prev) => [...prev, suggestion]);
setTimeout(() => sendButtonRippleRef?.current?.pulsate(), 100);
};
const output = [];
let language = null;
content.split('\n').forEach(line => {
const writeFileMatch = /writeFile\s+(?<filePath>[^\s]+)/.exec(line);
const codeStartMatch = /```(?<language>\w+)/g.exec(line);
const commandMatch = /\/(?<command>googleSearch|webpageToText|viewImage|metaAction|suggestion|jobCompleted|jobFailed)\((?<args>[^()]*)\)/g.exec(line);
if (!language && codeStartMatch) {
language = codeStartMatch.groups.language;
output.push({ language, code: '' });
} else if (language && line.match(/```/)) {
language = null;
} else if (language) {
output[output.length - 1].code += line + '\n';
} else if (commandMatch || writeFileMatch) {
const command = commandMatch?.groups?.command || 'writeFile';
let args = commandMatch?.groups?.args || writeFileMatch?.groups?.filePath;
if (args?.startsWith('"') && args?.endsWith('"')) args = args.slice(1, -1); // remove quotes if any
output.push({ command, args, line });
} else {
output.push(line);
}
});
return <ThemeProvider theme={() => createTheme(window.openkbsTheme)}>
{output.map((o, i) => {
if (typeof o === 'string') {
return <p key={i} style={{ marginTop: '0px', marginBottom: '0px' }}>{o}</p>;
} else if (o.command) {
if (o.command === 'suggestion') {
const added = addedSuggestions?.includes(o.args);
return (
<div key={`a${i}`}>
<Button
variant="contained"
color="primary"
disabled={added}
onClick={() => handleSuggestionClick(o.args)}
style={{ margin: '5px', textTransform: 'none' }}
>
{added ? <Check fontSize="small" sx={{mr: 2}} /> : ''}{o.args}
</Button>
</div>
);
} else if (o.command) {
const argsIcons = {
'execute_and_wait': <HourglassEmpty />,
'execute_and_callback': <Autorenew />
};
const commandIcons = {
'googleSearch': <TravelExplore />,
'webpageToText': <Preview />,
'writeFile': <EditNote />
};
const icon = argsIcons[o.args] || commandIcons[o.command];
return <div style={{ marginTop: '5px', marginBottom: '5px' }}>
<Tooltip title={o.line} placement="right">
<Chip
sx={{mt: '10px'}}
icon={icon}
label={o.args}
variant="outlined"
deleteIcon={ <CallMade
style={{
fontSize: 12,
borderRadius: '50%',
padding: '4px',
}}
/> }
onDelete={() => {}}
/>
</Tooltip>
</div>
}
} else {
return (
<div key={i}>
<CodeViewer
limitedWidth={isMobile}
language={o.language}
className="codeContainer"
source={o.code}
/>
</div>
);
}
})}
</ThemeProvider>
};
const onRenderChatMessage = async (params) => {
const { content } = params.messages[params.msgIndex];
const { CodeViewer, setInputValue, sendButtonRippleRef } = params;
if (content.match(/```/) || content.match(/\/(?<command>\w+)\(([\s\S]*)\)/g)) {
return (
<ChatMessageRenderer
content={content}
CodeViewer={CodeViewer}
setInputValue={setInputValue}
sendButtonRippleRef={sendButtonRippleRef}
/>
);
}
};
const exports = { onRenderChatMessage, Header };
window.contentRender = exports;
export default exports;
Frontend/contentRender.json
{
"dependencies": {
"react": "^18.2.0 (fixed)",
"react-dom": "^18.2.0 (fixed)",
"@mui/material": "^5.16.1 (fixed)",
"@mui/icons-material": "^5.16.1 (fixed)",
"@emotion/react": "^11.10.6 (fixed)",
"@emotion/styled": "^11.10.6 (fixed)"
}
}