Files
stock-assistant/skills/tencent-cos-skill/scripts/cos_node.mjs
root 691b8cdd0c feat: 初始化黄小瓜AI助手记忆仓库
- 核心配置: IDENTITY, USER, SOUL, AGENTS, TOOLS, HEARTBEAT, MEMORY
- memory/: 每日总结和临时记录
- skills/: 所有已安装技能
- notes/: 语音配置笔记
2026-04-04 02:42:48 +08:00

324 lines
6.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/**
* 腾讯云 COS Node.js SDK 操作脚本
* 作为 cos-mcp MCP 工具不可用时的降级方案
*
* 依赖npm install cos-nodejs-sdk-v5
* 凭证通过环境变量读取:
* TENCENT_COS_SECRET_ID / TENCENT_COS_SECRET_KEY / TENCENT_COS_REGION / TENCENT_COS_BUCKET
*
* 用法node cos_node.mjs <action> [options]
*/
import { createRequire } from 'module';
import { createReadStream, createWriteStream, existsSync } from 'fs';
import { basename, resolve } from 'path';
import { pipeline } from 'stream/promises';
const require = createRequire(import.meta.url);
const COS = require('cos-nodejs-sdk-v5');
// 读取环境变量
const SecretId = process.env.TENCENT_COS_SECRET_ID;
const SecretKey = process.env.TENCENT_COS_SECRET_KEY;
const Region = process.env.TENCENT_COS_REGION;
const Bucket = process.env.TENCENT_COS_BUCKET;
// 可选的自定义域名配置
const Domain = process.env.TENCENT_COS_DOMAIN;
const ServiceDomain = process.env.TENCENT_COS_SERVICE_DOMAIN;
const Protocol = process.env.TENCENT_COS_PROTOCOL;
if (!SecretId || !SecretKey || !Region || !Bucket) {
console.error(JSON.stringify({
success: false,
error: '缺少环境变量需要TENCENT_COS_SECRET_ID, TENCENT_COS_SECRET_KEY, TENCENT_COS_REGION, TENCENT_COS_BUCKET',
}));
process.exit(1);
}
const cosOptions = { SecretId, SecretKey };
if (Domain) {
cosOptions.Domain = Domain;
}
if (ServiceDomain) {
cosOptions.ServiceDomain = ServiceDomain;
}
if (Protocol) {
cosOptions.Protocol = Protocol;
}
const cos = new COS(cosOptions);
// 解析命令行参数
function parseArgs(args) {
const result = {};
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg.startsWith('--')) {
const key = arg.slice(2);
const next = args[i + 1];
if (next && !next.startsWith('--')) {
result[key] = next;
i++;
} else {
result[key] = true;
}
}
}
return result;
}
// 输出 JSON 结果
function output(data) {
console.log(JSON.stringify(data, null, 2));
}
// 封装 COS SDK 回调为 Promise
function cosPromise(method, params) {
return new Promise((resolve, reject) => {
cos[method]({ Bucket, Region, ...params }, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
// ========== 操作实现 ==========
async function upload(opts) {
const filePath = opts.file;
const key = opts.key || basename(filePath);
if (!filePath) {
throw new Error('缺少 --file 参数');
}
if (!existsSync(filePath)) {
throw new Error(`文件不存在:${filePath}`);
}
const data = await cosPromise('putObject', {
Key: key,
Body: createReadStream(filePath),
});
output({
success: true,
action: 'upload',
key,
etag: data.ETag,
location: data.Location,
statusCode: data.statusCode,
});
}
async function putString(opts) {
const content = opts.content;
const key = opts.key;
const contentType = opts['content-type'] || 'text/plain';
if (!content) {
throw new Error('缺少 --content 参数');
}
if (!key) {
throw new Error('缺少 --key 参数');
}
const data = await cosPromise('putObject', {
Key: key,
Body: content,
ContentType: contentType,
});
output({
success: true,
action: 'put-string',
key,
etag: data.ETag,
location: data.Location,
statusCode: data.statusCode,
});
}
async function download(opts) {
const key = opts.key;
const outputPath = opts.output || basename(key);
if (!key) {
throw new Error('缺少 --key 参数');
}
const data = await cosPromise('getObject', {
Key: key,
});
const resolvedPath = resolve(outputPath);
const ws = createWriteStream(resolvedPath);
if (data.Body instanceof Buffer) {
ws.write(data.Body);
ws.end();
} else if (data.Body && typeof data.Body.pipe === 'function') {
await pipeline(data.Body, ws);
} else {
ws.write(String(data.Body));
ws.end();
}
output({
success: true,
action: 'download',
key,
savedTo: resolvedPath,
contentLength: data.headers?.['content-length'],
statusCode: data.statusCode,
});
}
async function list(opts) {
const prefix = opts.prefix || '';
const maxKeys = parseInt(opts['max-keys'], 10) || 100;
const data = await cosPromise('getBucket', {
Prefix: prefix,
MaxKeys: maxKeys,
});
const files = (data.Contents || []).map(item => ({
key: item.Key,
size: parseInt(item.Size, 10),
lastModified: item.LastModified,
etag: item.ETag,
storageClass: item.StorageClass,
}));
output({
success: true,
action: 'list',
prefix,
count: files.length,
isTruncated: data.IsTruncated === 'true',
files,
});
}
async function signUrl(opts) {
const key = opts.key;
const expires = parseInt(opts.expires, 10) || 3600;
if (!key) {
throw new Error('缺少 --key 参数');
}
const url = await new Promise((resolve, reject) => {
cos.getObjectUrl({
Bucket,
Region,
Key: key,
Expires: expires,
Sign: true,
}, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data.Url);
}
});
});
output({
success: true,
action: 'sign-url',
key,
expires,
url,
});
}
async function deleteObject(opts) {
const key = opts.key;
if (!key) {
throw new Error('缺少 --key 参数');
}
const data = await cosPromise('deleteObject', {
Key: key,
});
output({
success: true,
action: 'delete',
key,
statusCode: data.statusCode,
});
}
async function head(opts) {
const key = opts.key;
if (!key) {
throw new Error('缺少 --key 参数');
}
const data = await cosPromise('headObject', {
Key: key,
});
output({
success: true,
action: 'head',
key,
contentLength: parseInt(data.headers?.['content-length'], 10),
contentType: data.headers?.['content-type'],
etag: data.headers?.etag,
lastModified: data.headers?.['last-modified'],
storageClass: data.headers?.['x-cos-storage-class'] || 'STANDARD',
statusCode: data.statusCode,
});
}
// ========== 主入口 ==========
const args = process.argv.slice(2);
const action = args[0];
const opts = parseArgs(args.slice(1));
const actions = {
upload,
'put-string': putString,
download,
list,
'sign-url': signUrl,
delete: deleteObject,
head,
};
if (!action || !actions[action]) {
output({
success: false,
error: `未知操作:${action || '(空)'}`,
availableActions: Object.keys(actions),
usage: 'node cos_node.mjs <action> [--option value ...]',
});
process.exit(1);
}
try {
await actions[action](opts);
} catch (err) {
output({
success: false,
action,
error: err.message || String(err),
code: err.code,
});
process.exit(1);
}