/** * Thin CLI wrapper for auth-runtime. * * Copy this file into your skill's src/ directory. It spawns * `bun run /src/cli.ts` as a subprocess, so the * skill has zero npm dependency on @clawd/auth-runtime. * * Usage: * import { createSkillClient } from './auth-cli.ts'; * const client = createSkillClient(); * const res = await client.post('/ecom/tasks/scrape', { url: '...' }); */ import { spawnSync } from 'child_process'; import * as path from 'path'; import * as os from 'os'; const AUTH_RUNTIME_DIR = process.env.AUTH_RUNTIME_DIR || path.join(os.homedir(), 'clawd', 'skills', 'auth-runtime'); const CLI_ENTRY = path.join(AUTH_RUNTIME_DIR, 'src', 'cli.ts'); export interface ApiResponse { status: number; body: string; } export interface SessionResponse { accessToken: string; expiresIn: number; ownerSessionToken?: string; hookUrl?: string; hookToken?: string; } export interface SkillClientOptions { apiBase?: string; dryRun?: boolean; } function runCli(...args: string[]): string { // Use the same bun binary that's running this process const bun = process.execPath; const result = spawnSync(bun, ['run', CLI_ENTRY, ...args], { cwd: AUTH_RUNTIME_DIR, encoding: 'utf-8', timeout: 60_000, }); if (result.error) { throw new Error(`auth-cli spawn failed: ${result.error.message}`); } if (result.status !== 0) { throw new Error(`auth-cli failed (exit ${result.status}): ${(result.stderr || '').trim()}`); } return (result.stdout || '').trim(); } export class SkillClient { private readonly apiBase?: string; private readonly dryRun: boolean; constructor(options: SkillClientOptions = {}) { this.apiBase = options.apiBase; this.dryRun = options.dryRun ?? false; } async session(): Promise { if (this.dryRun) { return { accessToken: '', expiresIn: 900 }; } return JSON.parse(runCli('session')); } async get(urlPath: string): Promise { return this.request('GET', urlPath); } async post(urlPath: string, body?: unknown): Promise { return this.request('POST', urlPath, body); } async put(urlPath: string, body?: unknown): Promise { return this.request('PUT', urlPath, body); } async patch(urlPath: string, body?: unknown): Promise { return this.request('PATCH', urlPath, body); } async delete(urlPath: string, body?: unknown): Promise { return this.request('DELETE', urlPath, body); } private async request(method: string, urlPath: string, body?: unknown): Promise { if (this.dryRun) { return { status: 200, body: JSON.stringify({ dryRun: true, method, path: urlPath }) }; } const args = ['request', method, urlPath]; if (body != null) { args.push('--body', JSON.stringify(body)); } if (this.apiBase) { args.push('--api-base', this.apiBase); } return JSON.parse(runCli(...args)); } } export function createSkillClient(options?: SkillClientOptions): SkillClient { return new SkillClient(options); }