Learn how to create, compile, and package custom WebAssembly tools for use in Co-do.
Co-do supports custom WebAssembly (WASM) tools that extend the AI assistant's capabilities.
Tools are packaged as ZIP files containing a compiled .wasm binary
and a manifest.json that describes the tool's interface. Once installed, the AI
can invoke your tool to perform operations like encoding, hashing, text processing, and more.
Tools are compiled using the WASI SDK (WebAssembly System Interface), which
means you can write standard C/C++ code that uses stdio, string, and
other standard library functions. Rust and Go are also supported via their respective WASM targets.
A tool package is a standard ZIP file with the following structure:
your-tool.zip
├── manifest.json # Required: tool metadata and interface definition
└── your-tool.wasm # Required: compiled WebAssembly binary
Key rules: The ZIP must contain exactly one manifest.json and exactly one .wasm file. Additional files are allowed but ignored. Files can be at any depth within the ZIP.
The manifest.json tells Co-do what your tool does, what parameters it accepts,
and how to run it. Here is the full schema:
{
"name": "my-tool",
"version": "1.0.0",
"description": "A clear description of what the tool does (shown to the AI).",
"parameters": {
"type": "object",
"properties": {
"input": {
"type": "string",
"description": "The data to process"
},
"mode": {
"type": "string",
"enum": ["encode", "decode"],
"description": "Operation mode"
}
},
"required": ["input"]
},
"returns": {
"type": "string",
"description": "The processed output"
},
"execution": {
"argStyle": "positional",
"fileAccess": "none",
"timeout": 5000
},
"pipeable": true,
"category": "crypto",
"author": "Your Name",
"license": "MIT",
"homepage": "https://example.com"
}
| Field | Required | Description |
|---|---|---|
name |
Yes | Tool identifier. Must start with a letter, lowercase, may contain hyphens/underscores. Pattern: ^[a-z][a-z0-9]*(?:[_-][a-z0-9]+)*$ |
version |
Yes | Semantic version string (e.g. "1.0.0") |
description |
Yes | What the tool does. This is shown to the AI so it knows when to use it. |
parameters |
Yes | JSON Schema object defining the tool's input parameters. Each property can have type (string, number, boolean, array), description, optional enum, and optional default. |
returns |
Yes | Object with type ("string" or "object") and description of the output. |
execution.argStyle |
Yes | How arguments are passed: "positional" (ordered args), "cli" (--flag value), or "json" (JSON via stdin). |
execution.fileAccess |
Yes | File system access level: "none", "read", "write", or "readwrite". |
execution.timeout |
No | Maximum execution time in milliseconds (default: 30000). |
execution.memoryLimit |
No | Maximum WASM memory in pages (each page = 64 KB). |
pipeable |
No | If true, the tool is registered as a pipeable command and can participate in pipe chains. The primary text input parameter receives piped stdin. Default: false. |
category |
Yes | Tool category (e.g. "crypto", "text", "data", "file", "code"). |
author |
No | Author name. |
license |
No | License identifier (e.g. "MIT"). |
homepage |
No | URL for the tool's homepage or repository. |
positional — Arguments are passed as ordered command-line arguments: [tool_name, arg1, arg2, ...] based on the order defined in required.cli — Arguments are passed as CLI flags: [tool_name, --key, value, --key, value].json — Arguments are serialized as a JSON object and passed via stdin.The recommended approach for C/C++ tools. Install the WASI SDK v24:
# Download WASI SDK v24 (or check releases for newer versions)
# https://github.com/WebAssembly/wasi-sdk/releases/tag/wasi-sdk-24
export WASI_SDK_PATH=/path/to/wasi-sdk
# Compile a C program to WASM
$WASI_SDK_PATH/bin/clang \
--target=wasm32-wasi \
-O2 \
-o my-tool.wasm \
main.c
Important: Do not use the --no-entry flag for WASI programs with a main() function. WASI programs export _start as the entry point, which calls main().
WASI_SDK_PATH ?= /opt/wasi-sdk
CC = $(WASI_SDK_PATH)/bin/clang
CFLAGS = --target=wasm32-wasi -O2
LDFLAGS = -Wl,--export-all
OUTPUT_DIR = ../../binaries
all: $(OUTPUT_DIR)/my-tool.wasm
$(OUTPUT_DIR)/my-tool.wasm: main.c | $(OUTPUT_DIR)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $<
$(OUTPUT_DIR):
mkdir -p $(OUTPUT_DIR)
clean:
rm -f $(OUTPUT_DIR)/my-tool.wasm
Rust has first-class WASM support via the wasm32-wasi target:
# Add the WASI target
rustup target add wasm32-wasi
# Build your project
cargo build --release --target wasm32-wasi
# The binary will be at:
# target/wasm32-wasi/release/your_tool.wasm
Co-do runs WebAssembly modules in a WASI runtime. To build Go tools that are compatible with WASI (and therefore with Co-do), use TinyGo with the WASI target:
# Build using TinyGo with the WASI target (required for Co-do)
tinygo build -o my-tool.wasm -target=wasi main.go
Important: The standard Go WebAssembly target (GOOS=js GOARCH=wasm) produces binaries that require the wasm_exec.js JavaScript shim and are designed for browser/JavaScript environments, not WASI. These binaries will not run in Co-do's WASI runtime. Always use TinyGo with -target=wasi when building Go tools for Co-do.
Once you have your compiled .wasm binary and your manifest.json, create the ZIP:
# Create a ZIP containing both files
zip my-tool.zip manifest.json my-tool.wasm
Or if you're using the Co-do build system:
# From the wasm-tools directory, the build script handles this automatically
./build.sh my-tool
# Packages are output to wasm-tools/dist/
# Each tool gets: my-tool.zip, my-tool.manifest.json
Tip: The Co-do repository includes a full build system at wasm-tools/build.sh that automatically downloads the WASI SDK, compiles tools, generates manifests, and creates ZIP packages.
.zip package file.Installed tools appear in the WebAssembly Tools list where you can enable/disable or delete them. Each tool's permission defaults to "Ask Each Time", meaning the AI will request your approval before running it.
When the AI invokes your tool, Co-do executes it in one of two modes:
"fileAccess": "none". The WASM binary runs in an isolated Web Worker thread, keeping the UI responsive. Network access is blocked."read", "write", "readwrite"). These tools can access the opened project directory through a virtual file system bridge.argStyle is "json".stdio.h, string.h, stdlib.h, etc.).
WASM tools can participate in pipe chains, where the output of one command
flows as input to the next — just like Unix pipes. To make your tool pipeable, add
"pipeable": true to your manifest.json.
Not all tools are pipeable. Only tools that accept text input and produce
text output are good candidates. Tools that require multiple non-text inputs, produce binary
output, or have no natural stdin concept should omit this field (or set it to false).
When a tool is marked as pipeable, Co-do automatically registers it in the
pipe command registry. In a pipe chain, the output of the previous command
is injected as the tool's primary text input parameter (typically named input).
All other parameters can still be passed explicitly via args.
{
"name": "base64",
"version": "1.0.0",
"description": "Encode or decode data using Base64 encoding.",
"parameters": {
"type": "object",
"properties": {
"mode": {
"type": "string",
"enum": ["encode", "decode"],
"description": "Whether to encode or decode"
},
"input": {
"type": "string",
"description": "The text to encode, or base64 string to decode"
}
},
"required": ["mode", "input"]
},
"returns": { "type": "string", "description": "The encoded or decoded result" },
"execution": { "argStyle": "positional", "fileAccess": "none", "timeout": 5000 },
"pipeable": true,
"category": "crypto"
}
With pipeable: true, the AI can chain your tool with other commands:
// Read a file, then base64-encode the content
{
"commands": [
{ "tool": "cat", "args": { "path": "secret.txt" } },
{ "tool": "base64", "args": { "mode": "encode" } }
]
}
// Decode base64 content, then count lines
{
"commands": [
{ "tool": "base64", "args": { "mode": "decode", "input": "SGVsbG8KV29ybGQ=" } },
{ "tool": "wc", "args": {} }
]
}
Co-do determines which parameter receives piped input using these rules (in order):
string parameter named input or textstring parameterinput or text
When piped input is available and the resolved parameter is not explicitly provided
in args, the piped input is automatically injected.
pipeable| Good Candidates | Not Suitable |
|---|---|
| Text processors (grep, sed, awk, sort) | Multi-input tools (diff requires two texts) |
| Encoders/decoders (base64, xxd) | Generators with no input (uuid) |
| Format converters (toml2json, markdown) | Binary I/O tools (gzip) |
| Code formatters (shfmt, minify) | Database engines (sqlite3) |
| Hash functions (md5sum, sha256sum) | File metadata tools (stat, du) |
| Constraint | Limit |
|---|---|
| Maximum ZIP file size | 50 MB |
| Maximum WASM binary size | 20 MB |
| Maximum files in ZIP | 100 |
| Default execution timeout | 30 seconds (configurable per-tool) |
| WASM magic number | Must start with 00 61 73 6d (\0asm) |
Additional security measures:
.. or absolute paths are rejected.Here's a complete example of a simple base64 encode/decode tool written in C.
main.c)#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static const char b64_table[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static int b64_index(char c) {
if (c >= 'A' && c <= 'Z') return c - 'A';
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
if (c >= '0' && c <= '9') return c - '0' + 52;
if (c == '+') return 62;
if (c == '/') return 63;
return -1;
}
void encode(const char *input) {
size_t len = strlen(input);
for (size_t i = 0; i < len; i += 3) {
unsigned char b0 = input[i];
unsigned char b1 = (i + 1 < len) ? input[i + 1] : 0;
unsigned char b2 = (i + 2 < len) ? input[i + 2] : 0;
putchar(b64_table[b0 >> 2]);
putchar(b64_table[((b0 & 0x03) << 4) | (b1 >> 4)]);
putchar((i + 1 < len) ? b64_table[((b1 & 0x0f) << 2) | (b2 >> 6)] : '=');
putchar((i + 2 < len) ? b64_table[b2 & 0x3f] : '=');
}
putchar('\n');
}
void decode(const char *input) {
size_t len = strlen(input);
for (size_t i = 0; i < len; i += 4) {
int a = b64_index(input[i]);
int b = b64_index(input[i + 1]);
int c = (i + 2 < len && input[i + 2] != '=') ? b64_index(input[i + 2]) : 0;
int d = (i + 3 < len && input[i + 3] != '=') ? b64_index(input[i + 3]) : 0;
if (a < 0 || b < 0) break;
putchar((a << 2) | (b >> 4));
if (i + 2 < len && input[i + 2] != '=')
putchar(((b & 0x0f) << 4) | (c >> 2));
if (i + 3 < len && input[i + 3] != '=')
putchar(((c & 0x03) << 6) | d);
}
putchar('\n');
}
int main(int argc, char **argv) {
if (argc < 3) {
fprintf(stderr, "Usage: base64 <encode|decode> <input>\n");
return 1;
}
if (strcmp(argv[1], "encode") == 0) {
encode(argv[2]);
} else if (strcmp(argv[1], "decode") == 0) {
decode(argv[2]);
} else {
fprintf(stderr, "Unknown mode: %s\n", argv[1]);
return 1;
}
return 0;
}
manifest.json){
"name": "base64",
"version": "1.0.0",
"description": "Encode or decode data using Base64 encoding. Use 'encode' to convert text to Base64, or 'decode' to convert Base64 back to text.",
"parameters": {
"type": "object",
"properties": {
"mode": {
"type": "string",
"enum": ["encode", "decode"],
"description": "Whether to encode or decode"
},
"input": {
"type": "string",
"description": "The text to encode, or base64 string to decode"
}
},
"required": ["mode", "input"]
},
"returns": {
"type": "string",
"description": "The encoded or decoded result"
},
"execution": {
"argStyle": "positional",
"fileAccess": "none",
"timeout": 5000
},
"category": "crypto",
"author": "Co-do",
"license": "MIT"
}
# Compile
$WASI_SDK_PATH/bin/clang --target=wasm32-wasi -O2 -o base64.wasm main.c
# Package
zip base64.zip manifest.json base64.wasm
# Install via Co-do UI: Settings → WebAssembly Tools → Install Tool
Co-do is open source. The WebAssembly tool system's source code, including the loader, runtime, and example tools, is available on GitHub:
| File | Purpose |
|---|---|
src/wasm-tools/loader.ts |
ZIP loading, validation, and manifest parsing |
src/wasm-tools/manager.ts |
Tool lifecycle management and execution |
src/wasm-tools/types.ts |
TypeScript interfaces and Zod schemas |
src/wasm-tools/runtime.ts |
WASM execution runtime environment |
src/wasm-tools/vfs.ts |
Virtual file system for sandboxed I/O |
src/wasm-tools/worker-manager.ts |
Web Worker isolation for tool execution |
wasm-tools/build.sh |
Automated build script for all tools |
wasm-tools/src/ |
Source code for built-in tools (C) |
wasm-tools/manifests/ |
Manifest files for built-in tools |