Skill: wangshengli0421/aiznt-images (version 1.0.0)

--- name: aiznt-images description: > 通用图生图（OpenAI 风格）与 Gemini generateContent 文生图。Use when: 文生图、异步出图、Gemini 图像模型。 metadata: openclaw: primaryEnv: TS_TOKEN requires: env: - AIZNT_PROXY_URLS --- # 图生图 (aiznt-images) ## 命令 ```bash node scripts/images.js sync --body '{"prompt":"...","model":"..."}' node scripts/images.js async --body '{...}' node scripts/images.js async-fetch --task-id <id> node scripts/images.js generate-content --model gemini-3-pro-image-preview --body '{...}' ``` ## URL 键 - `v1_images_generations` - `v1_images_generations_async` - [REDACTED_SECRET]（`{task_id}`） - `v1_models_generate_content`（`{model}`） 配置同其它 aiznt-*：TsClaw「同步天树凭证」批量写入。

path: clawhub.json
content: {
  "name": "TS Images",
  "tagline": "通用图 + Gemini 文生图",
  "description": "OpenAI-style images API and Gemini generateContent.",
  "category": "media",
  "tags": ["image", "gemini", "tianshu"],
  "version": "1.0.0",
  "license": "MIT",
  "pricing": "free"
}


path: package.json
content: { "name": "aiznt-images-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/images.js
content: #!/usr/bin/env node
const { loadClient, expandUrl, authHeaders, fetchJson } = require('./client.js');

function parseArgs(argv) {
  const args = argv.slice(2);
  const cmd = args[0];
  const rest = args.slice(1);
  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 { cmd, opts };
}

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

async function main() {
  const { cmd, opts } = parseArgs(process.argv);
  if (!cmd || cmd === 'help' || cmd === '-h') {
    console.log(`用法:
  sync --body '<json>' | --body-file <path>     # 通用图同步 v1/images/generations
  async --body '<json>'                         # 异步提交
  async-fetch --task-id <id>                    # 异步查询
  generate-content --model <id> --body '<json>' # Gemini generateContent`);
    process.exit(0);
  }

  const { token, urls } = loadClient();
  const bodyFromOpts = () => {
    if (opts['body-file']) {
      const fs = require('fs');
      return JSON.parse(fs.readFileSync(opts['body-file'], 'utf8'));
    }
    if (opts.body) return readJson(opts.body, '--body');
    throw new Error('需要 --body 或 --body-file');
  };

  if (cmd === 'sync') {
    const url = urls.v1_images_generations;
    if (!url) throw new Error('AIZNT_PROXY_URLS 缺少 v1_images_generations');
    const body = bodyFromOpts();
    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));
    return;
  }
  if (cmd === 'async') {
    const url = urls.v1_images_generations_async;
    if (!url) throw new Error('AIZNT_PROXY_URLS 缺少 v1_images_generations_async');
    const body = bodyFromOpts();
    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));
    return;
  }
  if (cmd === 'async-fetch') {
    const taskId = opts['task-id'];
    if (!taskId) throw new Error('需要 --task-id');
    const tpl = urls.v1_images_generations_async_fetch;
    if (!tpl) throw new Error('AIZNT_PROXY_URLS 缺少 v1_images_generations_async_fetch');
    const url = expandUrl(tpl, { task_id: taskId });
    const data = await fetchJson(url, { headers: authHeaders(token) });
    console.log(JSON.stringify(data, null, 2));
    return;
  }
  if (cmd === 'generate-content') {
    const model = opts.model;
    if (!model) throw new Error('需要 --model');
    const tpl = urls.v1_models_generate_content;
    if (!tpl) throw new Error('AIZNT_PROXY_URLS 缺少 v1_models_generate_content');
    const url = expandUrl(tpl, { model });
    const body = bodyFromOpts();
    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));
    return;
  }
  throw new Error(`未知命令: ${cmd}`);
}

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


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