Compare commits
21 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
5e5733f2e5 | |
|
|
c9d4e13814 | |
|
|
cbde7a9647 | |
|
|
a811058901 | |
|
|
1b1f3eb46c | |
|
|
06fe7445ea | |
|
|
9892adf566 | |
|
|
5d42a64615 | |
|
|
81b932a97d | |
|
|
2e23d74701 | |
|
|
850bc75046 | |
|
|
60c0265ac9 | |
|
|
f3483033db | |
|
|
9203a55ee0 | |
|
|
46f61d6660 | |
|
|
423ae7cde9 | |
|
|
d6ce01e6a7 | |
|
|
dab9b862ba | |
|
|
f26d449e16 | |
|
|
b568fe262a | |
|
|
6c224584b4 |
|
|
@ -1,125 +1,18 @@
|
||||||
name: register-skill-release
|
name: register-skill-release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
push:
|
||||||
types: [published]
|
tags:
|
||||||
|
- 'v*'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
|
||||||
skill_slug:
|
|
||||||
description: Skill slug override (optional)
|
|
||||||
required: false
|
|
||||||
skill_subpath:
|
|
||||||
description: Skill folder path override (optional)
|
|
||||||
required: false
|
|
||||||
skill_doc_path:
|
|
||||||
description: Skill doc path override
|
|
||||||
required: false
|
|
||||||
default: SKILL.md
|
|
||||||
skill_version:
|
|
||||||
description: Version override (default tag name)
|
|
||||||
required: false
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
register-skill-version:
|
register:
|
||||||
runs-on: ubuntu-latest
|
runs-on: docker
|
||||||
env:
|
|
||||||
API_BASE: ${{ vars.API_BASE || secrets.API_BASE }}
|
|
||||||
CLIENT_KEY: ${{ secrets.CLIENT_KEY }}
|
|
||||||
SKILL_VERSION: ${{ github.event.inputs.skill_version || github.ref_name }}
|
|
||||||
SKILL_SUBPATH: ${{ github.event.inputs.skill_subpath || vars.SKILL_SUBPATH || secrets.SKILL_SUBPATH }}
|
|
||||||
SKILL_DOC_PATH: ${{ github.event.inputs.skill_doc_path || vars.SKILL_DOC_PATH || secrets.SKILL_DOC_PATH || 'SKILL.md' }}
|
|
||||||
SKILL_SLUG: ${{ github.event.inputs.skill_slug || vars.SKILL_SLUG || secrets.SKILL_SLUG }}
|
|
||||||
RELEASE_NOTE: ${{ github.event.release.body }}
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Load skill doc content
|
- uses: http://192.168.0.108:3030/agent-skills/shared-actions/register-skill@main
|
||||||
shell: bash
|
with:
|
||||||
run: |
|
client_key: ${{ secrets.CLIENT_KEY }}
|
||||||
set -euo pipefail
|
|
||||||
DOC_ABS_PATH="${SKILL_SUBPATH:+$SKILL_SUBPATH/}${SKILL_DOC_PATH}"
|
|
||||||
if [ ! -f "$DOC_ABS_PATH" ]; then
|
|
||||||
if [ -f "${SKILL_SUBPATH:+$SKILL_SUBPATH/}README.md" ]; then
|
|
||||||
DOC_ABS_PATH="${SKILL_SUBPATH:+$SKILL_SUBPATH/}README.md"
|
|
||||||
export SKILL_DOC_PATH="README.md"
|
|
||||||
else
|
|
||||||
echo "skill doc not found: $DOC_ABS_PATH"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
jq -Rs . < "$DOC_ABS_PATH" > /tmp/skill_doc.json
|
|
||||||
|
|
||||||
- name: Register version to business system
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
if [ -z "${API_BASE:-}" ]; then
|
|
||||||
echo "API_BASE is required (global/repo var or secret)."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [ -z "${CLIENT_KEY:-}" ]; then
|
|
||||||
echo "CLIENT_KEY is required (secret)."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
SKILL_BASE_DIR="${SKILL_SUBPATH:-.}"
|
|
||||||
|
|
||||||
if [ -z "${SKILL_SLUG:-}" ]; then
|
|
||||||
if [ -f "${SKILL_BASE_DIR}/package.json" ]; then
|
|
||||||
PKG_NAME=$(jq -r '.name // empty' "${SKILL_BASE_DIR}/package.json")
|
|
||||||
if [ -n "$PKG_NAME" ]; then
|
|
||||||
# Strip npm scope: @scope/skill-name -> skill-name
|
|
||||||
SKILL_SLUG="${PKG_NAME##*/}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "${SKILL_SLUG:-}" ]; then
|
|
||||||
if [ -f "${SKILL_BASE_DIR}/pyproject.toml" ]; then
|
|
||||||
PYPROJECT_NAME=$(python3 -c "import sys,tomllib; p=sys.argv[1]; d=tomllib.load(open(p,'rb')); print((d.get('project',{}).get('name') or d.get('tool',{}).get('poetry',{}).get('name') or ''))" "${SKILL_BASE_DIR}/pyproject.toml" 2>/dev/null || true)
|
|
||||||
if [ -n "$PYPROJECT_NAME" ]; then
|
|
||||||
SKILL_SLUG="${PYPROJECT_NAME##*/}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "${SKILL_SLUG:-}" ]; then
|
|
||||||
SKILL_SLUG="${GITHUB_REPOSITORY##*/}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
SESSION_RES=$(curl -sS -X POST "${API_BASE}/auth/skill-credit/session" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d "{\"clientKey\":\"${CLIENT_KEY}\"}")
|
|
||||||
ACCESS_TOKEN=$(printf '%s' "$SESSION_RES" | jq -r '.accessToken // empty')
|
|
||||||
if [ -z "$ACCESS_TOKEN" ]; then
|
|
||||||
echo "failed to exchange access token from client key"
|
|
||||||
echo "$SESSION_RES"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
RUNTIME_META=$(jq -nc --arg entry "${SKILL_SUBPATH:+$SKILL_SUBPATH/}scripts" '{entry_hint:$entry, provider:"forgejo"}')
|
|
||||||
|
|
||||||
cat > /tmp/register_payload.json <<JSON
|
|
||||||
{
|
|
||||||
"skill_slug": "${SKILL_SLUG}",
|
|
||||||
"version": "${SKILL_VERSION}",
|
|
||||||
"release_note": $(printf '%s' "${RELEASE_NOTE:-}" | jq -Rs .),
|
|
||||||
"source_type": "git_ci",
|
|
||||||
"repo_url": "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git",
|
|
||||||
"repo_subpath": "${SKILL_SUBPATH:-}",
|
|
||||||
"git_ref": "${GITHUB_REF_NAME}",
|
|
||||||
"commit_sha": "${GITHUB_SHA}",
|
|
||||||
"skill_doc_path": "${SKILL_DOC_PATH}",
|
|
||||||
"skill_doc_content": $(cat /tmp/skill_doc.json),
|
|
||||||
"runtime_meta": ${RUNTIME_META}
|
|
||||||
}
|
|
||||||
JSON
|
|
||||||
|
|
||||||
curl -sS -X POST "${API_BASE}/ecom/skills/register-by-slug" \
|
|
||||||
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d @/tmp/register_payload.json
|
|
||||||
|
|
|
||||||
7
SKILL.md
7
SKILL.md
|
|
@ -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).
|
||||||
|
|
|
||||||
10
bun.lock
10
bun.lock
|
|
@ -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=="],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
38
install.sh
38
install.sh
|
|
@ -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...]"
|
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
105
src/index.ts
105
src/index.ts
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue