Skill flagged — suspicious patterns detected

ClawHub Security flagged this skill as suspicious. Review the scan results before using.

腾讯云SMH

v1.0.0

Cloud file upload and backup tool. Upload local files to Tencent SMH cloud storage, generate download links and image previews. Use cases: - User says "上传文件"...

0· 8·0 current·0 all-time
byShawnmZhang@shawnminh
MIT-0
Download zip
LicenseMIT-0 · Free to use, modify, and redistribute. No attribution required.
Security Scan
VirusTotalVirusTotal
Suspicious
View report →
OpenClawOpenClaw
Suspicious
medium confidence
!
Purpose & Capability
The skill claims to be a Tencent SMH cloud upload/backup tool, which reasonably requires a Node runtime and an SMH SDK. However, the registry metadata declares no required environment variables or config paths while the SKILL.md instructs the agent to read/write ~/.openclaw/openclaw.json, .env files, and to accept admin-level tokens or library secrets. The undeclared need for sensitive credentials and config access is an incoherence.
!
Instruction Scope
The SKILL.md tells the agent to discover local file paths, write a script to /tmp/smh-upload.js, run node to upload files, and read credentials from ~/.openclaw/openclaw.json or a .env file (or accept credentials passed in command JSON). Reading user config files and arbitrary local file paths is in scope for an uploader, but referencing the user's OpenClaw config file (not declared in metadata) and recommending storage of high-privilege tokens there expands the scope and should be explicit in the registry.
!
Install Mechanism
This is an instruction-only skill (no install spec), but SKILL.md instructs running npm install -g smh-node-sdk or installing into /tmp. That downloads code from the npm registry at runtime and places script(s) in /tmp for execution. Runtime npm installs from an external registry are moderate risk and should be declared; the registry metadata did not include an install step or the SDK dependency.
!
Credentials
The instructions request highly privileged secrets (a space_admin accessToken or a librarySecret used to mint tokens). None of these sensitive environment variables or config paths are listed in the skill's declared requirements. Requesting admin tokens is proportionate to uploading on behalf of the user only if clearly declared and when least-privilege tokens are recommended—here the guidance explicitly suggests space_admin tokens, which is potentially excessive.
Persistence & Privilege
The skill is not force-included (always:false) and does not request persistent global privileges. It writes transient files to /tmp and reads the user's config, but it does not request to modify other skills or system-wide settings—this is within reasonable bounds for an upload helper.
What to consider before installing
This skill appears to implement a file uploader, but there are important mismatches and risks to consider before installing: - Credentials: The SKILL.md expects either a space_admin accessToken or a librarySecret (used to mint tokens). These are high-privilege secrets but the registry metadata lists no required env vars. Do not supply admin tokens without understanding scope—prefer short-lived, least-privilege tokens. - Undeclared config access: The skill reads ~/.openclaw/openclaw.json and .env; the registry should have declared these config paths. If you keep sensitive tokens in other files, this skill could access them unintentionally. - Runtime npm install: The skill instructs installing smh-node-sdk from npm (global or /tmp) and writing/executing /tmp/smh-upload.js. Verify the smh-node-sdk package name and its provenance (official vendor package vs. third-party). Consider inspecting the package code before allowing installs. - Inspect the script: Because this is instruction-only and the upload script is written at runtime, request the full /tmp/smh-upload.js content from the skill (or view the SKILL.md's Script section) and review it for unexpected network calls or data exfiltration. Recommended actions before use: 1) Ask the publisher to update registry metadata to list required env vars/config paths and to explain why space_admin scope is needed. 2) Use limited-scope or temporary tokens rather than space_admin tokens. If possible, prefer librarySecret flow with a narrowly scoped secret and short-lived tokens. 3) Review the smh-node-sdk package (npm page, source) and the upload script contents for unexpected behavior. 4) If you cannot verify the code and package origin, avoid granting credentials or allow the skill to run npm install on your system.

Like a lobster shell, security has layers — review code before you run it.

latestvk979d3p5y7yfqefab0z366gnms8432rh

License

MIT-0
Free to use, modify, and redistribute. No attribution required.

Runtime requirements

☁️ Clawdis
Binsnode, npm

SKILL.md

云文件上传备份工具 (Tencent SMH)

将本地文件上传至腾讯 SMH 云存储,自动生成下载链接和图片预览。

Setup

脚本依赖 smh-node-sdk,首次使用前需安装:

npm install -g smh-node-sdk 2>/dev/null || npm install smh-node-sdk --prefix /tmp/smh-sdk

脚本会自动从全局或 /tmp/smh-sdk 中加载 SDK,无需手动配置路径。

脚本支持两种凭证模式(优先级从高到低):

模式一:配置文件(直接凭证,推荐)

~/.openclaw/openclaw.jsonenv 字段中配置完整凭证,脚本直接使用,无需每次传参:

{
  "env": {
    "smh-basePath": "https://api.tencentsmh.cn",
    "smh-libraryId": "smhxxx-xxxxx",
    "smh-spaceId": "space-xxxxx",
    "smh-accessToken": "<space_admin 权限的 token>"
  }
}

也可以在当前工作目录的 .env 文件中配置:

smh-basePath=https://api.tencentsmh.cn
smh-libraryId=smhxxx-xxxxx
smh-spaceId=space-xxxxx
smh-accessToken=<space_admin 权限的 token>

模式二:配置文件(librarySecret,脚本自动换取 token)

~/.openclaw/openclaw.jsonenv 字段中配置 librarySecret,脚本自动获取 token:

{
  "env": {
    "smh-libraryId": "your-library-id",
    "smh-librarySecret": "your-library-secret",
    "smh-basePath": "https://api.tencentsmh.cn"
  }
}

模式三:命令行直接传参(适合临时覆盖配置文件的场景)

在每条命令的 JSON 参数中直接传入,优先级最高:

{
  "basePath": "https://api.tencentsmh.cn",
  "libraryId": "smhxxx-xxxxx",
  "spaceId": "space-xxxxx",
  "accessToken": "<space_admin 权限的 token>"
}

Workflow

MyClaw uses this skill in any scenario that requires uploading files to the cloud.

Complete flow

User triggers file upload
  → Step 1: Identify the local file path(s)
  → Step 2: Run upload script (loop for batch)
  → Step 3: Extract downloadUrl from JSON output (signed COS URL)
  → Step 4: Deliver the download link with execution notice

IMPORTANT: 默认必须使用 conflictStrategy: "ask" 上传。这样当云端已存在同名文件时,脚本会返回错误,MyClaw 可以询问用户如何处理。只有用户明确说了 "覆盖"/"替换" 或 "重命名" 时,才使用对应的 conflictStrategy: "overwrite"conflictStrategy: "rename"

Step 2: Upload

Single file (默认):

node /tmp/smh-upload.js upload '{"localPath":"/path/to/file.pdf","conflictStrategy":"ask"}'

Upload to specific directory:

node /tmp/smh-upload.js upload '{"localPath":"/path/to/photo.jpg","remotePath":"photos/photo.jpg","conflictStrategy":"ask"}'

User explicitly requested overwrite:

node /tmp/smh-upload.js upload '{"localPath":"/path/to/report.pdf","conflictStrategy":"overwrite"}'

Batch upload:

node /tmp/smh-upload.js upload '{"localPath":"/path/to/file1.pdf","conflictStrategy":"ask"}'
node /tmp/smh-upload.js upload '{"localPath":"/path/to/file2.docx","conflictStrategy":"ask"}'

Conflict handling

When using conflictStrategy: "ask" (默认), if a same-name file already exists, the script returns {"success":false,"conflict":true}. MyClaw must then ask the user:

云端已存在同名文件 {filename},你想怎么处理?

  1. 🔄 覆盖 — 替换云端文件
  2. 📝 重命名 — 自动改名上传(如 file(1).pdf)
  3. ❌ 取消 — 不上传

三种策略对照:

StrategyBehaviorWhen to use
ask (默认,必须使用)同名文件存在时返回错误,MyClaw 询问用户用户未表明偏好时
overwrite直接覆盖已有文件用户明确说 "覆盖", "替换", "更新文件"
rename自动重命名 → file(1).pdf用户明确说 "重命名", "改名上传"

Step 4: Deliver link + execution notice

After every successful upload, include this notice alongside the download link(s):

链接已生成,链接有效期 2 小时,可直接在浏览器或手机中打开。

Single file example:

链接已生成,链接有效期 2 小时,可直接在浏览器或手机中打开。

已上传文件: report.pdf 大小: (2.3 MB) 下载链接: https://api.tencentsmh.cn/api/v1/file/smhxxx/space-xxx/report.pdf?access_token=acctk...&ContentDisposition=attachment&Purpose=download

Batch example:

链接已生成,链接有效期 2 小时,可直接在浏览器或手机中打开。

📎 report.pdf (2.3 MB) — https://api.tencentsmh.cn/api/v1/file/smhxxx/space-xxx/report.pdf?access_token=acctk...&ContentDisposition=attachment&Purpose=download 📎 photo.jpg (1.1 MB) — https://api.tencentsmh.cn/api/v1/file/smhxxx/space-xxx/photo.jpg?access_token=acctk...&ContentDisposition=attachment&Purpose=download


File Size Support

There is NO file size limit. The upload script supports files of any size, including multi-GB videos.

  • Small files (≤ 50 MB): Single-part upload.
  • Large files (> 50 MB): Multipart upload — the file is read in 5 MB chunks, never loaded entirely into memory.

Commands

所有命令输出 JSON 到 stdout。在执行前先将脚本写入 /tmp/smh-upload.js(见 Script 章节)。

upload

node /tmp/smh-upload.js upload '<json>'

JSON 参数:

  • localPath(必填):本地文件绝对路径,支持 ~ 展开
  • remotePath(可选):云端目标路径,省略则上传到根目录并保留原文件名
  • conflictStrategy(可选):ask(默认)| rename | overwrite
  • basePath(可选):SMH API 地址,如 https://api.tencentsmh.cn,有此参数时走直接传参模式
  • libraryId(可选):媒体库 ID,直接传参模式必填
  • spaceId(可选):空间 ID,直接传参模式必填
  • accessToken(可选):space_admin 权限的 token,直接传参模式必填

Output:

{
  "success": true,
  "upload": {
    "localFile": "/path/to/photo.jpg",
    "remotePath": "photo.jpg",
    "fileSize": 2048576,
    "fileSizeHuman": "2.0 MB",
    "uploadTime": "3.2s",
    "rapidUpload": false
  },
  "downloadUrl": "https://api.tencentsmh.cn/api/v1/file/smhxxx/space-xxx/photo.jpg?access_token=acctk...&ContentDisposition=attachment&Purpose=download"
}

info

node /tmp/smh-upload.js info '<json>'

JSON 参数:

  • remotePath(必填):云端文件路径
  • basePath / libraryId / spaceId / accessToken(可选):直接传参模式凭证

Output:

{
  "success": true,
  "remotePath": "report.pdf",
  "downloadUrl": "https://api.tencentsmh.cn/api/v1/file/smhxxx/space-xxx/report.pdf?access_token=acctk...&ContentDisposition=attachment&Purpose=download",
  "fileInfo": {
    "name": "report.pdf",
    "size": 2048576,
    "type": "application/pdf",
    "creationTime": "2026-03-13T10:00:00Z",
    "modificationTime": "2026-03-13T10:00:00Z"
  }
}

list

node /tmp/smh-upload.js list '<json>'

JSON 参数:

  • dirPath(可选):目录路径,默认 /
  • limit(可选):最大返回数量,默认 50
  • basePath / libraryId / spaceId / accessToken(可选):直接传参模式凭证

Script

在执行任何命令前,先将以下脚本写入 /tmp/smh-upload.js

cat > /tmp/smh-upload.js << 'SCRIPT_EOF'
#!/usr/bin/env node
'use strict';

const fs = require('fs');
const path = require('path');
const os = require('os');

// ==================== 加载 smh-node-sdk ====================

function loadSMHClient() {
  // 尝试从多个位置加载 smh-node-sdk
  const candidates = [
    () => require('smh-node-sdk'),
    () => require(path.join('/tmp/smh-sdk', 'node_modules', 'smh-node-sdk')),
    () => {
      // 尝试全局 node_modules
      const { execSync } = require('child_process');
      const globalPath = execSync('npm root -g 2>/dev/null').toString().trim();
      return require(path.join(globalPath, 'smh-node-sdk'));
    },
  ];
  for (const load of candidates) {
    try { return load(); } catch (e) { /* 继续尝试下一个 */ }
  }
  throw new Error(
    'smh-node-sdk 未安装,请先运行:\n' +
    '  npm install -g smh-node-sdk\n' +
    '或:\n' +
    '  npm install smh-node-sdk --prefix /tmp/smh-sdk'
  );
}

const { SMHClient } = loadSMHClient();

// ==================== 凭证加载 ====================

// 凭证优先级:
// 1. 命令行直接传参(args 中有 basePath + libraryId + spaceId + accessToken)
// 2. 配置文件直接凭证(openclaw.json smh 字段有 spaceId + accessToken)
// 3. 配置文件 librarySecret(SDK 自动换取 token)
function loadEnvConfig() {
  // 优先读取 .env 文件(当前工作目录)
  const dotEnvPath = path.join(process.cwd(), '.env');
  const envVars = {};
  if (fs.existsSync(dotEnvPath)) {
    const lines = fs.readFileSync(dotEnvPath, 'utf8').split('\n');
    for (const line of lines) {
      const trimmed = line.trim();
      if (!trimmed || trimmed.startsWith('#')) continue;
      const eqIdx = trimmed.indexOf('=');
      if (eqIdx === -1) continue;
      const key = trimmed.slice(0, eqIdx).trim();
      const val = trimmed.slice(eqIdx + 1).trim().replace(/^["']|["']$/g, '');
      envVars[key] = val;
    }
  }
  // 再读取 openclaw.json 的 env 字段(.env 优先级更高,已有的不覆盖)
  const cfgPath = path.join(os.homedir(), '.openclaw', 'openclaw.json');
  if (fs.existsSync(cfgPath)) {
    try {
      const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
      const envSection = cfg.env || {};
      for (const [k, v] of Object.entries(envSection)) {
        if (!(k in envVars)) envVars[k] = v;
      }
    } catch (e) { /* 解析失败忽略 */ }
  }
  return {
    basePath: envVars['smh-basePath'] || envVars['SMH_BASE_PATH'],
    libraryId: envVars['smh-libraryId'] || envVars['SMH_LIBRARY_ID'],
    spaceId: envVars['smh-spaceId'] || envVars['SMH_SPACE_ID'],
    accessToken: envVars['smh-accessToken'] || envVars['SMH_ACCESS_TOKEN'],
    librarySecret: envVars['smh-librarySecret'] || envVars['SMH_LIBRARY_SECRET'],
  };
}

async function resolveCredentials(args) {
  const { basePath, libraryId, spaceId, accessToken } = args;
  // 优先级 1:命令行直接传参
  if (basePath && libraryId && spaceId && accessToken) {
    return { host: basePath, libraryId, spaceId, accessToken };
  }
  // 读取环境配置(.env 或 openclaw.json env 字段)
  const smh = loadEnvConfig();
  const host = smh.basePath || 'https://api.tencentsmh.cn';
  const cfgLibraryId = smh.libraryId;
  if (!cfgLibraryId) {
    throw new Error('缺少 SMH 凭证,请在 ~/.openclaw/openclaw.json 的 env 字段或 .env 文件中配置 smh-libraryId');
  }
  // 优先级 2:配置文件直接凭证(有 spaceId + accessToken)
  if (smh.spaceId && smh.accessToken) {
    return { host, libraryId: cfgLibraryId, spaceId: smh.spaceId, accessToken: smh.accessToken };
  }
  // 优先级 3:配置文件 librarySecret,SDK 自动换取 token
  const librarySecret = smh.librarySecret;
  if (!librarySecret) {
    throw new Error('缺少 SMH 凭证,请在 ~/.openclaw/openclaw.json 的 env 字段或 .env 文件中配置 smh-accessToken 或 smh-librarySecret');
  }
  // 用 SDK 获取 admin token,再获取 space_admin token
  const client = new SMHClient({ basePath: host });
  const adminRes = await client.token.createToken({
    libraryId: cfgLibraryId,
    librarySecret,
    grant: 'admin',
    period: 3600,
  });
  const adminToken = adminRes.data.accessToken;

  // 获取默认 spaceId
  const resolvedSpaceId = spaceId || await getDefaultSpaceId(client, cfgLibraryId, adminToken);

  const spaceRes = await client.token.createToken({
    libraryId: cfgLibraryId,
    librarySecret,
    grant: 'space_admin',
    spaceId: resolvedSpaceId,
    period: 7200,
  });
  return { host, libraryId: cfgLibraryId, spaceId: resolvedSpaceId, accessToken: spaceRes.data.accessToken };
}

async function getDefaultSpaceId(client, libraryId, adminToken) {
  const res = await client.space.listSpace({
    libraryId,
    accessToken: adminToken,
    userId: '9527',
    page: 1,
    pageSize: 10,
  });
  const list = (res.data && res.data.list) || [];
  if (list.length === 0) throw new Error('没有可用的云存储空间,请先在管理后台创建空间');
  return list[0].spaceId;
}

// ==================== 工具函数 ====================

function expandHome(p) {
  if (!p) return p;
  if (p.startsWith('~/') || p === '~') return path.join(os.homedir(), p.slice(1));
  return p;
}

function formatSize(bytes) {
  if (!bytes) return '0 B';
  const u = ['B', 'KB', 'MB', 'GB', 'TB'];
  const i = Math.floor(Math.log(bytes) / Math.log(1024));
  return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${u[i]}`;
}

function out(obj) { process.stdout.write(JSON.stringify(obj) + '\n'); }

// ==================== 命令处理 ====================

async function cmdUpload(args) {
  const { localPath, remotePath, conflictStrategy = 'ask' } = args;
  if (!localPath) return out({ success: false, error: '缺少必填参数 localPath' });

  const absLocal = expandHome(localPath);
  if (!fs.existsSync(absLocal)) return out({ success: false, error: `本地文件不存在: ${absLocal}` });
  const stat = fs.statSync(absLocal);
  if (!stat.isFile()) return out({ success: false, error: `路径不是文件: ${absLocal}` });

  const fileSize = stat.size;
  const fileName = path.basename(absLocal);
  const cloudPath = remotePath || fileName;

  const startTime = Date.now();
  const { host, libraryId, spaceId, accessToken } = await resolveCredentials(args);

  // 使用 SMHClient 创建上传任务(支持秒传、简单上传、分片上传)
  const client = new SMHClient({ basePath: host });
  let rapidUpload = false;

  try {
    const task = await client.createUploadTask({
      libraryId,
      spaceId,
      accessToken,
      filePath: cloudPath,
      localPath: absLocal,
      conflictResolutionStrategy: conflictStrategy,
      onProgress: (state, progress) => {
        if (progress > 0 && progress < 100) {
          process.stderr.write(`[上传进度] ${fileName}: ${progress}%\n`);
        }
      },
      onComplete: (response) => {
        rapidUpload = !!(response && response.rapidUpload);
      },
    });
    await task.start();
  } catch (err) {
    // 409 冲突
    const status = err && err.response && err.response.status;
    if (status === 409) {
      return out({ success: false, conflict: true, fileName, error: `云端已存在同名文件 "${fileName}"` });
    }
    return out({ success: false, error: `Upload failed: ${err.message}` });
  }

  const uploadTime = ((Date.now() - startTime) / 1000).toFixed(1) + 's';

  // 构建带 access_token 的 SMH 直链(302 跳转到带签名的 COS URL,可直接访问)
  const encodedCloudPath = cloudPath.split('/').map(encodeURIComponent).join('/');
  const downloadUrl = `${host}/api/v1/file/${libraryId}/${spaceId}/${encodedCloudPath}?access_token=${encodeURIComponent(accessToken)}&ContentDisposition=attachment&Purpose=download`;

  out({
    success: true,
    upload: { localFile: absLocal, remotePath: cloudPath, fileSize, fileSizeHuman: formatSize(fileSize), uploadTime, rapidUpload },
    downloadUrl,
  });
}

async function cmdInfo(args) {
  const { remotePath } = args;
  if (!remotePath) return out({ success: false, error: '缺少必填参数 remotePath' });

  const { host, libraryId, spaceId, accessToken } = await resolveCredentials(args);
  const client = new SMHClient({ basePath: host });

  let info;
  try {
    const res = await client.file.infoFile({
      libraryId,
      spaceId,
      filePath: remotePath,
      info: 1,
      accessToken,
    });
    info = res.data;
  } catch (e) {
    const status = e && e.response && e.response.status;
    if (status === 404) return out({ success: false, error: `云端文件不存在: ${remotePath}` });
    return out({ success: false, error: e.message });
  }

  // 构建带 access_token 的 SMH 直链(302 跳转到带签名的 COS URL,可直接访问)
  const encodedRemotePath = remotePath.split('/').map(encodeURIComponent).join('/');
  const downloadUrl = `${host}/api/v1/file/${libraryId}/${spaceId}/${encodedRemotePath}?access_token=${encodeURIComponent(accessToken)}&ContentDisposition=attachment&Purpose=download`;

  out({
    success: true, remotePath,
    downloadUrl,
    fileInfo: {
      name: path.basename(remotePath),
      size: info && info.size ? parseInt(info.size, 10) : null,
      type: (info && info.contentType) || '',
      creationTime: (info && info.creationTime) || null,
      modificationTime: (info && info.modificationTime) || null,
    },
  });
}

async function cmdList(args) {
  const { dirPath = '/', limit = 50 } = args;
  const { host, libraryId, spaceId, accessToken } = await resolveCredentials(args);
  const client = new SMHClient({ basePath: host });

  // 目录路径:根目录传空字符串
  const normalized = dirPath === '/' || dirPath === '' ? '' : dirPath.replace(/^\//, '');

  let data;
  try {
    const res = await client.directory.listDirectoryByPage({
      libraryId,
      spaceId,
      filePath: normalized,
      byPage: 1,
      page: 1,
      pageSize: Math.min(limit, 200),
      accessToken,
    });
    data = res.data;
  } catch (e) { return out({ success: false, error: e.message }); }

  const files = ((data && data.contents) || []).map((item) => ({
    name: item.name || '',
    type: item.type || 'file',
    size: item.size ? parseInt(item.size, 10) : null,
    sizeHuman: item.size ? formatSize(parseInt(item.size, 10)) : null,
    creationTime: item.creationTime || null,
    modificationTime: item.modificationTime || null,
    path: normalized ? `${normalized}/${item.name}` : item.name,
  }));

  out({ success: true, dirPath, total: (data && data.totalNum) || files.length, files });
}

// ==================== 入口 ====================

const [,, cmd, argsStr] = process.argv;
let args = {};
try { if (argsStr) args = JSON.parse(argsStr); } catch (e) { out({ success: false, error: `参数解析失败: ${e.message}` }); process.exit(1); }

const cmds = { upload: cmdUpload, info: cmdInfo, list: cmdList };
if (!cmds[cmd]) { out({ success: false, error: `未知命令: ${cmd},支持: upload / info / list` }); process.exit(1); }

cmds[cmd](args).catch((e) => { out({ success: false, error: e.message }); process.exit(1); });
SCRIPT_EOF

Full Example (macOS / Linux)

# Step 0: 安装 smh-node-sdk(首次使用执行一次)
npm install smh-node-sdk --prefix /tmp/smh-sdk

# Step 1: 写入脚本(每次会话执行一次即可)
cat > /tmp/smh-upload.js << 'SCRIPT_EOF'
# ... (脚本内容见上方 Script 章节)
SCRIPT_EOF

# Step 2: 获取父进程 ID(日志追踪)
PPID_VAL=$(python3 -c "import os; print(os.getppid())")
echo "[MyClaw] Parent PID: $PPID_VAL"

# Step 3: 上传文件
node /tmp/smh-upload.js upload '{"localPath":"/path/to/report.pdf","conflictStrategy":"ask"}'

# Step 4: 查询文件信息
node /tmp/smh-upload.js info '{"remotePath":"report.pdf"}'

# Step 5: 列出云端文件
node /tmp/smh-upload.js list '{"dirPath":"/","limit":20}'

Error Handling

所有命令输出 JSON 到 stdout。错误也以 JSON 返回:{"success": false, "error": "..."}

错误处理方式
上传失败(success: false告诉用户:"文件上传失败:{具体原因}。你可以稍后再试,或者检查网络连接。"
同名冲突(conflict: true询问用户选择覆盖、重命名或取消
文件不存在让用户确认路径
网络错误重试 2 次,间隔 3s;仍失败告知用户
配置缺失提示用户在 ~/.openclaw/openclaw.jsonenv 字段或 .env 文件中添加 smh-* 配置

上传失败 MyClaw 对话模板(当 success: false 时必须使用):

❌ 文件上传失败:{error 中的具体原因}。

你可以:

  1. 🔄 重试 — 重新上传这个文件
  2. ❌ 取消 — 暂时不上传

禁止行为

  • NEVERsuccess: false 时展示下载链接
  • NEVER 在上传失败时不告知用户,必须明确提示"文件上传失败"及原因
  • NEVER 硬编码或暴露 SMH 凭证给用户
  • NEVER 未经用户主动要求就上传其本地个人文件
  • NEVER 跳过执行通知:"链接已生成,有效期 2 小时,可直接在浏览器或手机中打开"
  • NEVER 在用户未明确表态时使用 conflictStrategy: "rename"conflictStrategy: "overwrite"

重要注意

  • 用户说"上传文件"但没指定路径 → 追问:"你要上传哪个文件?告诉我文件路径或文件名就行。"
  • 用户说"确定上传 xxx"或"把 xxx 发给我" → 直接执行上传(conflictStrategy: "ask"
  • 同名文件冲突:上传时必须使用 conflictStrategy: "ask"。如果返回 conflict: true,必须询问用户选择覆盖、重命名或取消
  • 文件默认上传到云空间根目录,用户可通过 remotePath 参数指定目标路径
  • 下载链接为带 access_token 的 SMH 直链(${basePath}/api/v1/file/${libraryId}/${spaceId}/...?access_token=...&ContentDisposition=attachment&Purpose=download),SMH 服务会 302 跳转到带签名的 COS URL,可直接在浏览器或手机中打开,有效期与 accessToken 一致
  • 批量上传按顺序处理(不并行),避免 API 过载
  • 执行通知:每次上传完成后必须告知用户:"链接已生成,有效期 2 小时,可直接在浏览器或手机中打开"
  • /tmp/smh-upload.js 脚本在同一会话中只需写入一次,后续命令直接复用

Files

1 total
Select a file
Select a file to preview.

Comments

Loading comments…