Compare commits

..

12 Commits

Author SHA1 Message Date
ywkj 5e5733f2e5 feat: auth-rt 改用 Go 二进制,install.sh 自动下载
register-skill-release / register (push) Successful in 14s Details
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 20:52:02 +08:00
ywkj c9d4e13814 fix: auth-rt auto-install 使用永久路径 ~/.local/share/auth-runtime
修复之前 clone 到 /tmp 后删除导致 wrapper 指向不存在路径的问题。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 20:34:21 +08:00
ywkj cbde7a9647 feat: install.sh 自动下载安装 auth-rt(无需手动 clone)
register-skill-release / register (push) Successful in 13s Details
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 18:36:46 +08:00
ywkj a811058901 chore: auth-rt 默认路径改为 ~/.local/bin/
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 18:35:37 +08:00
ywkj 1b1f3eb46c fix: auth-cli.ts 修复 HOME 路径解析 + shell wrapper 兼容
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 18:31:06 +08:00
ywkj 06fe7445ea refactor: auth-cli.ts 改用 auth-rt 二进制
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 18:26:28 +08:00
ywkj 9892adf566 refactor: 移除 @clawd/auth-runtime npm 依赖,改用 CLI subprocess 调用
通过内置 auth-cli.ts 薄 wrapper 调用 auth-runtime CLI,
消除 npm git 依赖带来的缓存和更新问题。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 14:38:03 +08:00
ywkj 5d42a64615 chore: add LLM behavior constraints to SKILL.md
Prevent the LLM from reasoning about internal query/polling logic,
attempting fallback strategies, or retrying on its own.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 07:40:51 +08:00
ywkj 81b932a97d chore: remove debug-push workflow
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 07:26:26 +08:00
ywkj 2e23d74701 fix: install.sh always fetches latest git deps before build
/ smoke (push) Successful in 6s Details
register-skill-release / register (push) Successful in 14s Details
rm -rf node_modules/@clawd before npm install ensures auth-runtime
is always at the latest remote version, not cached.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 06:56:29 +08:00
ywkj 850bc75046 chore: pin @clawd/auth-runtime to v1.1.0
/ smoke (push) Successful in 6s Details
Ensures npm/bun fetches the exact version with loadGlobalEnv fix.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 06:49:06 +08:00
ywkj 60c0265ac9 refactor: use SkillClient, remove all auth concerns from business logic
/ smoke (push) Successful in 5s Details
register-skill-release / register (push) Successful in 14s Details
Replaced manual config/token/retry handling with createSkillClient().
scrape.ts is now pure data transformation with zero auth imports.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 08:17:53 +08:00
10 changed files with 213 additions and 121 deletions

View File

@ -1,6 +0,0 @@
on: [push]
jobs:
smoke:
runs-on: docker
steps:
- run: echo forgejo-actions-ok

View File

@ -42,6 +42,13 @@ Returns structured JSON with product data:
- Variants/SKUs - Variants/SKUs
- Supplier info - Supplier info
## Rules — MUST follow
1. **Execute only, do not reason about internals.** Run the CLI command and return the output. Do NOT attempt to understand, debug, or bypass the scrape/query/polling logic inside the tool.
2. **No fallback strategies.** If the command fails, report the error as-is. Do NOT try alternative approaches such as browser scraping, direct HTTP calls, or different API endpoints.
3. **No retry loops.** If authentication or the scrape call fails, report the failure. Do NOT re-check configs, re-acquire tokens, or retry on your own.
4. **Trust the tool's output.** The CLI handles session management, retries, and error formatting internally. Your job is to invoke it and relay the result — nothing more.
## Reference ## Reference
See [references/1688-product-master.md](references/1688-product-master.md). See [references/1688-product-master.md](references/1688-product-master.md).

View File

@ -5,11 +5,17 @@
"": { "": {
"name": "1688-product-master", "name": "1688-product-master",
"dependencies": { "dependencies": {
"@clawd/auth-runtime": "git+http://192.168.0.108:3030/agent-skills/auth-runtime.git", "@clawd/auth-runtime": "file:../auth-runtime",
}, },
}, },
}, },
"packages": { "packages": {
"@clawd/auth-runtime": ["@clawd/auth-runtime@git+http://192.168.0.108:3030/agent-skills/auth-runtime.git#70cf86889eecbe9c4649bb072cd971c3a560e889", {}, "70cf86889eecbe9c4649bb072cd971c3a560e889"], "@clawd/auth-runtime": ["@clawd/auth-runtime@file:../auth-runtime", { "devDependencies": { "@types/node": "^25.3.3", "typescript": "^5.9.3" } }],
"@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
} }
} }

View File

@ -1,24 +1,34 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Install 1688-product-master to a target directory.
# Bundles the skill + auth-runtime into a single self-contained file.
#
# Usage:
# ./install.sh # installs to ~/.openclaw/skills/
# ./install.sh /custom/path/
set -euo pipefail set -euo pipefail
SKILL_NAME="1688-product-master"
DEST="${1:-$HOME/.openclaw/skills}"
cd "$(dirname "$0")" cd "$(dirname "$0")"
echo "Building $SKILL_NAME..." # Auto-install auth-rt if not found
bun install --frozen-lockfile if ! command -v auth-rt &>/dev/null && [ ! -x "$HOME/.local/bin/auth-rt" ]; then
echo "auth-rt not found, installing..."
_FORGEJO="http://192.168.0.108:3030"
_OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
_ARCH="$(uname -m)"; case "$_ARCH" in x86_64) _ARCH="amd64";; aarch64) _ARCH="arm64";; esac
_URL="$_FORGEJO/agent-skills/auth-runtime/releases/download/latest/auth-rt-${_OS}-${_ARCH}"
mkdir -p "$HOME/.local/bin"
if curl -fsSL "$_URL" -o "$HOME/.local/bin/auth-rt" 2>/dev/null; then
chmod +x "$HOME/.local/bin/auth-rt"
echo "auth-rt installed (downloaded)"
else
echo "Download failed, building from source..."
_SRC="$HOME/.local/share/auth-runtime"
if [ -d "$_SRC/.git" ]; then git -C "$_SRC" pull --ff-only
else git clone --depth 1 "$_FORGEJO/agent-skills/auth-runtime.git" "$_SRC"
fi
bash "$_SRC/install.sh"
fi
fi
echo "Building 1688-product-master..."
bun build scripts/run.ts --outfile dist/run.js --target bun bun build scripts/run.ts --outfile dist/run.js --target bun
DEST="${1:-$HOME/.openclaw/skills}"
SKILL_NAME="1688-product-master"
mkdir -p "$DEST/$SKILL_NAME" mkdir -p "$DEST/$SKILL_NAME"
cp dist/run.js "$DEST/$SKILL_NAME/run.js" cp dist/run.js "$DEST/$SKILL_NAME/run.js"
echo "Installed: $DEST/$SKILL_NAME/run.js" echo "Installed: $DEST/$SKILL_NAME/run.js"
echo "Run with: bun $DEST/$SKILL_NAME/run.js <command> [args...]"

20
package-lock.json generated Normal file
View File

@ -0,0 +1,20 @@
{
"name": "1688-product-master",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "1688-product-master",
"version": "1.0.0",
"dependencies": {
"@clawd/auth-runtime": "git+http://192.168.0.108:3030/agent-skills/auth-runtime.git"
}
},
"node_modules/@clawd/auth-runtime": {
"version": "1.0.0",
"resolved": "git+http://192.168.0.108:3030/agent-skills/auth-runtime.git#466a4303b247f99eb444a74b014c40b148974e02",
"license": "MIT"
}
}
}

View File

@ -7,7 +7,5 @@
"build": "bun build scripts/run.ts --outfile dist/run.js --target bun", "build": "bun build scripts/run.ts --outfile dist/run.js --target bun",
"package": "bun run build && cd .. && zip -r 1688-product-master.skill 1688-product-master/SKILL.md 1688-product-master/dist/run.js && echo 'Created: 1688-product-master.skill'" "package": "bun run build && cd .. && zip -r 1688-product-master.skill 1688-product-master/SKILL.md 1688-product-master/dist/run.js && echo 'Created: 1688-product-master.skill'"
}, },
"dependencies": { "dependencies": {}
"@clawd/auth-runtime": "git+http://192.168.0.108:3030/agent-skills/auth-runtime.git"
}
} }

119
src/auth-cli.ts Normal file
View File

@ -0,0 +1,119 @@
/**
* Thin CLI wrapper for auth-runtime.
*
* Copy this file into your skill's src/ directory. It calls the
* `auth-rt` binary (a standalone Go executable), so the skill has
* zero npm/runtime dependency on auth-runtime.
*
* Prerequisites:
* `auth-rt` must be in PATH or at ~/.local/bin/auth-rt
* (install.sh handles this automatically)
*
* 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 home = process.env.HOME || os.homedir();
const AUTH_RT_BIN = process.env.AUTH_RT_BIN
|| (() => {
// Check if auth-rt is in PATH
const which = spawnSync('which', ['auth-rt'], { encoding: 'utf-8' });
if (which.status === 0 && which.stdout.trim()) {
return which.stdout.trim();
}
return path.join(home, '.local', 'bin', 'auth-rt');
})();
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 {
const result = spawnSync(AUTH_RT_BIN, args, {
encoding: 'utf-8',
timeout: 60_000,
});
if (result.error) {
throw new Error(`auth-rt spawn failed: ${result.error.message}`);
}
if (result.status !== 0) {
throw new Error(`auth-rt 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);
}

View File

@ -1,98 +1,61 @@
import type { Command, OutputResult, ScrapePayload } from './types.js'; import type { Command, OutputResult, ScrapePayload } from './types.js';
import { createEnvConfig, getAccessToken, fetchSessionJson } from '@clawd/auth-runtime'; import { createSkillClient } from './auth-cli.ts';
import { buildPayloadFromUrl, validatePayloadJson, scrapeProduct } from './scrape.js'; import { buildPayloadFromUrl, validatePayloadJson } from './scrape.js';
export async function run1688( export async function run1688(
command: Command, command: Command,
args: string[], args: string[],
dryRun: boolean = false, dryRun: boolean = false,
): Promise<OutputResult> { ): Promise<OutputResult> {
const config = createEnvConfig(); let client;
const ecomBase = (process.env.ECOM_BASE || config.authBase).replace(/\/$/, ''); try {
client = createSkillClient({ dryRun });
if (!config.clientKey) { } catch (error) {
return failed(command, dryRun, 'missing required env: CLIENT_KEY'); return failed(command, dryRun, error instanceof Error ? error.message : String(error));
} }
switch (command) { switch (command) {
case 'session': case 'session': {
return runSession(command, dryRun, config); try {
case 'scrape-url': const session = await client.session();
return runScrapeUrl(command, dryRun, config, ecomBase, args); return { status: 'success', error: null, command, dryRun, session };
case 'scrape-payload': } catch (error) {
return runScrapePayload(command, dryRun, config, ecomBase, args); return failed(command, dryRun, error instanceof Error ? error.message : String(error));
}
}
case 'scrape-url': {
const url = args[0];
if (!url) return failed(command, dryRun, 'scrape-url requires <1688-url>');
const defaults = readDefaults();
const payload = buildPayloadFromUrl(url, args[1] || '', defaults);
return runScrape(client, command, dryRun, payload);
}
case 'scrape-payload': {
const rawPayload = args[0];
if (!rawPayload) return failed(command, dryRun, 'scrape-payload requires <payload-json>');
try {
const payload = validatePayloadJson(rawPayload);
return runScrape(client, command, dryRun, payload);
} catch (error) {
return failed(command, dryRun, error instanceof Error ? error.message : String(error));
}
}
default: default:
return failed(command, dryRun, `unknown command: ${command}`); return failed(command, dryRun, `unknown command: ${command}`);
} }
} }
async function runSession(
command: string,
dryRun: boolean,
config: ReturnType<typeof createEnvConfig>,
): Promise<OutputResult> {
const session = await fetchSessionJson(dryRun, config);
return { status: 'success', error: null, command, dryRun, session };
}
async function runScrapeUrl(
command: string,
dryRun: boolean,
config: ReturnType<typeof createEnvConfig>,
ecomBase: string,
args: string[],
): Promise<OutputResult> {
const url = args[0];
if (!url) {
return failed(command, dryRun, 'scrape-url requires <1688-url>');
}
const defaults = readDefaults();
const payload = buildPayloadFromUrl(url, args[1] || '', defaults);
return runScrape(command, dryRun, config, ecomBase, payload);
}
async function runScrapePayload(
command: string,
dryRun: boolean,
config: ReturnType<typeof createEnvConfig>,
ecomBase: string,
args: string[],
): Promise<OutputResult> {
const rawPayload = args[0];
if (!rawPayload) {
return failed(command, dryRun, 'scrape-payload requires <payload-json>');
}
let payload: ScrapePayload;
try {
payload = validatePayloadJson(rawPayload);
} catch (error) {
return failed(command, dryRun, error instanceof Error ? error.message : String(error));
}
return runScrape(command, dryRun, config, ecomBase, payload);
}
async function runScrape( async function runScrape(
client: ReturnType<typeof createSkillClient>,
command: string, command: string,
dryRun: boolean, dryRun: boolean,
config: ReturnType<typeof createEnvConfig>,
ecomBase: string,
payload: ScrapePayload, payload: ScrapePayload,
): Promise<OutputResult> { ): Promise<OutputResult> {
if (dryRun) { if (dryRun) {
return { status: 'success', error: null, command, dryRun, requestPayload: payload, scrapeHttpStatus: 0, scrapeBody: null }; return { status: 'success', error: null, command, dryRun, requestPayload: payload, scrapeHttpStatus: 0, scrapeBody: null };
} }
let accessToken: string; const result = await client.post('/ecom/tasks/scrape', payload);
try {
accessToken = await getAccessToken(dryRun, config);
} catch (error) {
return failed(command, dryRun, error instanceof Error ? error.message : 'failed to get access token');
}
const result = await scrapeProduct(config, ecomBase, payload, dryRun, accessToken);
if (result.status < 200 || result.status >= 300) { if (result.status < 200 || result.status >= 300) {
return failed(command, dryRun, `scrape failed: HTTP ${result.status}: ${result.body}`, payload, result.status); return failed(command, dryRun, `scrape failed: HTTP ${result.status}: ${result.body}`, payload, result.status);

View File

@ -1,6 +1,4 @@
import type { ScrapePayload } from './types.js'; import type { ScrapePayload } from './types.js';
import { requestApiWithAutoRefresh } from '@clawd/auth-runtime';
import type { ApiResponse, EnvConfig } from '@clawd/auth-runtime';
type Defaults = { type Defaults = {
optimizeImages: boolean; optimizeImages: boolean;
@ -59,23 +57,6 @@ export function validatePayloadJson(raw: string): ScrapePayload {
}; };
} }
export async function scrapeProduct(
config: EnvConfig,
ecomBase: string,
payload: ScrapePayload,
dryRun: boolean,
accessToken?: string,
): Promise<ApiResponse> {
return requestApiWithAutoRefresh(
'POST',
`${ecomBase}/ecom/tasks/scrape`,
dryRun,
config,
JSON.stringify(payload),
accessToken,
);
}
function parseBoolean(value: unknown): boolean { function parseBoolean(value: unknown): boolean {
const str = String(value).trim().toLowerCase(); const str = String(value).trim().toLowerCase();
return ['1', 'true', 'yes', 'y'].includes(str); return ['1', 'true', 'yes', 'y'].includes(str);

View File

@ -6,12 +6,6 @@ export interface ScrapePayload {
needTranslate: boolean; needTranslate: boolean;
} }
export interface ScrapeResponse {
[key: string]: unknown;
}
export type ApiResponse = import("@clawd/auth-runtime").ApiResponse;
export type Command = "session" | "scrape-url" | "scrape-payload"; export type Command = "session" | "scrape-url" | "scrape-payload";
export interface OutputResult { export interface OutputResult {