refactor: 移除 @clawd/auth-runtime npm 依赖,改用 auth-cli.ts CLI wrapper
- 新增 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 <noreply@anthropic.com>
This commit is contained in:
parent
2c2c24a310
commit
d5c9ffa542
|
|
@ -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 目录检查
|
||||||
2
SKILL.md
2
SKILL.md
|
|
@ -7,7 +7,7 @@ description: "TODO: describe what this skill does and when to use it."
|
||||||
|
|
||||||
TODO: one-line description.
|
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
|
## Run
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -6,7 +6,5 @@
|
||||||
"run": "bun run scripts/run.ts",
|
"run": "bun run scripts/run.ts",
|
||||||
"build": "bun build scripts/run.ts --outfile dist/run.js --target bun"
|
"build": "bun build scripts/run.ts --outfile dist/run.js --target bun"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {}
|
||||||
"@clawd/auth-runtime": "git+http://192.168.0.108:3030/agent-skills/auth-runtime.git"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ function printUsage(): void {
|
||||||
Commands:
|
Commands:
|
||||||
run <arg>
|
run <arg>
|
||||||
|
|
||||||
Config: ~/.openclaw/.env (CLIENT_KEY, API_BASE)
|
Config: ~/.openclaw/.env (API_BASE)
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
20
src/index.ts
20
src/index.ts
|
|
@ -1,8 +1,4 @@
|
||||||
import {
|
import { createSkillClient, type ApiResponse } from './auth-cli.ts';
|
||||||
createEnvConfig,
|
|
||||||
requestApiWithAutoRefresh,
|
|
||||||
type ApiResponse,
|
|
||||||
} from '@clawd/auth-runtime';
|
|
||||||
|
|
||||||
export type Command = 'run'; // TODO: add your commands
|
export type Command = 'run'; // TODO: add your commands
|
||||||
|
|
||||||
|
|
@ -19,17 +15,13 @@ export async function run(
|
||||||
args: string[],
|
args: string[],
|
||||||
dryRun: boolean,
|
dryRun: boolean,
|
||||||
): Promise<RunResult> {
|
): Promise<RunResult> {
|
||||||
const config = createEnvConfig();
|
const client = createSkillClient({
|
||||||
const apiBase = (process.env.API_BASE ?? 'https://api-gw-test.yuanwei-lnc.com').replace(/\/$/, '');
|
apiBase: process.env.API_BASE,
|
||||||
|
dryRun,
|
||||||
|
});
|
||||||
|
|
||||||
if (command === 'run') {
|
if (command === 'run') {
|
||||||
const response: ApiResponse = await requestApiWithAutoRefresh(
|
const response: ApiResponse = await client.post('/your/endpoint', { param: args[0] });
|
||||||
'POST',
|
|
||||||
`${apiBase}/your/endpoint`,
|
|
||||||
dryRun,
|
|
||||||
config,
|
|
||||||
JSON.stringify({ param: args[0] }),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.status < 200 || response.status >= 300) {
|
if (response.status < 200 || response.status >= 300) {
|
||||||
return { status: 'failed', command, dryRun, error: `HTTP ${response.status}: ${response.body}` };
|
return { status: 'failed', command, dryRun, error: `HTTP ${response.status}: ${response.body}` };
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue