Skill: wangshengli0421/aiznt-xhs (version 1.0.0)

--- name: aiznt-xhs description: > 小红书图文排版生成，JSON 入参。Use when: 用户要生成小红书风格多页图文、排版。 metadata: openclaw: primaryEnv: TS_TOKEN requires: env: - AIZNT_PROXY_URLS --- # 小红书图文 (aiznt-xhs) 服务端将 JSON 转为 form-urlencoded 调上游。 ```bash node scripts/xhs.js --body '{"user_text":"标题与正文","page_count":3}' node scripts/xhs.js ``` URL 键：`xhs_generate`。配置见 TsClaw「同步天树凭证」。

path: clawhub.json
content: {
  "name": "XHS Layout",
  "tagline": "小红书图文排版",
  "description": "Xiaohongshu multi-page layout.",
  "category": "media",
  "tags": ["xhs", "layout", "tianshu"],
  "version": "1.0.0",
  "license": "MIT",
  "pricing": "free"
}


path: package.json
content: { "name": "aiznt-xhs-skill", "private": true, "dependencies": {} }


path: scripts/client.js
content: /** 从环境变量读取 TS_TOKEN 与 AIZNT_PROXY_URLS */
function loadClient() {
  const [REDACTED_SECRET] || '').trim();
  if (!token) {
    throw new Error('缺少 TS_TOKEN（天树对话凭证 ts_xxx，对应 skills.entries 的 apiKey）');
  }
  const raw = process.env.AIZNT_PROXY_URLS;
  if (!raw || !String(raw).trim()) {
    throw new Error('缺少 AIZNT_PROXY_URLS（JSON 字符串，与 GET /miniapp/ai/chat/credentials 返回的 aiznt_proxy_urls 一致）');
  }
  let urls;
  try {
    urls = typeof raw === 'string' ? JSON.parse(raw) : raw;
  } catch {
    throw new Error('AIZNT_PROXY_URLS 不是合法 JSON');
  }
  if (!urls || typeof urls !== 'object') {
    throw new Error('AIZNT_PROXY_URLS 必须是对象');
  }
  return { token, urls };
}

function expandUrl(template, replacements = {}) {
  if (!template || typeof template !== 'string') {
    throw new Error('URL 模板无效');
  }
  let u = template;
  for (const [k, v] of Object.entries(replacements)) {
    u = u.split(`{${k}}`).join(String(v));
  }
  if (u.includes('{')) {
    throw new Error(`URL 仍有未替换占位符: ${u}`);
  }
  return u;
}

function authHeaders(token, extra = {}) {
  return {
    Authorization: `Bearer ${token}`,
    ...extra,
  };
}

async function fetchJson(url, options = {}) {
  const res = await fetch(url, options);
  const text = await res.text();
  let data;
  try {
    data = text ? JSON.parse(text) : {};
  } catch {
    throw new Error(`非 JSON 响应 (${res.status}): ${text.slice(0, 200)}`);
  }
  if (!res.ok) {
    const msg = data?.message || data?.error?.message || text.slice(0, 200);
    throw new Error(`HTTP ${res.status}: ${msg}`);
  }
  if (data.code !== undefined && data.code !== 0) {
    throw new Error(data.message || `业务错误 code=${data.code}`);
  }
  return data.data !== undefined ? data.data : data;
}

module.exports = {
  loadClient,
  expandUrl,
  authHeaders,
  fetchJson,
};


path: scripts/xhs.js
content: #!/usr/bin/env node
const { loadClient, authHeaders, fetchJson } = require('./client.js');

function parseArgs(argv) {
  const args = argv.slice(2);
  const rest = args;
  const opts = { _: [] };
  for (let i = 0; i < rest.length; i++) {
    const a = rest[i];
    if (a.startsWith('--')) {
      const key = a.slice(2);
      const next = rest[i + 1];
      if (next !== undefined && !next.startsWith('--')) {
        opts[key] = next;
        i++;
      } else {
        opts[key] = true;
      }
    } else {
      opts._.push(a);
    }
  }
  return opts;
}

function readJson(val, label) {
  if (!val) return undefined;
  try {
    return JSON.parse(val);
  } catch {
    throw new Error(`${label} 须为合法 JSON 字符串`);
  }
}

async function main() {
  const opts = parseArgs(process.argv);
  if (opts.help || opts.h) {
    console.log(`用法: node xhs.js [--body '<json>'] [--body-file <path>]
无参数或空对象表示可选字段均为空。`);
    process.exit(0);
  }

  const { token, urls } = loadClient();
  const url = urls.xhs_generate;
  if (!url) throw new Error('AIZNT_PROXY_URLS 缺少 xhs_generate');

  let body = {};
  if (opts['body-file']) {
    const fs = require('fs');
    body = JSON.parse(fs.readFileSync(opts['body-file'], 'utf8'));
  } else if (opts.body) {
    body = readJson(opts.body, '--body');
  }

  const data = await fetchJson(url, {
    method: 'POST',
    headers: authHeaders(token, { 'Content-Type': 'application/json' }),
    body: JSON.stringify(body),
  });
  console.log(JSON.stringify(data, null, 2));
}

main().catch((e) => {
  console.error('Error:', e.message);
  process.exit(1);
});


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)