CLI and programmatic toolkit to scaffold, validate, pack, and publish GRAFTs (Guayaba Runtime Agent Framework Templates) from existing agent workspaces.
A GRAFT is a reusable, declarative agent template that captures everything needed to reproduce an agent — its system prompt, skills, model settings, and metadata — so it can be shared and reinstalled from the Guayaba marketplace.
npm i -g @guayaba/graft-cliOr run on demand:
npx @guayaba/graft-cli --helpThe CLI generates a self-contained scaffold directory from your agent's workspace. The scaffold contains:
graft.json— declarative schema + marketplace metadata.- Markdown sidecar stubs named after their wizard field (e.g.
personality.md,vibe.md,extra_instructions.mdfor openclaw). Each stub contains only an instructional comment; the author fills in the template content by hand. The CLI inlines them into the schema'sdefaultsat validate / pack / push time. - A copy of the workspace's
skills/directory (andTOOLS.mdwhen present), so the scaffold is the only input the rest of the pipeline needs. - An optional
install.shat the scaffold root, copied verbatim from the workspace if present. The launcher runs it once on the agent's first boot to install any binary your bundled skills depend on (e.g.gh,aws). See Optionalinstall.shhook below.
# 1. Generate the scaffold from an existing agent workspace.
graft init --framework openclaw -w ./my-agent -o ./graft
# 2. Fill in the sidecar stubs with your template content.
$EDITOR ./graft/personality.md
# 3. Validate against the Guayaba backend.
graft validate --framework openclaw -i ./graft
# 4. (optional) Pack a tarball locally without uploading.
graft pack --framework openclaw -i ./graft -o ./graft.tar.gz
# 5. Push to your tenant-scoped personal storage on the Guayaba backend.
graft push --framework openclaw -i ./graftEvery command requires --framework <slug>. Supported today:
openclaw. The flag drives the sidecar → schema field mapping (see
framework mapping below).
Global flags available on the root graft command:
-v, --version Print the installed version and exit.
--help Print help.
Create a scaffold directory: graft.json, blank markdown sidecar stubs
for you to fill in, and a copy of the workspace's skills.
Required:
--framework <slug> Source framework. Today: openclaw.
Options:
-w, --workspace <path> Workspace root to inspect. Defaults to cwd.
-o, --out <path> Scaffold dir to create. Defaults to ./graft.
Behaviour:
- Reads the workspace, builds the structural schema (channels, model,
thinking) via the framework-specific extractor. Channel constraint
(openclaw): only
telegramis supported indefaults.channels. Specifying any other channel will causeinitto exit with an error. - Asks for marketplace metadata interactively (slug, name, description, version, tags, categories, author).
- Creates a sidecar stub for each mapped field (
personality.md,vibe.md,extra_instructions.mdfor openclaw). Each stub contains only an instructional HTML comment; the author writes the template content themselves. Workspace content is intentionally not copied — copying verbatim would duplicate the text when the GRAFT is applied, because the framework combines personality and extra_instructions. - Copies the workspace's installed skills into
<scaffold>/skills/, flattening<workspace>/skills/and<workspace>/.agents/skills/into a single tree. Also copiesTOOLS.mdif present. The scaffold is then self-contained —packandpushconsume only the scaffold. - Writes
graft.jsonlast so the scaffold is consistent on Ctrl-C. - If the scaffold dir already exists, it asks for confirmation and then wipes it (re-init regenerates from scratch — sidecars and skills are overwritten). Refuses non-interactively.
Inline the sidecars into the schema and POST the envelope to the Guayaba backend, which is the authoritative validator.
Required:
--framework <slug>
Options:
-i, --input <path> Scaffold dir. Defaults to cwd.
The CLI does not pre-validate field caps locally — it defers to the backend's 422 response.
Authentication: requires a tenant-bound master API key (g_master_*).
Read from $GUAYABA_API_KEY or prompted in a TTY. Agent-scoped keys
(g_agent_*) are rejected with 403.
Inline the sidecars and build a graft.tar.gz locally without
uploading. Useful for inspection and CI artefacts.
Required:
--framework <slug>
Options:
-i, --input <path> Scaffold dir. Defaults to cwd.
-o, --out <path> Output tarball. Defaults to ./graft.tar.gz.
-f, --force Overwrite the output if it exists.
Skills (and TOOLS.md) are read straight from the scaffold —
init already copied them there.
Same as pack, but uploads to tenant-scoped personal storage on the
Guayaba backend instead of writing locally. Drafts are private until
explicitly submitted for review.
Required:
--framework <slug>
Options:
-i, --input <path> Scaffold dir. Defaults to cwd.
--icon <path> Optional icon image (PNG/JPG/WebP, ≤ 1 MB).
--cover <path> Optional cover image (PNG/JPG/WebP, ≤ 4 MB).
Authentication: requires a tenant-bound master API key (g_master_*).
Read from $GUAYABA_API_KEY or prompted in a TTY. Agent-scoped keys
(g_agent_*) are rejected with 403.
The CLI does not accept a tenant selector and does not send a tenant header.
The backend derives ownership from the master API key and stores bundles under
the corresponding personal/{tenant_id}/... prefix.
Immutable versions: pushing the same
(slug, version)pair twice returns409 Conflict. Bumpmetadata.versioningraft.jsonto push a new revision.
Each framework defines a fixed registry of scaffold sidecar → schema
field entries. The CLI uses it to create stubs on init and to inline
content on validate/pack/push.
| Framework | Scaffold sidecar | Maps to (workspace file) | Schema field |
|---|---|---|---|
| openclaw | personality.md |
SOUL.md |
defaults.personality |
| openclaw | vibe.md |
IDENTITY.md |
defaults.vibe |
| openclaw | extra_instructions.md |
AGENTS.md |
defaults.settings.extra_instructions |
Empty / whitespace-only sidecars are treated as absent — the
corresponding field stays out of defaults.
The registry lives in
src/framework/mapping.ts; the inlining
logic lives in src/framework/sidecars.ts.
If the source workspace has an install.sh at its root,
graft init copies it into the scaffold and graft pack / graft push ship it inside the bundle. The launcher (openclaw-launcher's
graft-apply.ts) then runs it once on the agent's first boot.
Use it for runtime binary setup that can't be expressed declaratively — typically installing a CLI your bundled skill depends on:
#!/bin/sh
# Make `gh` available so the bundled `github` skill can use it.
set -e
command -v gh >/dev/null && exit 0
apt-get update
apt-get install -y --no-install-recommends gh
echo "gh" >> "/mnt/efs/agents/$AGENT_ID/.deps/apt-packages.txt"Runtime contract enforced by the launcher:
- Invocation:
/bin/sh install.sh, working dir is the bundle's extract dir. - Runs as root inside the agent container;
apt-get install -yworks without sudo. - Hard timeout: 5 minutes (SIGKILL on overrun).
- stdout → launcher logs at
info, stderr atwarn. - Non-zero exit aborts the apply and the marker is not written, so
the launcher retries on next boot. Use
set -e. - Must be idempotent.
When not to use it:
- Per-user secrets → declare a
secretfield inside theschemaobject ingraft.jsonwith amaterializeblock.materializere-runs on every boot/reload to follow rotations;install.shruns only once. - Things the agent will install on demand anyway → if the cost of one
user message waiting on
apt-get installis acceptable, skip the hook entirely. The agent template tells the LLM it has root and can install anything it needs.
The full author-facing reference is in the public docs: guayaba-docs/guides/authoring-grafts.md.
| Variable | Required | Description |
|---|---|---|
GUAYABA_API_KEY |
Yes (for validate / push) |
Tenant-bound master API key (g_master_*). Prompted interactively when missing and stdin is a TTY. |
GUAYABA_API_BASE_URL |
No | Override the API base URL. Defaults to https://api.guayaba.run/api/v1. |
import {
readOpenclawWorkspace,
extractOpenclawSummary,
buildGraftFromOpenclaw,
defaultMetadataFor,
buildGraftBundle,
validateGraftPackage,
pushGraftPackage,
} from '@guayaba/graft-cli';Reach into src/framework/ for the sidecar
machinery if you need to inline outside the CLI shell.
npm install
npm run build # tsup → dist/
npm run test # vitest
npm run typecheck # tsc --noEmitNode ≥ 18 (uses globalThis.fetch, FormData, Blob).