From d5c9ffa542a120aca7270a1a7639017389231c51 Mon Sep 17 00:00:00 2001 From: ywkj Date: Fri, 20 Mar 2026 14:44:09 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E7=A7=BB=E9=99=A4=20@clawd/auth-ru?= =?UTF-8?q?ntime=20npm=20=E4=BE=9D=E8=B5=96=EF=BC=8C=E6=94=B9=E7=94=A8=20a?= =?UTF-8?q?uth-cli.ts=20CLI=20wrapper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 src/auth-cli.ts:通过 subprocess 调用 auth-runtime CLI - 新增 install.sh:检查 auth-runtime 目录 - 新增 README.md:文档化 auth-cli.ts 认证机制和新建 skill 检查清单 - package.json 移除 @clawd/auth-runtime 依赖 - src/index.ts 改用 createSkillClient API Co-Authored-By: Claude Opus 4.6 --- README.md | 61 ++++++++++++++++++++++++++ SKILL.md | 2 +- install.sh | 12 ++++++ package.json | 4 +- scripts/run.ts | 2 +- src/auth-cli.ts | 111 ++++++++++++++++++++++++++++++++++++++++++++++++ src/index.ts | 20 +++------ 7 files changed, 193 insertions(+), 19 deletions(-) create mode 100644 README.md create mode 100755 install.sh create mode 100644 src/auth-cli.ts diff --git a/README.md b/README.md new file mode 100644 index 0000000..bd97428 --- /dev/null +++ b/README.md @@ -0,0 +1,61 @@ +# template-skill + +新 skill 的基础模版。 + +## 认证机制:auth-cli.ts + +每个 skill 内置一份 `src/auth-cli.ts`,它是一个薄 wrapper,通过 subprocess 调用 `auth-runtime` CLI(`bun run ~/clawd/skills/auth-runtime/src/cli.ts`)。 + +**不使用 npm 依赖**,避免了 git 包缓存导致的版本不一致问题。 + +### 工作原理 + +``` +skill/src/index.ts + → import { createSkillClient } from './auth-cli.ts' + → auth-cli.ts 通过 spawnSync 调用 bun run cli.ts + → auth-runtime/src/cli.ts 处理 token/session/request +``` + +### 使用方式 + +```typescript +import { createSkillClient } from './auth-cli.ts'; + +const client = createSkillClient({ + apiBase: process.env.API_BASE, // 可选 + dryRun: false, // 可选,dry-run 模式返回模拟数据 +}); + +// API 调用 +const res = await client.post('/ecom/your/endpoint', { param: 'value' }); +// res = { status: 200, body: '...' } + +// 获取 session +const session = await client.session(); +// session = { accessToken: '...', expiresIn: 900 } +``` + +### 前置条件 + +每台运行 skill 的机器上必须有 `auth-runtime`: + +```bash +git clone http://192.168.0.108:3030/agent-skills/auth-runtime.git ~/clawd/skills/auth-runtime +``` + +`install.sh` 会检查此目录是否存在。 + +### 路径解析 + +默认路径:`~/clawd/skills/auth-runtime` + +可通过环境变量覆盖:`AUTH_RUNTIME_DIR=/custom/path bun run scripts/run.ts ...` + +### 新建 skill 检查清单 + +1. 从此模版创建仓库 +2. 确认 `src/auth-cli.ts` 已包含(直接从模版继承) +3. `src/index.ts` 中 `import { createSkillClient } from './auth-cli.ts'` +4. `package.json` 中 **不要** 添加 `@clawd/auth-runtime` 依赖 +5. `install.sh` 中包含 auth-runtime 目录检查 diff --git a/SKILL.md b/SKILL.md index e895188..db0dee6 100644 --- a/SKILL.md +++ b/SKILL.md @@ -7,7 +7,7 @@ description: "TODO: describe what this skill does and when to use it." TODO: one-line description. -> Auth (CLIENT_KEY) is loaded automatically from `~/.openclaw/.env`. +> Auth is handled automatically via `auth-cli.ts` → `auth-runtime` CLI. ## Run diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..c324683 --- /dev/null +++ b/install.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -euo pipefail +cd "$(dirname "$0")" + +# Prerequisite: auth-runtime must be cloned at ~/clawd/skills/auth-runtime +if [ ! -d "$HOME/clawd/skills/auth-runtime/src" ]; then + echo "ERROR: auth-runtime not found at ~/clawd/skills/auth-runtime" + echo "Run: git clone http://192.168.0.108:3030/agent-skills/auth-runtime.git ~/clawd/skills/auth-runtime" + exit 1 +fi + +npm install diff --git a/package.json b/package.json index 5fd4e73..d1deedc 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,5 @@ "run": "bun run scripts/run.ts", "build": "bun build scripts/run.ts --outfile dist/run.js --target bun" }, - "dependencies": { - "@clawd/auth-runtime": "git+http://192.168.0.108:3030/agent-skills/auth-runtime.git" - } + "dependencies": {} } diff --git a/scripts/run.ts b/scripts/run.ts index f2dfab8..378f6b2 100644 --- a/scripts/run.ts +++ b/scripts/run.ts @@ -9,7 +9,7 @@ function printUsage(): void { Commands: run -Config: ~/.openclaw/.env (CLIENT_KEY, API_BASE) +Config: ~/.openclaw/.env (API_BASE) `); } diff --git a/src/auth-cli.ts b/src/auth-cli.ts new file mode 100644 index 0000000..77c09a9 --- /dev/null +++ b/src/auth-cli.ts @@ -0,0 +1,111 @@ +/** + * 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); +} diff --git a/src/index.ts b/src/index.ts index 086be5f..56effac 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,4 @@ -import { - createEnvConfig, - requestApiWithAutoRefresh, - type ApiResponse, -} from '@clawd/auth-runtime'; +import { createSkillClient, type ApiResponse } from './auth-cli.ts'; export type Command = 'run'; // TODO: add your commands @@ -19,17 +15,13 @@ export async function run( args: string[], dryRun: boolean, ): Promise { - const config = createEnvConfig(); - const apiBase = (process.env.API_BASE ?? 'https://api-gw-test.yuanwei-lnc.com').replace(/\/$/, ''); + const client = createSkillClient({ + apiBase: process.env.API_BASE, + dryRun, + }); if (command === 'run') { - const response: ApiResponse = await requestApiWithAutoRefresh( - 'POST', - `${apiBase}/your/endpoint`, - dryRun, - config, - JSON.stringify({ param: args[0] }), - ); + const response: ApiResponse = await client.post('/your/endpoint', { param: args[0] }); if (response.status < 200 || response.status >= 300) { return { status: 'failed', command, dryRun, error: `HTTP ${response.status}: ${response.body}` };