install_utility
Fetches and writes a CustomClaw utility payload to a specified directory. For paid utilities, requires a Stripe session ID from the buyer's receipt. Returns written files and npm dependencies but does not run install.
Instructions
Fetch a CustomClaw utility payload and write its files into target_dir. For paid utilities, pass session_id (the Stripe checkout session_id from the buyer's receipt email). Returns the list of files written and any npm dependencies the caller should install — this tool does NOT run npm install itself; the host agent decides when to install deps.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| slug | Yes | The utility slug to install. | |
| target_dir | Yes | Directory to write files into. Absolute path preferred; relative paths are resolved against the server process cwd. | |
| session_id | No | Optional. Stripe checkout session_id for paid utilities. Found as session_id=... in the CustomClaw receipt email download URL. |
Implementation Reference
- index.js:103-174 (handler)The main handler function for the install_utility tool. Fetches payload from the registry API, validates inputs, writes files to target_dir (with path traversal protection), and returns files_written, dependencies, and a message.
async function installUtility({ slug, target_dir, session_id }) { if (!slug || typeof slug !== 'string') { throw new Error('slug is required (string)'); } if (!target_dir || typeof target_dir !== 'string') { throw new Error('target_dir is required (string, absolute or relative directory)'); } const url = new URL(`${SITE_BASE}/api/cli`); url.searchParams.set('slug', slug); if (session_id) url.searchParams.set('session_id', session_id); const res = await fetch(url.toString()); if (res.status === 401) { throw new Error( `This utility is paid and requires proof of purchase. ` + `Pass session_id (the Stripe checkout session_id) — you'll find session_id=... ` + `in the download URL from your CustomClaw receipt email. ` + `Then call install_utility again with { slug: "${slug}", target_dir, session_id: "cs_..." }.` ); } if (res.status === 404) { throw new Error(`No utility with slug "${slug}". Use list_utilities or search_utilities to find valid slugs.`); } if (!res.ok) { let msg = `HTTP ${res.status}`; try { const body = await res.json(); if (body && body.error) msg = body.error; } catch { // body wasn't JSON, keep generic message } throw new Error(`Registry error: ${msg}`); } const payload = await res.json(); const { files = [], dependencies = [], message } = payload; // Resolve target_dir against cwd if relative. const resolvedDir = path.isAbsolute(target_dir) ? target_dir : path.resolve(process.cwd(), target_dir); const written = []; for (const file of files) { if (!file || typeof file.path !== 'string' || typeof file.content !== 'string') { continue; } // Join and ensure the final path stays inside resolvedDir (defence against "../"). const destPath = path.resolve(resolvedDir, file.path); const rel = path.relative(resolvedDir, destPath); if (rel.startsWith('..') || path.isAbsolute(rel)) { throw new Error(`Refusing to write outside target_dir: ${file.path}`); } fs.mkdirSync(path.dirname(destPath), { recursive: true }); fs.writeFileSync(destPath, file.content); written.push(destPath); } return { slug, target_dir: resolvedDir, files_written: written, dependencies, dependencies_install_hint: dependencies.length > 0 ? `Run one of: npm install ${dependencies.join(' ')} | yarn add ${dependencies.join(' ')} | pnpm add ${dependencies.join(' ')}` : 'No dependencies required.', message: message || 'Utility successfully written.', }; } - index.js:234-262 (schema)Input schema definition registered for install_utility. Defines required string parameters: slug, target_dir, and optional session_id (for paid utilities).
{ name: 'install_utility', description: 'Fetch a CustomClaw utility payload and write its files into target_dir. ' + 'For paid utilities, pass session_id (the Stripe checkout session_id from the buyer\'s receipt email). ' + 'Returns the list of files written and any npm dependencies the caller should install — ' + 'this tool does NOT run npm install itself; the host agent decides when to install deps.', inputSchema: { type: 'object', properties: { slug: { type: 'string', description: 'The utility slug to install.', }, target_dir: { type: 'string', description: 'Directory to write files into. Absolute path preferred; relative paths are resolved against the server process cwd.', }, session_id: { type: 'string', description: 'Optional. Stripe checkout session_id for paid utilities. Found as session_id=... in the CustomClaw receipt email download URL.', }, }, required: ['slug', 'target_dir'], additionalProperties: false, }, }, - index.js:234-263 (registration)The tool registration entry within the TOOLS array, including name, description, and inputSchema.
{ name: 'install_utility', description: 'Fetch a CustomClaw utility payload and write its files into target_dir. ' + 'For paid utilities, pass session_id (the Stripe checkout session_id from the buyer\'s receipt email). ' + 'Returns the list of files written and any npm dependencies the caller should install — ' + 'this tool does NOT run npm install itself; the host agent decides when to install deps.', inputSchema: { type: 'object', properties: { slug: { type: 'string', description: 'The utility slug to install.', }, target_dir: { type: 'string', description: 'Directory to write files into. Absolute path preferred; relative paths are resolved against the server process cwd.', }, session_id: { type: 'string', description: 'Optional. Stripe checkout session_id for paid utilities. Found as session_id=... in the CustomClaw receipt email download URL.', }, }, required: ['slug', 'target_dir'], additionalProperties: false, }, }, ]; - index.js:300-307 (handler)The case branch in the CallToolRequestSchema handler that dispatches install_utility calls to the installUtility function.
case 'install_utility': { const result = await installUtility({ slug: args.slug, target_dir: args.target_dir, session_id: args.session_id, }); return jsonResult(result); }