client-finder/src/auth-cli.ts

112 lines
3.1 KiB
TypeScript
Raw Normal View History

/**
* Thin CLI wrapper for auth-runtime.
*
* Copy this file into your skill's src/ directory. It spawns
* `bun run <auth-runtime>/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<SessionResponse> {
if (this.dryRun) {
return { accessToken: '<dry-run-token>', expiresIn: 900 };
}
return JSON.parse(runCli('session'));
}
async get(urlPath: string): Promise<ApiResponse> {
return this.request('GET', urlPath);
}
async post(urlPath: string, body?: unknown): Promise<ApiResponse> {
return this.request('POST', urlPath, body);
}
async put(urlPath: string, body?: unknown): Promise<ApiResponse> {
return this.request('PUT', urlPath, body);
}
async patch(urlPath: string, body?: unknown): Promise<ApiResponse> {
return this.request('PATCH', urlPath, body);
}
async delete(urlPath: string, body?: unknown): Promise<ApiResponse> {
return this.request('DELETE', urlPath, body);
}
private async request(method: string, urlPath: string, body?: unknown): Promise<ApiResponse> {
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);
}