ossput 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/skills/ossput/SKILL.md +87 -0
- package/.cursor/skills/ossput/examples.md +59 -0
- package/.cursor/skills/ossput/reference.md +78 -0
- package/.ossput.json.example +3 -0
- package/AGENTS.md +16 -0
- package/CHANGELOG.md +25 -0
- package/LICENSE +21 -0
- package/README.md +173 -0
- package/config.index.example.json +8 -0
- package/dist/batch-upload.d.ts +28 -0
- package/dist/batch-upload.js +34 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +253 -0
- package/dist/config-profiles.d.ts +45 -0
- package/dist/config-profiles.js +282 -0
- package/dist/config.d.ts +14 -0
- package/dist/config.js +81 -0
- package/dist/constants.d.ts +5 -0
- package/dist/constants.js +5 -0
- package/dist/delete-object.d.ts +6 -0
- package/dist/delete-object.js +26 -0
- package/dist/doctor.d.ts +9 -0
- package/dist/doctor.js +128 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +21 -0
- package/dist/key-builder.d.ts +1 -0
- package/dist/key-builder.js +13 -0
- package/dist/list-directories.d.ts +11 -0
- package/dist/list-directories.js +64 -0
- package/dist/list-format.d.ts +6 -0
- package/dist/list-format.js +57 -0
- package/dist/list-objects.d.ts +7 -0
- package/dist/list-objects.js +47 -0
- package/dist/mcp-annotations.d.ts +16 -0
- package/dist/mcp-annotations.js +16 -0
- package/dist/mcp-result.d.ts +14 -0
- package/dist/mcp-result.js +57 -0
- package/dist/mcp.d.ts +4 -0
- package/dist/mcp.js +244 -0
- package/dist/object-key.d.ts +10 -0
- package/dist/object-key.js +46 -0
- package/dist/oss-client.d.ts +4 -0
- package/dist/oss-client.js +24 -0
- package/dist/profile-cli.d.ts +16 -0
- package/dist/profile-cli.js +191 -0
- package/dist/setup/connectivity.d.ts +2 -0
- package/dist/setup/connectivity.js +5 -0
- package/dist/setup/logo.d.ts +1 -0
- package/dist/setup/logo.js +30 -0
- package/dist/setup/mcp-registry.d.ts +17 -0
- package/dist/setup/mcp-registry.js +128 -0
- package/dist/setup/prompts.d.ts +24 -0
- package/dist/setup/prompts.js +410 -0
- package/dist/setup/run-setup.d.ts +9 -0
- package/dist/setup/run-setup.js +156 -0
- package/dist/setup/skill-install.d.ts +19 -0
- package/dist/setup/skill-install.js +85 -0
- package/dist/setup/ui.d.ts +49 -0
- package/dist/setup/ui.js +164 -0
- package/dist/setup/write-config.d.ts +5 -0
- package/dist/setup/write-config.js +5 -0
- package/dist/skill-cli.d.ts +6 -0
- package/dist/skill-cli.js +34 -0
- package/dist/types.d.ts +82 -0
- package/dist/types.js +1 -0
- package/dist/upload-pipeline.d.ts +7 -0
- package/dist/upload-pipeline.js +96 -0
- package/dist/validators.d.ts +10 -0
- package/dist/validators.js +77 -0
- package/docs/ram-policy.example.json +31 -0
- package/package.json +59 -0
- package/profiles.example/default.json +21 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AppConfig, DeleteObjectResult } from "./types.js";
|
|
2
|
+
export interface DeleteObjectOptions {
|
|
3
|
+
/** 必须为 true,防止 Agent 误删 */
|
|
4
|
+
confirm: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare function deleteObject(config: AppConfig, objectKey: string, options: DeleteObjectOptions): Promise<DeleteObjectResult>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createOssClient, publicObjectUrl } from "./oss-client.js";
|
|
2
|
+
import { assertDeleteEnabled, assertObjectKeyDeletable, } from "./object-key.js";
|
|
3
|
+
export async function deleteObject(config, objectKey, options) {
|
|
4
|
+
if (options.confirm !== true) {
|
|
5
|
+
throw new Error("refusing to delete without confirm: true — show the user objectKey and get explicit approval first");
|
|
6
|
+
}
|
|
7
|
+
assertDeleteEnabled(config);
|
|
8
|
+
const key = assertObjectKeyDeletable(config, objectKey);
|
|
9
|
+
const client = createOssClient(config);
|
|
10
|
+
try {
|
|
11
|
+
await client.head(key);
|
|
12
|
+
}
|
|
13
|
+
catch (err) {
|
|
14
|
+
const e = err;
|
|
15
|
+
if (e.code === "NoSuchKey" || e.status === 404) {
|
|
16
|
+
throw new Error(`object not found: ${key}`);
|
|
17
|
+
}
|
|
18
|
+
throw err;
|
|
19
|
+
}
|
|
20
|
+
await client.delete(key);
|
|
21
|
+
return {
|
|
22
|
+
objectKey: key,
|
|
23
|
+
deleted: true,
|
|
24
|
+
objectUrl: publicObjectUrl(config, key),
|
|
25
|
+
};
|
|
26
|
+
}
|
package/dist/doctor.d.ts
ADDED
package/dist/doctor.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { access } from "node:fs/promises";
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import { lstat, readlink } from "node:fs/promises";
|
|
4
|
+
import { resolve, dirname } from "node:path";
|
|
5
|
+
import { configExists, findProjectOssputJson, listProfileNames, loadConfigWithProfile, INDEX_CONFIG_PATH, } from "./config-profiles.js";
|
|
6
|
+
import { listConfiguredClients, MCP_CLIENTS } from "./setup/mcp-registry.js";
|
|
7
|
+
import { SKILL_TARGETS, resolveBundledSkillDir } from "./setup/skill-install.js";
|
|
8
|
+
import { testConnectivity } from "./setup/connectivity.js";
|
|
9
|
+
async function pathOk(path) {
|
|
10
|
+
try {
|
|
11
|
+
await access(path, constants.F_OK);
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function checkSkillTarget(dest) {
|
|
19
|
+
if (!(await pathOk(dest))) {
|
|
20
|
+
return "未安装(运行 ossput skill install)";
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const st = await lstat(dest);
|
|
24
|
+
if (st.isSymbolicLink()) {
|
|
25
|
+
const target = await readlink(dest);
|
|
26
|
+
const resolved = resolve(dirname(dest), target);
|
|
27
|
+
const bundled = resolve(resolveBundledSkillDir());
|
|
28
|
+
if (resolve(resolved) === resolve(bundled)) {
|
|
29
|
+
return `已链接 → ${resolved}`;
|
|
30
|
+
}
|
|
31
|
+
return `符号链接 → ${resolved}`;
|
|
32
|
+
}
|
|
33
|
+
return "已复制(非符号链接,升级包后建议重装 Skill)";
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return "存在但无法读取";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export async function runDoctor(profile) {
|
|
40
|
+
const checks = [];
|
|
41
|
+
const nodeMajor = Number(process.version.slice(1).split(".")[0]);
|
|
42
|
+
checks.push({
|
|
43
|
+
name: "Node.js",
|
|
44
|
+
ok: nodeMajor >= 18,
|
|
45
|
+
detail: nodeMajor >= 18 ? process.version : `${process.version}(需要 ≥18)`,
|
|
46
|
+
});
|
|
47
|
+
const hasFetch = typeof globalThis.fetch === "function";
|
|
48
|
+
checks.push({
|
|
49
|
+
name: "fetch(上传)",
|
|
50
|
+
ok: hasFetch,
|
|
51
|
+
detail: hasFetch
|
|
52
|
+
? `Node ${process.version} 内置 fetch`
|
|
53
|
+
: "不可用(需要 Node.js 18+)",
|
|
54
|
+
});
|
|
55
|
+
const configured = await configExists();
|
|
56
|
+
checks.push({
|
|
57
|
+
name: "OSS 配置",
|
|
58
|
+
ok: configured,
|
|
59
|
+
detail: configured ? INDEX_CONFIG_PATH : `未配置(运行 npx ossput setup)`,
|
|
60
|
+
});
|
|
61
|
+
if (configured) {
|
|
62
|
+
try {
|
|
63
|
+
const rows = await listProfileNames();
|
|
64
|
+
const { config, resolved } = await loadConfigWithProfile({ profile });
|
|
65
|
+
checks.push({
|
|
66
|
+
name: "当前 Profile",
|
|
67
|
+
ok: true,
|
|
68
|
+
detail: `${resolved.name}(${resolved.source})· ${config.bucket}`,
|
|
69
|
+
});
|
|
70
|
+
checks.push({
|
|
71
|
+
name: "Profile 数量",
|
|
72
|
+
ok: rows.length > 0,
|
|
73
|
+
detail: String(rows.length),
|
|
74
|
+
});
|
|
75
|
+
try {
|
|
76
|
+
await testConnectivity(config);
|
|
77
|
+
checks.push({
|
|
78
|
+
name: "Bucket 连通性",
|
|
79
|
+
ok: true,
|
|
80
|
+
detail: "可访问",
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
catch (e) {
|
|
84
|
+
checks.push({
|
|
85
|
+
name: "Bucket 连通性",
|
|
86
|
+
ok: false,
|
|
87
|
+
detail: e instanceof Error ? e.message : String(e),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (e) {
|
|
92
|
+
checks.push({
|
|
93
|
+
name: "配置加载",
|
|
94
|
+
ok: false,
|
|
95
|
+
detail: e instanceof Error ? e.message : String(e),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const clients = configured ? await listConfiguredClients() : [];
|
|
100
|
+
checks.push({
|
|
101
|
+
name: "MCP 注册",
|
|
102
|
+
ok: clients.length > 0,
|
|
103
|
+
detail: clients.length > 0
|
|
104
|
+
? clients
|
|
105
|
+
.map((id) => MCP_CLIENTS.find((c) => c.id === id)?.displayName ?? id)
|
|
106
|
+
.join(", ")
|
|
107
|
+
: "未注册(运行 ossput setup)",
|
|
108
|
+
});
|
|
109
|
+
for (const target of SKILL_TARGETS) {
|
|
110
|
+
const dest = target.resolvePath();
|
|
111
|
+
const detail = await checkSkillTarget(dest);
|
|
112
|
+
checks.push({
|
|
113
|
+
name: `Skill · ${target.displayName}`,
|
|
114
|
+
ok: await pathOk(dest),
|
|
115
|
+
detail,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
const projectFile = await findProjectOssputJson();
|
|
119
|
+
if (projectFile) {
|
|
120
|
+
checks.push({
|
|
121
|
+
name: "项目绑定",
|
|
122
|
+
ok: true,
|
|
123
|
+
detail: projectFile,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
const ok = checks.every((c) => c.ok);
|
|
127
|
+
return { checks, ok };
|
|
128
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { runCli } from "./cli.js";
|
|
3
|
+
import { startMcpServer } from "./mcp.js";
|
|
4
|
+
const argv = process.argv.slice(2);
|
|
5
|
+
const subcommand = argv[0];
|
|
6
|
+
const MCP_MODE = !subcommand ||
|
|
7
|
+
subcommand.startsWith("-") ||
|
|
8
|
+
subcommand === "mcp" ||
|
|
9
|
+
process.env.OSSPUT_MCP === "1";
|
|
10
|
+
async function main() {
|
|
11
|
+
if (MCP_MODE && (!subcommand || subcommand === "mcp")) {
|
|
12
|
+
await startMcpServer();
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const code = await runCli(argv);
|
|
16
|
+
process.exit(code);
|
|
17
|
+
}
|
|
18
|
+
main().catch((err) => {
|
|
19
|
+
console.error(err instanceof Error ? err.message : err);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function buildObjectKey(prefix: string, subdir: string, filename: string, overwrite: boolean): string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
export function buildObjectKey(prefix, subdir, filename, overwrite) {
|
|
3
|
+
const ext = filename.includes(".")
|
|
4
|
+
? filename.slice(filename.lastIndexOf(".") + 1).toLowerCase()
|
|
5
|
+
: "bin";
|
|
6
|
+
const now = new Date();
|
|
7
|
+
const yyyy = String(now.getUTCFullYear());
|
|
8
|
+
const mm = String(now.getUTCMonth() + 1).padStart(2, "0");
|
|
9
|
+
const name = overwrite
|
|
10
|
+
? filename.replace(/[^a-zA-Z0-9._-]/g, "_")
|
|
11
|
+
: `${randomUUID()}.${ext}`;
|
|
12
|
+
return `${prefix}${subdir}${yyyy}/${mm}/${name}`;
|
|
13
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AppConfig } from "./types.js";
|
|
2
|
+
export interface ListDirectoriesResult {
|
|
3
|
+
prefix: string;
|
|
4
|
+
count: number;
|
|
5
|
+
directories: string[];
|
|
6
|
+
}
|
|
7
|
+
/** 递归列出 prefix 下所有「目录」(CommonPrefixes) */
|
|
8
|
+
export declare function listDirectories(config: AppConfig, options?: {
|
|
9
|
+
subdir?: string;
|
|
10
|
+
}): Promise<ListDirectoriesResult>;
|
|
11
|
+
export declare function formatDirectoriesAsMarkdown(result: ListDirectoriesResult): string;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { formatPrefixDisplay, normalizePrefix } from "./config.js";
|
|
2
|
+
import { createOssClient } from "./oss-client.js";
|
|
3
|
+
import { validateSubdir } from "./validators.js";
|
|
4
|
+
function buildListPrefix(config, subdir) {
|
|
5
|
+
const sub = validateSubdir(subdir);
|
|
6
|
+
if (!sub)
|
|
7
|
+
return config.prefix;
|
|
8
|
+
const normalized = sub.endsWith("/") ? sub : `${sub}/`;
|
|
9
|
+
return `${config.prefix}${normalized}`;
|
|
10
|
+
}
|
|
11
|
+
async function listImmediateSubdirs(client, prefix) {
|
|
12
|
+
const dirs = [];
|
|
13
|
+
let marker;
|
|
14
|
+
do {
|
|
15
|
+
const page = await client.list({ prefix, delimiter: "/", marker, "max-keys": 1000 }, {});
|
|
16
|
+
for (const p of page.prefixes ?? [])
|
|
17
|
+
dirs.push(p);
|
|
18
|
+
marker = page.isTruncated ? page.nextMarker : undefined;
|
|
19
|
+
} while (marker);
|
|
20
|
+
return dirs;
|
|
21
|
+
}
|
|
22
|
+
/** 递归列出 prefix 下所有「目录」(CommonPrefixes) */
|
|
23
|
+
export async function listDirectories(config, options) {
|
|
24
|
+
const prefix = buildListPrefix(config, options?.subdir);
|
|
25
|
+
const client = createOssClient(config);
|
|
26
|
+
const all = [];
|
|
27
|
+
async function walk(current) {
|
|
28
|
+
const children = await listImmediateSubdirs(client, current);
|
|
29
|
+
for (const dir of children.sort()) {
|
|
30
|
+
all.push(dir);
|
|
31
|
+
await walk(dir);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
await walk(prefix);
|
|
35
|
+
return {
|
|
36
|
+
prefix: formatPrefixDisplay(prefix),
|
|
37
|
+
count: all.length,
|
|
38
|
+
directories: all,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function formatDirectoriesAsMarkdown(result) {
|
|
42
|
+
const lines = [
|
|
43
|
+
`# OSS 目录列表`,
|
|
44
|
+
``,
|
|
45
|
+
`- **前缀**: \`${result.prefix}\``,
|
|
46
|
+
`- **目录数**: ${result.count}`,
|
|
47
|
+
``,
|
|
48
|
+
];
|
|
49
|
+
if (result.count === 0) {
|
|
50
|
+
lines.push(`_(无子目录)_`);
|
|
51
|
+
return lines.join("\n");
|
|
52
|
+
}
|
|
53
|
+
const rootNorm = result.prefix === "/" ? "" : normalizePrefix(result.prefix);
|
|
54
|
+
lines.push("```");
|
|
55
|
+
for (const dir of result.directories) {
|
|
56
|
+
const rel = rootNorm && dir.startsWith(rootNorm) ? dir.slice(rootNorm.length) : dir;
|
|
57
|
+
const depth = rel.replace(/\/$/, "").split("/").filter(Boolean).length - 1;
|
|
58
|
+
const indent = " ".repeat(Math.max(0, depth));
|
|
59
|
+
const label = rel.replace(/\/$/, "").split("/").filter(Boolean).pop() ?? dir;
|
|
60
|
+
lines.push(`${indent}${label}/`);
|
|
61
|
+
}
|
|
62
|
+
lines.push("```");
|
|
63
|
+
return lines.join("\n");
|
|
64
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ListObjectsResult, OssObjectInfo } from "./types.js";
|
|
2
|
+
export declare function isPreviewableImage(objectKey: string): boolean;
|
|
3
|
+
export declare function filterObjects(objects: OssObjectInfo[], imagesOnly: boolean): OssObjectInfo[];
|
|
4
|
+
export declare function formatListAsMarkdown(result: ListObjectsResult, options?: {
|
|
5
|
+
previewMax?: number;
|
|
6
|
+
}): string;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { formatPrefixDisplay } from "./config.js";
|
|
2
|
+
const IMAGE_EXT = new Set(["png", "jpg", "jpeg", "gif", "webp", "svg"]);
|
|
3
|
+
export function isPreviewableImage(objectKey) {
|
|
4
|
+
const dot = objectKey.lastIndexOf(".");
|
|
5
|
+
if (dot < 0)
|
|
6
|
+
return false;
|
|
7
|
+
return IMAGE_EXT.has(objectKey.slice(dot + 1).toLowerCase());
|
|
8
|
+
}
|
|
9
|
+
function formatBytes(size) {
|
|
10
|
+
if (size < 1024)
|
|
11
|
+
return `${size} B`;
|
|
12
|
+
if (size < 1024 * 1024)
|
|
13
|
+
return `${(size / 1024).toFixed(1)} KB`;
|
|
14
|
+
return `${(size / (1024 * 1024)).toFixed(1)} MB`;
|
|
15
|
+
}
|
|
16
|
+
function basename(objectKey) {
|
|
17
|
+
const slash = objectKey.lastIndexOf("/");
|
|
18
|
+
return slash >= 0 ? objectKey.slice(slash + 1) : objectKey;
|
|
19
|
+
}
|
|
20
|
+
export function filterObjects(objects, imagesOnly) {
|
|
21
|
+
if (!imagesOnly)
|
|
22
|
+
return objects;
|
|
23
|
+
return objects.filter((o) => isPreviewableImage(o.objectKey));
|
|
24
|
+
}
|
|
25
|
+
export function formatListAsMarkdown(result, options) {
|
|
26
|
+
const previewMax = Math.min(options?.previewMax ?? 20, 50);
|
|
27
|
+
const lines = [
|
|
28
|
+
`# OSS 文件列表`,
|
|
29
|
+
``,
|
|
30
|
+
`- **前缀**: \`${formatPrefixDisplay(result.prefix)}\``,
|
|
31
|
+
`- **数量**: ${result.count}${result.truncated ? "(已截断,可增大 maxKeys 或缩小 subdir)" : ""}`,
|
|
32
|
+
``,
|
|
33
|
+
];
|
|
34
|
+
const images = result.objects.filter((o) => isPreviewableImage(o.objectKey));
|
|
35
|
+
const others = result.objects.filter((o) => !isPreviewableImage(o.objectKey));
|
|
36
|
+
if (images.length > 0) {
|
|
37
|
+
lines.push(`## 图片预览`, ``);
|
|
38
|
+
for (const obj of images.slice(0, previewMax)) {
|
|
39
|
+
const name = basename(obj.objectKey);
|
|
40
|
+
lines.push(`### ${name}`, ``, ``, ``, `- **地址**: ${obj.objectUrl}`, `- **Key**: \`${obj.objectKey}\``, `- **大小**: ${formatBytes(obj.size)}`, `- **更新**: ${obj.lastModified}`, ``);
|
|
41
|
+
}
|
|
42
|
+
if (images.length > previewMax) {
|
|
43
|
+
lines.push(`_… 另有 ${images.length - previewMax} 张图片未展示预览_`, ``);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const listOthers = images.length > 0 ? others : result.objects;
|
|
47
|
+
if (listOthers.length > 0) {
|
|
48
|
+
lines.push(`## 文件地址`, ``);
|
|
49
|
+
lines.push(`| 文件 | 大小 | 地址 |`, `| --- | --- | --- |`);
|
|
50
|
+
for (const obj of listOthers) {
|
|
51
|
+
const name = basename(obj.objectKey);
|
|
52
|
+
lines.push(`| ${name} | ${formatBytes(obj.size)} | ${obj.objectUrl} |`);
|
|
53
|
+
}
|
|
54
|
+
lines.push(``);
|
|
55
|
+
}
|
|
56
|
+
return lines.join("\n");
|
|
57
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AppConfig, ListObjectsResult } from "./types.js";
|
|
2
|
+
export interface ListObjectsOptions {
|
|
3
|
+
subdir?: string;
|
|
4
|
+
maxKeys?: number;
|
|
5
|
+
imagesOnly?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function listObjects(config: AppConfig, options?: ListObjectsOptions): Promise<ListObjectsResult>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { formatPrefixDisplay } from "./config.js";
|
|
2
|
+
import { filterObjects } from "./list-format.js";
|
|
3
|
+
import { createOssClient, publicObjectUrl } from "./oss-client.js";
|
|
4
|
+
import { validateSubdir } from "./validators.js";
|
|
5
|
+
function buildListPrefix(config, subdir) {
|
|
6
|
+
const sub = validateSubdir(subdir);
|
|
7
|
+
if (!sub)
|
|
8
|
+
return config.prefix;
|
|
9
|
+
const normalized = sub.endsWith("/") ? sub : `${sub}/`;
|
|
10
|
+
return `${config.prefix}${normalized}`;
|
|
11
|
+
}
|
|
12
|
+
export async function listObjects(config, options) {
|
|
13
|
+
const prefix = buildListPrefix(config, options?.subdir);
|
|
14
|
+
const limit = Math.min(Math.max(options?.maxKeys ?? 1000, 1), 1000);
|
|
15
|
+
const client = createOssClient(config);
|
|
16
|
+
const objects = [];
|
|
17
|
+
let marker;
|
|
18
|
+
let truncated = false;
|
|
19
|
+
do {
|
|
20
|
+
const page = await client.list({
|
|
21
|
+
prefix,
|
|
22
|
+
marker,
|
|
23
|
+
"max-keys": Math.min(limit - objects.length, 1000),
|
|
24
|
+
}, {});
|
|
25
|
+
for (const obj of page.objects ?? []) {
|
|
26
|
+
if (obj.name.endsWith("/"))
|
|
27
|
+
continue;
|
|
28
|
+
objects.push({
|
|
29
|
+
objectKey: obj.name,
|
|
30
|
+
size: Number(obj.size ?? 0),
|
|
31
|
+
lastModified: String(obj.lastModified ?? ""),
|
|
32
|
+
objectUrl: publicObjectUrl(config, obj.name),
|
|
33
|
+
});
|
|
34
|
+
if (objects.length >= limit)
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
truncated = Boolean(page.isTruncated) || objects.length >= limit;
|
|
38
|
+
marker = page.isTruncated ? page.nextMarker : undefined;
|
|
39
|
+
} while (marker && objects.length < limit);
|
|
40
|
+
const filtered = filterObjects(objects, Boolean(options?.imagesOnly));
|
|
41
|
+
return {
|
|
42
|
+
prefix: formatPrefixDisplay(prefix),
|
|
43
|
+
count: filtered.length,
|
|
44
|
+
objects: filtered,
|
|
45
|
+
truncated,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** MCP tool annotation hints for clients (non-authoritative). */
|
|
2
|
+
export declare const MCP_READ_ONLY: {
|
|
3
|
+
readonly readOnlyHint: true;
|
|
4
|
+
readonly openWorldHint: true;
|
|
5
|
+
};
|
|
6
|
+
export declare const MCP_WRITE: {
|
|
7
|
+
readonly readOnlyHint: false;
|
|
8
|
+
readonly destructiveHint: false;
|
|
9
|
+
readonly openWorldHint: true;
|
|
10
|
+
};
|
|
11
|
+
export declare const MCP_DESTRUCTIVE: {
|
|
12
|
+
readonly readOnlyHint: false;
|
|
13
|
+
readonly destructiveHint: true;
|
|
14
|
+
readonly idempotentHint: true;
|
|
15
|
+
readonly openWorldHint: true;
|
|
16
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** MCP tool annotation hints for clients (non-authoritative). */
|
|
2
|
+
export const MCP_READ_ONLY = {
|
|
3
|
+
readOnlyHint: true,
|
|
4
|
+
openWorldHint: true,
|
|
5
|
+
};
|
|
6
|
+
export const MCP_WRITE = {
|
|
7
|
+
readOnlyHint: false,
|
|
8
|
+
destructiveHint: false,
|
|
9
|
+
openWorldHint: true,
|
|
10
|
+
};
|
|
11
|
+
export const MCP_DESTRUCTIVE = {
|
|
12
|
+
readOnlyHint: false,
|
|
13
|
+
destructiveHint: true,
|
|
14
|
+
idempotentHint: true,
|
|
15
|
+
openWorldHint: true,
|
|
16
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
type TextContent = {
|
|
2
|
+
type: "text";
|
|
3
|
+
text: string;
|
|
4
|
+
};
|
|
5
|
+
export type McpToolResult = {
|
|
6
|
+
content: TextContent[];
|
|
7
|
+
isError?: boolean;
|
|
8
|
+
};
|
|
9
|
+
export declare function mcpJson(data: unknown): McpToolResult;
|
|
10
|
+
export declare function mcpText(text: string): McpToolResult;
|
|
11
|
+
export declare function mcpError(message: string, nextStep?: string): McpToolResult;
|
|
12
|
+
export declare function mcpErrorFromUnknown(err: unknown): McpToolResult;
|
|
13
|
+
export declare function runMcpTool<T extends McpToolResult>(fn: () => Promise<T>): Promise<McpToolResult>;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export function mcpJson(data) {
|
|
2
|
+
return {
|
|
3
|
+
content: [
|
|
4
|
+
{
|
|
5
|
+
type: "text",
|
|
6
|
+
text: JSON.stringify(data, null, 2),
|
|
7
|
+
},
|
|
8
|
+
],
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export function mcpText(text) {
|
|
12
|
+
return {
|
|
13
|
+
content: [{ type: "text", text }],
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function mcpError(message, nextStep) {
|
|
17
|
+
const lines = [message];
|
|
18
|
+
if (nextStep) {
|
|
19
|
+
lines.push("", `下一步:${nextStep}`);
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
23
|
+
isError: true,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export function mcpErrorFromUnknown(err) {
|
|
27
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
28
|
+
let nextStep;
|
|
29
|
+
if (/not configured|未配置/i.test(message)) {
|
|
30
|
+
nextStep = "在终端运行 npx ossput setup,然后重启 IDE";
|
|
31
|
+
}
|
|
32
|
+
else if (/allowDelete|confirm: true|refusing to delete/i.test(message)) {
|
|
33
|
+
nextStep =
|
|
34
|
+
"删除需在 profile 设 allowDelete:true,并向用户确认后再传 confirm:true 与完整 objectKey";
|
|
35
|
+
}
|
|
36
|
+
else if (/upload failed|OSS upload/i.test(message)) {
|
|
37
|
+
nextStep = "检查网络、RAM 权限与 presigned URL 是否过期";
|
|
38
|
+
}
|
|
39
|
+
else if (/fetch|Node\.js 18/i.test(message)) {
|
|
40
|
+
nextStep = "使用 Node.js 18 或更高版本";
|
|
41
|
+
}
|
|
42
|
+
else if (/extension|subdir|refusing/i.test(message)) {
|
|
43
|
+
nextStep = "检查文件类型、subdir 命名或是否误选敏感文件";
|
|
44
|
+
}
|
|
45
|
+
else if (/AccessDenied|InvalidAccessKeyId|SignatureDoesNotMatch/i.test(message)) {
|
|
46
|
+
nextStep = "检查 RAM 权限与 AccessKey,或运行 ossput doctor";
|
|
47
|
+
}
|
|
48
|
+
return mcpError(message, nextStep);
|
|
49
|
+
}
|
|
50
|
+
export async function runMcpTool(fn) {
|
|
51
|
+
try {
|
|
52
|
+
return await fn();
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
return mcpErrorFromUnknown(err);
|
|
56
|
+
}
|
|
57
|
+
}
|
package/dist/mcp.d.ts
ADDED