aiwg
Version:
Deployment tool and support utility for AI context. Copies agents, skills, commands, rules, and behaviors into the paths each AI platform reads (Claude Code, Codex, Copilot, Cursor, Warp, OpenClaw, and 6 more) so one source of truth works across 10 platfo
113 lines • 4.35 kB
JavaScript
/**
* ClawHub Package Adapter
*
* Implements PackageRegistryAdapter for the `clawhub:` and `openclaw:` URI
* schemes, routing `aiwg install clawhub:<owner>/<name>` through the
* ClawHub REST API at clawhub.ai.
*
* The adapter fetches the package manifest and skill content via the API,
* writes the result into the standard AIWG cache layout, and returns the
* local cache path — matching the contract of all other adapters.
*
* Cache layout (shared with GitAdapter):
* ~/.cache/aiwg/packages/<owner>/<name>@<version>/
*
* @implements #803
* @see https://clawhub.ai
* @see https://docs.openclaw.ai/tools/clawhub
*/
import { mkdir, writeFile } from 'fs/promises';
import { join } from 'path';
import { existsSync } from 'fs';
import { buildCachePath } from './git.js';
const CLAWHUB_API_BASE = 'https://clawhub.ai/api/v1';
/**
* Attempt a ClawHub API request, returning parsed JSON or null on failure.
*/
async function clawHubFetch(endpoint) {
try {
const response = await fetch(`${CLAWHUB_API_BASE}${endpoint}`, {
headers: { Accept: 'application/json' },
signal: AbortSignal.timeout(30_000),
});
if (!response.ok)
return null;
return await response.json();
}
catch {
return null;
}
}
/**
* ClawHubPackageAdapter
*
* Handles "clawhub:owner/name[@version]" and "openclaw:owner/name[@version]"
* references in the install pipeline.
*/
export class ClawHubPackageAdapter {
id = 'clawhub';
name = 'ClawHub Registry';
/**
* Matches the `clawhub:` and `openclaw:` URI schemes.
*/
canResolve(ref) {
return ref.startsWith('clawhub:') || ref.startsWith('openclaw:');
}
/**
* Resolve a parsed PackageRef to a PackageSource.
*
* The `gitUrl` field is a canonical API URL (not a git remote) so the
* caller can store it in packages.yaml for display and re-install.
*/
async resolve(ref) {
if (!ref.owner || !ref.name)
return null;
const apiUrl = `${CLAWHUB_API_BASE}/skills/${encodeURIComponent(ref.owner)}/${encodeURIComponent(ref.name)}`;
return {
gitUrl: apiUrl,
ref: ref.version,
label: `clawhub.ai/${ref.owner}/${ref.name}${ref.version ? `@${ref.version}` : ''}`,
};
}
/**
* Fetch the package from ClawHub and write it to the local cache.
*
* Skips the network request if the cache directory already exists and
* `options.refresh` is not set.
*/
async fetch(source, options = {}) {
// Extract owner/name from the API URL stored in source.gitUrl
const urlMatch = source.gitUrl.match(/\/skills\/([^/]+)\/([^/]+)$/);
const owner = urlMatch ? decodeURIComponent(urlMatch[1]) : 'clawhub';
const name = urlMatch ? decodeURIComponent(urlMatch[2]) : 'package';
const version = source.ref ?? 'latest';
const cachePath = buildCachePath(owner, name, version);
// Return cached version unless refresh is requested
if (!options.refresh && existsSync(cachePath)) {
return cachePath;
}
// Fetch package content from ClawHub API
const downloadEndpoint = `/skills/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/download`;
const apiResult = await clawHubFetch(downloadEndpoint);
if (!apiResult || !apiResult.content) {
throw new Error(`Could not fetch '${owner}/${name}' from ClawHub. ` +
`Ensure the package exists at https://clawhub.ai and the API is reachable. ` +
`(URL: ${CLAWHUB_API_BASE}${downloadEndpoint})`);
}
// Write content to cache
await mkdir(cachePath, { recursive: true });
const skillMdPath = join(cachePath, 'SKILL.md');
await writeFile(skillMdPath, apiResult.content, 'utf-8');
// Write a minimal manifest so detectManifestType can classify it
if (apiResult.manifest) {
const manifestPath = join(cachePath, 'manifest.json');
await writeFile(manifestPath, JSON.stringify(apiResult.manifest, null, 2) + '\n', 'utf-8');
}
return cachePath;
}
/** ClawHubPackageAdapter does not enumerate packages */
async list() {
return [];
}
}
//# sourceMappingURL=clawhub.js.map