
Skill Bundle Fabric
- 1 installs
- Updated April 3, 2026
- unbrowse-ai/skill-bundle-fabric
Develops, packages, shares, indexes, and host-routes reusable skill bundles from one preset JSON, generating install commands, routing rules, and registry metadata across Codex, Claude, and OpenClaw.
About
Turns a reusable workflow into one portable, installable skill bundle with share manifest, registry metadata, and host-memory routing derived from a single preset. A developer uses it when a multi-skill workflow should become one portable bundle that works across hosts.
- Single preset drives bundle build, share, index, and host-memory routing
- Build and write-host-memory scripts; backend-ready artifacts
Skill Bundle Fabric by the numbers
- 1 all-time installs (skills.sh)
- Ranked #644 of 782 Skill Development skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/unbrowse-ai/skill-bundle-fabric --skill skill-bundle-fabricAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| Last updated | April 3, 2026 |
| Repository | unbrowse-ai/skill-bundle-fabric ↗ |
What it does
Develops, packages, shares, indexes, and host-routes reusable skill bundles from one preset JSON, generating install commands, routing rules, and registry metadata across Codex, Claude, and OpenClaw.
Files
Skill Bundle Fabric
Core job:
- turn a reusable workflow into one portable skill bundle that can be developed, shared, indexed, and routed through host memory
Use this skill when:
- a workflow needs to become a portable multi-skill bundle
- the bundle should ship install commands, routing rules, and registry metadata together
- the same bundle should work across Codex, Claude, or OpenClaw
- the bundle should be shareable by files now and indexable by backend later
Do not use this skill for:
- building one domain skill with no bundle/share/index concern
- publishing runtime API routes to a marketplace
- writing free-form memory notes with no executable routing value
Workflow:
1. Start from the workflow bundle outcome: skills, routes, share path, index metadata. 2. Encode that in a preset JSON. 3. Run node scripts/build-bundle.mjs --preset presets/<name>.json --out dist. 4. Run node scripts/write-host-memory.mjs --preset presets/<name>.json --host <host> --scope <scope>. 5. Verify the generated bundle, share manifest, registry entry, and host-memory block. 6. Publish or share the generated artifacts with the target repo/host/backend.
Load-bearing rules:
- one bundle preset is the source of truth
- install commands, routing rules, share metadata, and index metadata stay derived from the same preset
- the memory block must say which skill to call, not just what to install
- support host-specific root files:
AGENTS.md,CLAUDE.md,MEMORY.md - build artifacts must be backend-ready even if the backend is not live yet
.DS_Store
dist/
MIT License
Copyright (c) 2026 Unbrowse
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
{
"name": "skill-bundle-fabric",
"private": true,
"type": "module",
"scripts": {
"build": "node scripts/build-bundle.mjs --preset presets/unbrowse-workflows.json --out dist",
"test": "node --test tests/*.test.mjs"
}
}
{
"bundle_id": "unbrowse-workflows",
"title": "Unbrowse Workflow Bundle",
"repo": "https://github.com/unbrowse-ai/unbrowse",
"bootstrap_skill": "find-skills",
"skills": [
"history-skill-miner",
"docs-release-sync",
"skill-surface-ship",
"main-actions-triage"
],
"routes": [
{
"when": "the request is about turning repeated local workflows into skills",
"call": "history-skill-miner"
},
{
"when": "the request is about README, changelog, or release-note alignment",
"call": "docs-release-sync"
},
{
"when": "the request is about skill install, publish, packaging, or sync surface changes",
"call": "skill-surface-ship"
},
{
"when": "the request is about blockers or status on main CI or deploy runs",
"call": "main-actions-triage"
}
],
"share": {
"transport": "files",
"manifest_path": "/.well-known/skill-bundles/unbrowse-workflows/share.json"
},
"index": {
"slug": "unbrowse-workflows",
"summary": "Develop, share, and route Unbrowse workflow skills as one portable bundle.",
"tags": ["unbrowse", "skills", "workflow-bundle", "memory-routing"]
}
}
skill-bundle-fabric
Develop, package, share, index, and host-route reusable skill bundles.
This repo provides one installable skill, skill-bundle-fabric. It treats a skill bundle as one config-driven unit:
- skills to install
- request shapes -> skill calls
- share artifacts
- registry/index metadata
- host memory blocks
Supported hosts:
- Codex:
AGENTS.md - Claude:
CLAUDE.md - OpenClaw:
MEMORY.md
Install
npx skills add https://github.com/unbrowse-ai/skill-bundle-fabric --skill skill-bundle-fabricMain flow
Build all bundle artifacts:
node scripts/build-bundle.mjs \
--preset presets/unbrowse-workflows.json \
--out distWrite host memory:
node scripts/write-host-memory.mjs \
--preset presets/unbrowse-workflows.json \
--host claude \
--scope agentOutput artifacts
build-bundle writes:
bundle.json— canonical bundle manifestshare.json— file-share/p2p manifestregistry-entry.json— backend/index ingestion payloadhosts/<host>/<file>— host-ready memory snippets
Preset shape
{
"bundle_id": "unbrowse-workflows",
"title": "Unbrowse Workflow Bundle",
"repo": "https://github.com/unbrowse-ai/unbrowse",
"bootstrap_skill": "find-skills",
"skills": ["skill-a", "skill-b"],
"routes": [
{ "when": "the request is about X", "call": "skill-a" }
],
"share": {
"transport": "files",
"manifest_path": "/.well-known/skill-bundles/unbrowse-workflows/share.json"
},
"index": {
"slug": "unbrowse-workflows",
"summary": "What this bundle is for",
"tags": ["bundle", "workflow"]
}
}Test
node --test tests/*.test.mjsHost Targets
Files
- Codex project memory:
./AGENTS.md - Codex agent memory:
~/.codex/AGENTS.md - Claude project memory:
./CLAUDE.md - Claude agent memory:
~/.claude/CLAUDE.md - OpenClaw project memory:
./MEMORY.md - OpenClaw agent memory:
$OPENCLAW_HOME/MEMORY.md
Routing Rule
- each route line must say:
If the request is about X, call Y - install commands live in the same managed block
- reruns replace the managed block instead of duplicating it
Bundle Outputs
bundle.json— canonical bundle manifestshare.json— share/export manifestregistry-entry.json— backend/index ingestion payloadhosts/<host>/<file>— rendered host snippets
Preset Schema
Required fields:
bundle_idtitlerepobootstrap_skillskills[]routes[]share.transportshare.manifest_pathindex.slugindex.summaryindex.tags[]
Route fields
when: request-shape clausecall: skill name to invoke
Generated bundle manifest
- repo/install truth
- routing truth
- host targets
- derived install commands
Generated registry entry
- backend/index oriented
- keeps
slug,summary,tags,skills,routes - safe to POST to a future registry backend
#!/usr/bin/env node
import { mkdirSync, writeFileSync } from "node:fs";
import path from "node:path";
import {
buildBundleManifest,
buildHostTargets,
buildRegistryEntry,
buildShareManifest,
parseArgs,
readPreset,
renderMemoryBlock,
validatePreset,
} from "./lib.mjs";
function writeJson(filePath, value) {
mkdirSync(path.dirname(filePath), { recursive: true });
writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n");
}
function main() {
const flags = parseArgs(process.argv);
const outRoot = path.resolve(String(flags.out || "dist"));
const preset = validatePreset(readPreset(flags.preset));
const bundleDir = path.join(outRoot, preset.bundle_id);
writeJson(path.join(bundleDir, "bundle.json"), buildBundleManifest(preset));
writeJson(path.join(bundleDir, "share.json"), buildShareManifest(preset));
writeJson(path.join(bundleDir, "registry-entry.json"), buildRegistryEntry(preset));
for (const target of buildHostTargets()) {
const outputPath = path.join(bundleDir, target.snippet_path);
mkdirSync(path.dirname(outputPath), { recursive: true });
writeFileSync(outputPath, renderMemoryBlock(preset, target.host));
}
process.stdout.write(JSON.stringify({
bundle_id: preset.bundle_id,
output_dir: bundleDir,
files: [
"bundle.json",
"share.json",
"registry-entry.json",
...buildHostTargets().map((target) => target.snippet_path),
],
}, null, 2) + "\n");
}
main();
import { existsSync, readFileSync } from "node:fs";
import os from "node:os";
import path from "node:path";
export const MARKER_BEGIN = "<!-- SKILL_BUNDLE_FABRIC:BEGIN -->";
export const MARKER_END = "<!-- SKILL_BUNDLE_FABRIC:END -->";
export const HOST_FILES = {
codex: "AGENTS.md",
claude: "CLAUDE.md",
openclaw: "MEMORY.md",
};
export function parseArgs(argv) {
const flags = {};
for (let i = 2; i < argv.length; i += 1) {
const arg = argv[i];
if (!arg.startsWith("--")) continue;
const key = arg.slice(2);
const next = argv[i + 1];
if (!next || next.startsWith("--")) {
flags[key] = true;
continue;
}
flags[key] = next;
i += 1;
}
return flags;
}
export function resolveHome() {
return process.env.HOME || os.homedir();
}
export function readPreset(presetPath) {
if (!presetPath) throw new Error("--preset is required");
return JSON.parse(readFileSync(path.resolve(presetPath), "utf8"));
}
export function validatePreset(preset) {
const required = ["bundle_id", "title", "repo", "bootstrap_skill", "skills", "routes", "share", "index"];
for (const key of required) {
if (!(key in preset)) throw new Error(`Missing preset field: ${key}`);
}
if (!Array.isArray(preset.skills) || preset.skills.length === 0) throw new Error("skills[] required");
if (!Array.isArray(preset.routes) || preset.routes.length === 0) throw new Error("routes[] required");
if (!preset.share?.manifest_path) throw new Error("share.manifest_path required");
if (!preset.index?.slug || !preset.index?.summary || !Array.isArray(preset.index?.tags)) {
throw new Error("index.slug, index.summary, index.tags[] required");
}
for (const route of preset.routes) {
if (!route.when || !route.call) throw new Error("each route needs when + call");
}
return preset;
}
export function installCommands(preset) {
return preset.skills.map((skill) => `npx skills add ${preset.repo} --skill ${skill} --yes`);
}
export function buildHostTargets() {
return [
{ host: "codex", target_path: "./AGENTS.md", snippet_path: "hosts/codex/AGENTS.md" },
{ host: "claude", target_path: "./CLAUDE.md", snippet_path: "hosts/claude/CLAUDE.md" },
{ host: "openclaw", target_path: "./MEMORY.md", snippet_path: "hosts/openclaw/MEMORY.md" },
];
}
export function renderMemoryBlock(preset, host) {
const commands = installCommands(preset);
const lines = [
MARKER_BEGIN,
`## ${preset.title} (${host})`,
"",
"Install from repo:",
...commands.map((command) => `- \`${command}\``),
"",
"Skill-call routing defaults:",
...preset.routes.map((route) => `- If ${route.when}, call \`${route.call}\`.`),
"",
`Use \`${preset.bootstrap_skill}\` first when available to confirm/install the right skill, then call that skill.`,
MARKER_END,
"",
];
return lines.join("\n");
}
export function upsertManagedBlock(existing, block) {
if (existing.includes(MARKER_BEGIN) && existing.includes(MARKER_END)) {
return existing.replace(new RegExp(`${MARKER_BEGIN}[\\s\\S]*?${MARKER_END}\\n?`, "m"), block);
}
return `${existing.replace(/\s*$/, "")}${existing.trim().length > 0 ? "\n\n" : ""}${block}`;
}
export function targetFileFor(host, scope, cwd) {
const home = resolveHome();
const projectFiles = {
codex: path.join(cwd, "AGENTS.md"),
claude: path.join(cwd, "CLAUDE.md"),
openclaw: path.join(cwd, "MEMORY.md"),
};
const agentFiles = {
codex: path.join(home, ".codex", "AGENTS.md"),
claude: path.join(home, ".claude", "CLAUDE.md"),
openclaw: process.env.OPENCLAW_HOME ? path.join(process.env.OPENCLAW_HOME, "MEMORY.md") : null,
};
if (!(host in projectFiles)) throw new Error(`Unsupported host: ${host}`);
if (scope === "project") return projectFiles[host];
if (scope === "agent") {
if (!agentFiles[host]) throw new Error(`No agent memory target for host: ${host}`);
return agentFiles[host];
}
if (existsSync(projectFiles[host])) return projectFiles[host];
if (agentFiles[host] && existsSync(agentFiles[host])) return agentFiles[host];
return projectFiles[host];
}
export function buildBundleManifest(preset) {
return {
bundle_id: preset.bundle_id,
title: preset.title,
repo: preset.repo,
bootstrap_skill: preset.bootstrap_skill,
skills: preset.skills,
routes: preset.routes,
install_commands: installCommands(preset),
host_targets: buildHostTargets(),
share: preset.share,
index: preset.index,
};
}
export function buildShareManifest(preset) {
return {
bundle_id: preset.bundle_id,
transport: preset.share.transport,
manifest_path: preset.share.manifest_path,
repo: preset.repo,
skills: preset.skills,
install_commands: installCommands(preset),
host_targets: buildHostTargets(),
};
}
export function buildRegistryEntry(preset) {
return {
slug: preset.index.slug,
title: preset.title,
summary: preset.index.summary,
tags: preset.index.tags,
repo: preset.repo,
bundle_id: preset.bundle_id,
bootstrap_skill: preset.bootstrap_skill,
skills: preset.skills,
routes: preset.routes,
};
}
#!/usr/bin/env node
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import path from "node:path";
import {
parseArgs,
readPreset,
renderMemoryBlock,
targetFileFor,
upsertManagedBlock,
validatePreset,
} from "./lib.mjs";
function main() {
const flags = parseArgs(process.argv);
const host = String(flags.host || "");
const scope = String(flags.scope || "auto");
const cwd = path.resolve(String(flags.cwd || process.cwd()));
const preset = validatePreset(readPreset(flags.preset));
const targetFile = targetFileFor(host, scope, cwd);
const block = renderMemoryBlock(preset, host);
const existing = existsSync(targetFile) ? readFileSync(targetFile, "utf8") : "";
const updated = upsertManagedBlock(existing, block);
mkdirSync(path.dirname(targetFile), { recursive: true });
writeFileSync(targetFile, updated);
process.stdout.write(JSON.stringify({
host,
scope,
target_file: targetFile,
bundle_id: preset.bundle_id,
skills: preset.skills,
repo: preset.repo,
}, null, 2) + "\n");
}
main();
import test from "node:test";
import assert from "node:assert/strict";
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
import os from "node:os";
import path from "node:path";
import { execFileSync } from "node:child_process";
import { fileURLToPath } from "node:url";
const tmpDirs = [];
const REPO_ROOT = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
test.afterEach(() => {
while (tmpDirs.length > 0) {
rmSync(tmpDirs.pop(), { recursive: true, force: true });
}
});
test("builds bundle, share, registry, and host artifacts from one preset", () => {
const outDir = mkdtempSync(path.join(os.tmpdir(), "skill-bundle-build-"));
tmpDirs.push(outDir);
const stdout = execFileSync(process.execPath, [
"scripts/build-bundle.mjs",
"--preset", "presets/unbrowse-workflows.json",
"--out", outDir,
], {
cwd: REPO_ROOT,
encoding: "utf8",
});
const result = JSON.parse(stdout);
const bundleDir = path.join(outDir, "unbrowse-workflows");
const bundle = JSON.parse(readFileSync(path.join(bundleDir, "bundle.json"), "utf8"));
const share = JSON.parse(readFileSync(path.join(bundleDir, "share.json"), "utf8"));
const registry = JSON.parse(readFileSync(path.join(bundleDir, "registry-entry.json"), "utf8"));
const claude = readFileSync(path.join(bundleDir, "hosts", "claude", "CLAUDE.md"), "utf8");
assert.equal(result.bundle_id, "unbrowse-workflows");
assert.equal(bundle.bundle_id, "unbrowse-workflows");
assert.equal(share.transport, "files");
assert.equal(registry.slug, "unbrowse-workflows");
assert.match(claude, /call `main-actions-triage`/);
});
import test from "node:test";
import assert from "node:assert/strict";
import { mkdtempSync, mkdirSync, readFileSync, rmSync } from "node:fs";
import os from "node:os";
import path from "node:path";
import { execFileSync } from "node:child_process";
import { fileURLToPath } from "node:url";
const tmpDirs = [];
const REPO_ROOT = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
test.afterEach(() => {
while (tmpDirs.length > 0) {
rmSync(tmpDirs.pop(), { recursive: true, force: true });
}
});
test("writes Claude agent memory with executable routing rules", () => {
const home = mkdtempSync(path.join(os.tmpdir(), "skill-bundle-fabric-"));
tmpDirs.push(home);
mkdirSync(path.join(home, ".claude"), { recursive: true });
const stdout = execFileSync(process.execPath, [
"scripts/write-host-memory.mjs",
"--preset", "presets/unbrowse-workflows.json",
"--host", "claude",
"--scope", "agent",
"--cwd", home,
], {
cwd: REPO_ROOT,
env: { ...process.env, HOME: home },
encoding: "utf8",
});
const result = JSON.parse(stdout);
const target = path.join(home, ".claude", "CLAUDE.md");
const written = readFileSync(target, "utf8");
assert.equal(result.target_file, target);
assert.match(written, /call `history-skill-miner`/);
assert.match(written, /call `docs-release-sync`/);
});
test("prefers project memory in auto scope when the file exists", () => {
const home = mkdtempSync(path.join(os.tmpdir(), "skill-bundle-fabric-project-"));
tmpDirs.push(home);
mkdirSync(path.join(home, ".codex"), { recursive: true });
execFileSync("sh", ["-lc", "printf '# local\\n' > AGENTS.md"], { cwd: home });
execFileSync(process.execPath, [
"scripts/write-host-memory.mjs",
"--preset", "presets/unbrowse-workflows.json",
"--host", "codex",
"--scope", "auto",
"--cwd", home,
], {
cwd: REPO_ROOT,
env: { ...process.env, HOME: home },
encoding: "utf8",
});
const written = readFileSync(path.join(home, "AGENTS.md"), "utf8");
assert.match(written, /SKILL_BUNDLE_FABRIC/);
});