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.
Files changed (72) hide show
  1. package/.cursor/skills/ossput/SKILL.md +87 -0
  2. package/.cursor/skills/ossput/examples.md +59 -0
  3. package/.cursor/skills/ossput/reference.md +78 -0
  4. package/.ossput.json.example +3 -0
  5. package/AGENTS.md +16 -0
  6. package/CHANGELOG.md +25 -0
  7. package/LICENSE +21 -0
  8. package/README.md +173 -0
  9. package/config.index.example.json +8 -0
  10. package/dist/batch-upload.d.ts +28 -0
  11. package/dist/batch-upload.js +34 -0
  12. package/dist/cli.d.ts +1 -0
  13. package/dist/cli.js +253 -0
  14. package/dist/config-profiles.d.ts +45 -0
  15. package/dist/config-profiles.js +282 -0
  16. package/dist/config.d.ts +14 -0
  17. package/dist/config.js +81 -0
  18. package/dist/constants.d.ts +5 -0
  19. package/dist/constants.js +5 -0
  20. package/dist/delete-object.d.ts +6 -0
  21. package/dist/delete-object.js +26 -0
  22. package/dist/doctor.d.ts +9 -0
  23. package/dist/doctor.js +128 -0
  24. package/dist/index.d.ts +2 -0
  25. package/dist/index.js +21 -0
  26. package/dist/key-builder.d.ts +1 -0
  27. package/dist/key-builder.js +13 -0
  28. package/dist/list-directories.d.ts +11 -0
  29. package/dist/list-directories.js +64 -0
  30. package/dist/list-format.d.ts +6 -0
  31. package/dist/list-format.js +57 -0
  32. package/dist/list-objects.d.ts +7 -0
  33. package/dist/list-objects.js +47 -0
  34. package/dist/mcp-annotations.d.ts +16 -0
  35. package/dist/mcp-annotations.js +16 -0
  36. package/dist/mcp-result.d.ts +14 -0
  37. package/dist/mcp-result.js +57 -0
  38. package/dist/mcp.d.ts +4 -0
  39. package/dist/mcp.js +244 -0
  40. package/dist/object-key.d.ts +10 -0
  41. package/dist/object-key.js +46 -0
  42. package/dist/oss-client.d.ts +4 -0
  43. package/dist/oss-client.js +24 -0
  44. package/dist/profile-cli.d.ts +16 -0
  45. package/dist/profile-cli.js +191 -0
  46. package/dist/setup/connectivity.d.ts +2 -0
  47. package/dist/setup/connectivity.js +5 -0
  48. package/dist/setup/logo.d.ts +1 -0
  49. package/dist/setup/logo.js +30 -0
  50. package/dist/setup/mcp-registry.d.ts +17 -0
  51. package/dist/setup/mcp-registry.js +128 -0
  52. package/dist/setup/prompts.d.ts +24 -0
  53. package/dist/setup/prompts.js +410 -0
  54. package/dist/setup/run-setup.d.ts +9 -0
  55. package/dist/setup/run-setup.js +156 -0
  56. package/dist/setup/skill-install.d.ts +19 -0
  57. package/dist/setup/skill-install.js +85 -0
  58. package/dist/setup/ui.d.ts +49 -0
  59. package/dist/setup/ui.js +164 -0
  60. package/dist/setup/write-config.d.ts +5 -0
  61. package/dist/setup/write-config.js +5 -0
  62. package/dist/skill-cli.d.ts +6 -0
  63. package/dist/skill-cli.js +34 -0
  64. package/dist/types.d.ts +82 -0
  65. package/dist/types.js +1 -0
  66. package/dist/upload-pipeline.d.ts +7 -0
  67. package/dist/upload-pipeline.js +96 -0
  68. package/dist/validators.d.ts +10 -0
  69. package/dist/validators.js +77 -0
  70. package/docs/ram-policy.example.json +31 -0
  71. package/package.json +59 -0
  72. package/profiles.example/default.json +21 -0
package/dist/mcp.js ADDED
@@ -0,0 +1,244 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { z } from "zod";
4
+ import { formatPrefixDisplay } from "./config.js";
5
+ import { configExists, findProjectOssputJson, listProfileNames, loadConfigWithProfile, INDEX_CONFIG_PATH, } from "./config-profiles.js";
6
+ import { listConfiguredClients } from "./setup/mcp-registry.js";
7
+ import { formatDirectoriesAsMarkdown, listDirectories, } from "./list-directories.js";
8
+ import { formatListAsMarkdown } from "./list-format.js";
9
+ import { listObjects } from "./list-objects.js";
10
+ import { confirmUpload, prepareUpload, uploadFile, } from "./upload-pipeline.js";
11
+ import { batchUploadFile, MAX_BATCH_FILES } from "./batch-upload.js";
12
+ import { deleteObject } from "./delete-object.js";
13
+ import { inferContentType } from "./validators.js";
14
+ import { getPackageVersion } from "./setup/mcp-registry.js";
15
+ import { mcpJson, mcpText, runMcpTool } from "./mcp-result.js";
16
+ import { MCP_DESTRUCTIVE, MCP_READ_ONLY, MCP_WRITE, } from "./mcp-annotations.js";
17
+ import { NPX_DOCTOR, NPX_SETUP } from "./constants.js";
18
+ const profileField = z
19
+ .string()
20
+ .optional()
21
+ .describe("Named profile; default from .ossput.json or global default");
22
+ const INSTRUCTIONS = `You are connected to ossput — Aliyun OSS direct upload (presigned PUT via fetch).
23
+
24
+ When the user wants to upload files to OSS / 阿里云:
25
+ 1. If not configured, ask them to run in terminal: ${NPX_SETUP} (do NOT ask for secrets in chat).
26
+ 2. Call list_profiles or read .ossput.json for activeProfile; optional profile param overrides.
27
+ 3. Prefer upload_file(localPath, subdir?). Object keys are: {prefix}{subdir}{YYYY}/{MM}/{uuid}.ext — not the original filename.
28
+ 4. list_objects(format=markdown) for image previews; list_directories for folder prefixes.
29
+ 5. Never upload .env, keys, or files over 100MB. Return objectUrl from tool results to the user.
30
+ 6. delete_object: ONLY after user confirms exact objectKey; requires profile allowDelete:true and confirm:true.
31
+ 7. Multiple local files: prefer batch_upload_file (max ${MAX_BATCH_FILES} per call) over many upload_file calls.`;
32
+ async function requireResolved(profile) {
33
+ if (!(await configExists())) {
34
+ throw new Error(`ossput is not configured. Run in terminal: ${NPX_SETUP}`);
35
+ }
36
+ return loadConfigWithProfile({ profile });
37
+ }
38
+ export async function startMcpServer() {
39
+ const version = await getPackageVersion();
40
+ const server = new McpServer({
41
+ name: "ossput",
42
+ version,
43
+ }, {
44
+ instructions: INSTRUCTIONS,
45
+ });
46
+ server.registerTool("list_profiles", {
47
+ description: "List configured OSS profiles and which profile is active in the current workspace.",
48
+ inputSchema: z.object({}),
49
+ annotations: MCP_READ_ONLY,
50
+ }, async () => runMcpTool(async () => {
51
+ const rows = await listProfileNames();
52
+ let active = null;
53
+ if (rows.length > 0) {
54
+ try {
55
+ active = await loadConfigWithProfile();
56
+ }
57
+ catch {
58
+ active = null;
59
+ }
60
+ }
61
+ const projectFile = await findProjectOssputJson();
62
+ return mcpJson({
63
+ profiles: rows,
64
+ activeProfile: active?.resolved.name ?? null,
65
+ bindingSource: active?.resolved.source ?? null,
66
+ projectFile,
67
+ });
68
+ }));
69
+ server.registerTool("get_setup_status", {
70
+ description: "Check ossput configuration, active profile, and MCP client registration.",
71
+ inputSchema: z.object({
72
+ profile: profileField,
73
+ }),
74
+ annotations: MCP_READ_ONLY,
75
+ }, async ({ profile }) => runMcpTool(async () => {
76
+ const configured = await configExists();
77
+ const clientsConfigured = configured
78
+ ? await listConfiguredClients()
79
+ : [];
80
+ let body = {
81
+ configured,
82
+ indexPath: INDEX_CONFIG_PATH,
83
+ clientsConfigured,
84
+ setupCommand: NPX_SETUP,
85
+ doctorCommand: NPX_DOCTOR,
86
+ };
87
+ if (configured) {
88
+ const { config, resolved } = await loadConfigWithProfile({ profile });
89
+ body = {
90
+ ...body,
91
+ activeProfile: resolved.name,
92
+ bindingSource: resolved.source,
93
+ projectFile: resolved.projectFile ?? null,
94
+ prefix: formatPrefixDisplay(config.prefix),
95
+ bucket: config.bucket,
96
+ region: config.region,
97
+ publicBaseUrl: config.publicBaseUrl ?? null,
98
+ allowDelete: config.allowDelete === true,
99
+ };
100
+ }
101
+ return mcpJson(body);
102
+ }));
103
+ server.registerTool("list_directories", {
104
+ description: "List subdirectory prefixes under configured prefix. format=markdown for tree view.",
105
+ inputSchema: z.object({
106
+ profile: profileField,
107
+ subdir: z.string().optional(),
108
+ format: z.enum(["json", "markdown"]).optional(),
109
+ }),
110
+ annotations: MCP_READ_ONLY,
111
+ }, async ({ profile, subdir, format }) => runMcpTool(async () => {
112
+ const { config } = await requireResolved(profile);
113
+ const result = await listDirectories(config, { subdir });
114
+ if (format === "markdown") {
115
+ return mcpText(formatDirectoriesAsMarkdown(result));
116
+ }
117
+ return mcpJson(result);
118
+ }));
119
+ server.registerTool("list_objects", {
120
+ description: "List OSS objects. format=markdown for image previews; imagesOnly to filter images.",
121
+ inputSchema: z.object({
122
+ profile: profileField,
123
+ subdir: z.string().optional(),
124
+ maxKeys: z.number().int().min(1).max(1000).optional(),
125
+ format: z.enum(["json", "markdown"]).optional(),
126
+ imagesOnly: z.boolean().optional(),
127
+ previewMax: z.number().int().min(1).max(50).optional(),
128
+ }),
129
+ annotations: MCP_READ_ONLY,
130
+ }, async ({ profile, subdir, maxKeys, format, imagesOnly, previewMax }) => runMcpTool(async () => {
131
+ const { config } = await requireResolved(profile);
132
+ const result = await listObjects(config, {
133
+ subdir,
134
+ maxKeys,
135
+ imagesOnly,
136
+ });
137
+ if (format === "markdown") {
138
+ return mcpText(formatListAsMarkdown(result, { previewMax }));
139
+ }
140
+ return mcpJson(result);
141
+ }));
142
+ server.registerTool("upload_file", {
143
+ description: "Upload a local file to Aliyun OSS (presigned PUT). Returns objectKey and objectUrl.",
144
+ inputSchema: z.object({
145
+ profile: profileField,
146
+ localPath: z.string(),
147
+ subdir: z.string().optional(),
148
+ contentType: z.string().optional(),
149
+ }),
150
+ annotations: MCP_WRITE,
151
+ }, async ({ profile, localPath, subdir, contentType }) => runMcpTool(async () => {
152
+ const { config } = await requireResolved(profile);
153
+ const result = await uploadFile(config, localPath, subdir, contentType);
154
+ return mcpJson(result);
155
+ }));
156
+ server.registerTool("batch_upload_file", {
157
+ description: `Batch upload local files (max ${MAX_BATCH_FILES} per call). Same rules as upload_file. Returns per-file success or error.`,
158
+ inputSchema: z.object({
159
+ profile: profileField,
160
+ localPaths: z.array(z.string()).min(1).max(MAX_BATCH_FILES),
161
+ subdir: z.string().optional(),
162
+ contentType: z.string().optional(),
163
+ stopOnError: z.boolean().optional(),
164
+ }),
165
+ annotations: MCP_WRITE,
166
+ }, async ({ profile, localPaths, subdir, contentType, stopOnError }) => runMcpTool(async () => {
167
+ const { config } = await requireResolved(profile);
168
+ const result = await batchUploadFile(config, localPaths, {
169
+ subdir,
170
+ contentType,
171
+ stopOnError: stopOnError ?? false,
172
+ });
173
+ return mcpJson(result);
174
+ }));
175
+ server.registerTool("delete_object", {
176
+ description: "Delete one OSS object. Destructive: requires profile allowDelete:true, objectKey under profile prefix, and confirm:true after user approval.",
177
+ inputSchema: z.object({
178
+ profile: profileField,
179
+ objectKey: z.string().describe("Exact key from list_objects or upload result"),
180
+ confirm: z
181
+ .literal(true)
182
+ .describe("Must be true after the user explicitly confirms deletion"),
183
+ }),
184
+ annotations: MCP_DESTRUCTIVE,
185
+ }, async ({ profile, objectKey, confirm }) => runMcpTool(async () => {
186
+ const { config } = await requireResolved(profile);
187
+ const result = await deleteObject(config, objectKey, { confirm });
188
+ return mcpJson(result);
189
+ }));
190
+ server.registerTool("prepare_upload", {
191
+ description: "Generate presigned PUT URL and objectKey.",
192
+ inputSchema: z.object({
193
+ profile: profileField,
194
+ filename: z.string(),
195
+ contentType: z.string(),
196
+ subdir: z.string().optional(),
197
+ overwrite: z.boolean().optional(),
198
+ }),
199
+ annotations: MCP_WRITE,
200
+ }, async ({ profile, filename, contentType, subdir, overwrite }) => runMcpTool(async () => {
201
+ const { config } = await requireResolved(profile);
202
+ const result = await prepareUpload(config, filename, contentType, subdir, overwrite ?? false);
203
+ return mcpJson(result);
204
+ }));
205
+ server.registerTool("confirm_upload", {
206
+ description: "Verify object exists on OSS after upload.",
207
+ inputSchema: z.object({
208
+ profile: profileField,
209
+ objectKey: z.string(),
210
+ expectedSizeBytes: z.number().optional(),
211
+ }),
212
+ annotations: MCP_READ_ONLY,
213
+ }, async ({ profile, objectKey, expectedSizeBytes }) => runMcpTool(async () => {
214
+ const { config } = await requireResolved(profile);
215
+ const result = await confirmUpload(config, objectKey, expectedSizeBytes);
216
+ return mcpJson(result);
217
+ }));
218
+ const transport = new StdioServerTransport();
219
+ await server.connect(transport);
220
+ }
221
+ export async function getSetupStatusJson(profile) {
222
+ const configured = await configExists();
223
+ const base = {
224
+ configured,
225
+ indexPath: INDEX_CONFIG_PATH,
226
+ clientsConfigured: configured ? await listConfiguredClients() : [],
227
+ setupCommand: NPX_SETUP,
228
+ doctorCommand: NPX_DOCTOR,
229
+ };
230
+ if (!configured)
231
+ return base;
232
+ const { config, resolved } = await loadConfigWithProfile({ profile });
233
+ return {
234
+ ...base,
235
+ activeProfile: resolved.name,
236
+ bindingSource: resolved.source,
237
+ projectFile: resolved.projectFile ?? null,
238
+ prefix: formatPrefixDisplay(config.prefix),
239
+ bucket: config.bucket,
240
+ region: config.region,
241
+ publicBaseUrl: config.publicBaseUrl ?? null,
242
+ };
243
+ }
244
+ export { inferContentType };
@@ -0,0 +1,10 @@
1
+ import type { AppConfig } from "./types.js";
2
+ /** 规范化 OSS 对象 Key(无前导斜杠) */
3
+ export declare function normalizeObjectKey(objectKey: string): string;
4
+ /**
5
+ * 删除前校验 Key 落在当前 profile 允许范围内。
6
+ * - 有 prefix:Key 必须以 prefix 开头且长于 prefix
7
+ * - 无 prefix(桶根):须 allowDelete,且至少 subdir/file 两段路径
8
+ */
9
+ export declare function assertObjectKeyDeletable(config: AppConfig, objectKey: string): string;
10
+ export declare function assertDeleteEnabled(config: AppConfig): void;
@@ -0,0 +1,46 @@
1
+ import { formatPrefixDisplay } from "./config.js";
2
+ /** 规范化 OSS 对象 Key(无前导斜杠) */
3
+ export function normalizeObjectKey(objectKey) {
4
+ const key = objectKey.trim().replace(/^\/+/, "");
5
+ if (!key) {
6
+ throw new Error("objectKey cannot be empty");
7
+ }
8
+ if (key.includes("..")) {
9
+ throw new Error("objectKey must not contain '..'");
10
+ }
11
+ if (key.endsWith("/")) {
12
+ throw new Error("objectKey must point to a file, not a directory prefix");
13
+ }
14
+ return key;
15
+ }
16
+ /**
17
+ * 删除前校验 Key 落在当前 profile 允许范围内。
18
+ * - 有 prefix:Key 必须以 prefix 开头且长于 prefix
19
+ * - 无 prefix(桶根):须 allowDelete,且至少 subdir/file 两段路径
20
+ */
21
+ export function assertObjectKeyDeletable(config, objectKey) {
22
+ const key = normalizeObjectKey(objectKey);
23
+ const prefix = config.prefix;
24
+ if (prefix) {
25
+ if (!key.startsWith(prefix)) {
26
+ throw new Error(`objectKey must start with profile prefix '${formatPrefixDisplay(prefix)}'`);
27
+ }
28
+ if (key.length <= prefix.length) {
29
+ throw new Error("objectKey must name a file under the profile prefix");
30
+ }
31
+ return key;
32
+ }
33
+ if (config.allowDelete !== true) {
34
+ throw new Error("delete is disabled when profile prefix is bucket root; set allowDelete: true in profile JSON only if you accept the risk");
35
+ }
36
+ const segments = key.split("/").filter(Boolean);
37
+ if (segments.length < 2) {
38
+ throw new Error("with bucket-root prefix, objectKey must be at least subdir/filename (2 path segments)");
39
+ }
40
+ return key;
41
+ }
42
+ export function assertDeleteEnabled(config) {
43
+ if (config.allowDelete !== true) {
44
+ throw new Error("delete is disabled for this profile; set allowDelete: true in ~/.config/ossput/profiles/<name>.json");
45
+ }
46
+ }
@@ -0,0 +1,4 @@
1
+ import OSS from "ali-oss";
2
+ import type { AppConfig } from "./types.js";
3
+ export declare function createOssClient(config: AppConfig): OSS;
4
+ export declare function publicObjectUrl(config: AppConfig, objectKey: string): string;
@@ -0,0 +1,24 @@
1
+ import OSS from "ali-oss";
2
+ export function createOssClient(config) {
3
+ const opts = {
4
+ region: config.region,
5
+ accessKeyId: config.accessKeyId,
6
+ accessKeySecret: config.accessKeySecret,
7
+ bucket: config.bucket,
8
+ secure: true,
9
+ };
10
+ if (config.endpoint) {
11
+ opts.endpoint = config.endpoint;
12
+ }
13
+ return new OSS(opts);
14
+ }
15
+ export function publicObjectUrl(config, objectKey) {
16
+ if (config.publicBaseUrl) {
17
+ return `${config.publicBaseUrl}/${objectKey}`;
18
+ }
19
+ if (config.endpoint) {
20
+ const host = config.endpoint.replace(/^https?:\/\//, "");
21
+ return `https://${config.bucket}.${host}/${objectKey}`;
22
+ }
23
+ return `https://${config.bucket}.${config.region}.aliyuncs.com/${objectKey}`;
24
+ }
@@ -0,0 +1,16 @@
1
+ export declare function runProfileList(): Promise<void>;
2
+ export declare function runProfileShow(name?: string): Promise<void>;
3
+ export interface ProfileAddOptions {
4
+ name?: string;
5
+ nonInteractive?: boolean;
6
+ configPath?: string;
7
+ skipConnectivity?: boolean;
8
+ setDefault?: boolean;
9
+ bindProject?: boolean;
10
+ label?: string;
11
+ }
12
+ export declare function runProfileAdd(options?: ProfileAddOptions): Promise<void>;
13
+ export declare function runProfileUse(name: string, cwd?: string): Promise<void>;
14
+ export declare function runProfileDefault(name: string): Promise<void>;
15
+ export declare function runProfileRm(name: string): Promise<void>;
16
+ export declare function runProfileCommand(sub: string, rest: string[], flags: Record<string, string | boolean>): Promise<number>;
@@ -0,0 +1,191 @@
1
+ import { confirm } from "@inquirer/prompts";
2
+ import { formatPrefixDisplay, listProfileNames, loadConfigWithProfile, loadIndex, profileExists, profileFilePath, removeProfile, saveProfile, setDefaultProfile, validateProfileName, writeProjectBinding, } from "./config-profiles.js";
3
+ import { maskSecret, printHint, ui } from "./setup/ui.js";
4
+ import { promptOssConfig, promptProfileIdentity, } from "./setup/prompts.js";
5
+ import { testConnectivity } from "./setup/connectivity.js";
6
+ import { withSpinner } from "./setup/ui.js";
7
+ export async function runProfileList() {
8
+ const rows = await listProfileNames();
9
+ if (rows.length === 0) {
10
+ console.log("No profiles. Run: ossput setup or ossput profile add <name>");
11
+ return;
12
+ }
13
+ for (const row of rows) {
14
+ const mark = row.isDefault ? "*" : " ";
15
+ const label = row.label ? ` ${row.label}` : "";
16
+ console.log(`${mark} ${row.name}${label}`);
17
+ }
18
+ }
19
+ export async function runProfileShow(name) {
20
+ const resolved = name
21
+ ? { name, source: "arg" }
22
+ : (await loadConfigWithProfile()).resolved;
23
+ const target = name ?? resolved.name;
24
+ const { config } = await loadConfigWithProfile({ profile: target });
25
+ const index = await loadIndex();
26
+ const label = index?.profiles[target]?.label;
27
+ console.log(JSON.stringify({
28
+ profile: target,
29
+ label: label ?? null,
30
+ region: config.region,
31
+ bucket: config.bucket,
32
+ prefix: formatPrefixDisplay(config.prefix),
33
+ accessKeyId: maskSecret(config.accessKeyId, 6),
34
+ endpoint: config.endpoint,
35
+ presignExpiresSec: config.presignExpiresSec,
36
+ allowedExtensions: config.allowedExtensions,
37
+ file: profileFilePath(target),
38
+ }, null, 2));
39
+ }
40
+ export async function runProfileAdd(options = {}) {
41
+ let name = options.name?.trim();
42
+ let label = options.label;
43
+ if (!options.nonInteractive) {
44
+ const identity = await promptProfileIdentity({
45
+ name,
46
+ requireNew: true,
47
+ });
48
+ name = identity.name;
49
+ label = identity.label ?? label;
50
+ }
51
+ else {
52
+ if (!name)
53
+ name = "default";
54
+ const valid = validateProfileName(name);
55
+ if (valid !== true)
56
+ throw new Error(valid);
57
+ if (await profileExists(name)) {
58
+ throw new Error(`Profile "${name}" already exists`);
59
+ }
60
+ }
61
+ if (!name)
62
+ throw new Error("Profile name is required");
63
+ let config;
64
+ if (options.nonInteractive && options.configPath) {
65
+ const { readFile } = await import("node:fs/promises");
66
+ const { parseConfig } = await import("./config.js");
67
+ config = parseConfig(JSON.parse(await readFile(options.configPath, "utf8")));
68
+ }
69
+ else if (options.nonInteractive) {
70
+ throw new Error("--non-interactive requires --config <path>");
71
+ }
72
+ else {
73
+ config = await promptOssConfig({ excludeProfile: name });
74
+ }
75
+ await saveProfile(name, config, {
76
+ label,
77
+ setDefault: options.setDefault,
78
+ });
79
+ console.log(ui.green(`✓ 已写入 ${profileFilePath(name)}`));
80
+ if (!options.skipConnectivity) {
81
+ try {
82
+ await withSpinner("正在检测 OSS 连通性", async () => {
83
+ await testConnectivity(config);
84
+ });
85
+ console.log(ui.green(" ✓ Bucket 连通性正常"));
86
+ }
87
+ catch (err) {
88
+ const msg = err instanceof Error ? err.message : String(err);
89
+ console.log(ui.yellow(` ⚠ 连通性检测未通过:${msg}`));
90
+ }
91
+ }
92
+ if (!options.nonInteractive) {
93
+ const makeDefault = options.setDefault ??
94
+ (await confirm({
95
+ message: "是否设为全局默认账号(defaultProfile)?",
96
+ default: !(await loadIndex()),
97
+ }));
98
+ if (makeDefault) {
99
+ await setDefaultProfile(name);
100
+ printHint(`默认账号已设为 ${ui.bold(name)}`);
101
+ }
102
+ const bind = options.bindProject ??
103
+ (await confirm({
104
+ message: `在当前目录写入 .ossput.json,绑定 profile「${name}」?`,
105
+ default: true,
106
+ }));
107
+ if (bind) {
108
+ const p = await writeProjectBinding(name);
109
+ console.log(ui.green(`✓ 已写入 ${p}`));
110
+ }
111
+ }
112
+ else {
113
+ if (options.setDefault)
114
+ await setDefaultProfile(name);
115
+ if (options.bindProject)
116
+ await writeProjectBinding(name);
117
+ }
118
+ }
119
+ export async function runProfileUse(name, cwd) {
120
+ const valid = validateProfileName(name);
121
+ if (valid !== true)
122
+ throw new Error(valid);
123
+ if (!(await profileExists(name))) {
124
+ throw new Error(`Profile "${name}" not found. Run: ossput profile list`);
125
+ }
126
+ const path = await writeProjectBinding(name, cwd);
127
+ console.log(ui.green(`✓ 已绑定 profile "${name}" → ${path}`));
128
+ }
129
+ export async function runProfileDefault(name) {
130
+ await setDefaultProfile(name);
131
+ console.log(ui.green(`✓ 默认 profile: ${name}`));
132
+ }
133
+ export async function runProfileRm(name) {
134
+ await removeProfile(name);
135
+ console.log(ui.green(`✓ 已删除 profile: ${name}`));
136
+ }
137
+ export async function runProfileCommand(sub, rest, flags) {
138
+ switch (sub) {
139
+ case "list":
140
+ case "ls":
141
+ await runProfileList();
142
+ return 0;
143
+ case "show":
144
+ await runProfileShow(rest[0]);
145
+ return 0;
146
+ case "add": {
147
+ await runProfileAdd({
148
+ name: rest[0],
149
+ nonInteractive: Boolean(flags.nonInteractive),
150
+ configPath: typeof flags.config === "string" ? flags.config : undefined,
151
+ skipConnectivity: Boolean(flags.skipConnectivity),
152
+ setDefault: flags.setDefault === true || flags.setDefault === "true",
153
+ bindProject: flags.bindProject === true || flags.bindProject === "true",
154
+ label: typeof flags.label === "string" ? flags.label : undefined,
155
+ });
156
+ return 0;
157
+ }
158
+ case "use":
159
+ if (!rest[0]) {
160
+ console.error("Usage: ossput profile use <name>");
161
+ return 1;
162
+ }
163
+ await runProfileUse(rest[0]);
164
+ return 0;
165
+ case "default":
166
+ if (!rest[0]) {
167
+ console.error("Usage: ossput profile default <name>");
168
+ return 1;
169
+ }
170
+ await runProfileDefault(rest[0]);
171
+ return 0;
172
+ case "rm":
173
+ case "remove":
174
+ if (!rest[0]) {
175
+ console.error("Usage: ossput profile rm <name>");
176
+ return 1;
177
+ }
178
+ await runProfileRm(rest[0]);
179
+ return 0;
180
+ default:
181
+ console.error(`Unknown profile command: ${sub}`);
182
+ console.log(`Usage:
183
+ ossput profile list
184
+ ossput profile add [name]
185
+ ossput profile show [name]
186
+ ossput profile use <name>
187
+ ossput profile default <name>
188
+ ossput profile rm <name>`);
189
+ return 1;
190
+ }
191
+ }
@@ -0,0 +1,2 @@
1
+ import type { AppConfig } from "../types.js";
2
+ export declare function testConnectivity(config: AppConfig): Promise<void>;
@@ -0,0 +1,5 @@
1
+ import { createOssClient } from "../oss-client.js";
2
+ export async function testConnectivity(config) {
3
+ const client = createOssClient(config);
4
+ await client.getBucketInfo(config.bucket);
5
+ }
@@ -0,0 +1 @@
1
+ export declare function printAsciiLogo(version?: string): void;
@@ -0,0 +1,30 @@
1
+ const supportsColor = process.stdout.isTTY && process.env.NO_COLOR !== "1" && process.env.TERM !== "dumb";
2
+ function style(codes, text) {
3
+ return supportsColor ? `\x1b[${codes}m${text}\x1b[0m` : text;
4
+ }
5
+ const front = (t) => style("1;96", t);
6
+ const dim = (t) => style("2", t);
7
+ /** ANSI Shadow 字体 OSSPUT(figlet) */
8
+ const LOGO_LINES = [
9
+ " ██████╗ ███████╗███████╗██████╗ ██╗ ██╗████████╗",
10
+ "██╔═══██╗██╔════╝██╔════╝██╔══██╗██║ ██║╚══██╔══╝",
11
+ "██║ ██║███████╗███████╗██████╔╝██║ ██║ ██║ ",
12
+ "██║ ██║╚════██║╚════██║██╔═══╝ ██║ ██║ ██║ ",
13
+ "╚██████╔╝███████║███████║██║ ╚██████╔╝ ██║ ",
14
+ " ╚═════╝ ╚══════╝╚══════╝╚═╝ ╚═════╝ ╚═╝ ",
15
+ ];
16
+ function pad(text, spaces) {
17
+ return " ".repeat(spaces) + text;
18
+ }
19
+ export function printAsciiLogo(version) {
20
+ console.log("");
21
+ for (const row of LOGO_LINES) {
22
+ console.log(pad(front(row), 2));
23
+ }
24
+ const tag = version != null
25
+ ? `── ossput v${version} · Aliyun OSS direct upload MCP ──`
26
+ : "── ossput · Aliyun OSS MCP ──";
27
+ console.log("");
28
+ console.log(pad(dim(tag), 2));
29
+ console.log("");
30
+ }
@@ -0,0 +1,17 @@
1
+ export interface McpClientDefinition {
2
+ id: string;
3
+ displayName: string;
4
+ resolveConfigPath: () => string | null;
5
+ }
6
+ export declare const MCP_CLIENTS: McpClientDefinition[];
7
+ export declare const MCP_SERVER_NAME = "ossput";
8
+ export declare function getPackageVersion(): Promise<string>;
9
+ /** 使用当前安装包内的 dist/index.js,避免未发布到 npm 时 npx 404 */
10
+ export declare function buildMcpServerEntry(_version: string): Record<string, unknown>;
11
+ export declare function readMcpEntry(configPath: string, serverName: string): Promise<unknown | undefined>;
12
+ export declare function readOurMcpEntry(configPath: string): Promise<unknown | undefined>;
13
+ export declare function mergeMcpConfig(configPath: string, serverName: string, entry: Record<string, unknown>, options?: {
14
+ force?: boolean;
15
+ }): Promise<"created" | "updated" | "skipped">;
16
+ export declare function listConfiguredClients(): Promise<string[]>;
17
+ export declare function formatMergeAction(action: string): string;