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
@@ -0,0 +1,128 @@
1
+ import { readFile, writeFile, copyFile, access, mkdir } from "node:fs/promises";
2
+ import { homedir, platform } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ import { constants } from "node:fs";
5
+ import { readFile as readPkg } from "node:fs/promises";
6
+ import { fileURLToPath } from "node:url";
7
+ function claudeDesktopPath() {
8
+ if (platform() === "darwin") {
9
+ return join(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
10
+ }
11
+ if (platform() === "win32") {
12
+ return join(process.env.APPDATA ?? join(homedir(), "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
13
+ }
14
+ return join(homedir(), ".config", "Claude", "claude_desktop_config.json");
15
+ }
16
+ export const MCP_CLIENTS = [
17
+ {
18
+ id: "cursor",
19
+ displayName: "Cursor",
20
+ resolveConfigPath: () => join(homedir(), ".cursor", "mcp.json"),
21
+ },
22
+ {
23
+ id: "claude-code",
24
+ displayName: "Claude Code",
25
+ resolveConfigPath: () => join(homedir(), ".claude.json"),
26
+ },
27
+ {
28
+ id: "claude-desktop",
29
+ displayName: "Claude Desktop",
30
+ resolveConfigPath: claudeDesktopPath,
31
+ },
32
+ ];
33
+ export const MCP_SERVER_NAME = "ossput";
34
+ export async function getPackageVersion() {
35
+ try {
36
+ const dir = dirname(fileURLToPath(import.meta.url));
37
+ const pkgPath = join(dir, "..", "..", "package.json");
38
+ const raw = JSON.parse(await readPkg(pkgPath, "utf8"));
39
+ return raw.version ?? "0.0.1";
40
+ }
41
+ catch {
42
+ return "0.0.1";
43
+ }
44
+ }
45
+ /** 使用当前安装包内的 dist/index.js,避免未发布到 npm 时 npx 404 */
46
+ export function buildMcpServerEntry(_version) {
47
+ const indexJs = join(dirname(fileURLToPath(import.meta.url)), "..", "index.js");
48
+ return {
49
+ command: "node",
50
+ args: [indexJs],
51
+ };
52
+ }
53
+ async function fileExists(path) {
54
+ try {
55
+ await access(path, constants.F_OK);
56
+ return true;
57
+ }
58
+ catch {
59
+ return false;
60
+ }
61
+ }
62
+ export async function readMcpEntry(configPath, serverName) {
63
+ if (!(await fileExists(configPath)))
64
+ return undefined;
65
+ const text = await readFile(configPath, "utf8");
66
+ const doc = JSON.parse(text);
67
+ return doc.mcpServers?.[serverName];
68
+ }
69
+ export async function readOurMcpEntry(configPath) {
70
+ return readMcpEntry(configPath, MCP_SERVER_NAME);
71
+ }
72
+ export async function mergeMcpConfig(configPath, serverName, entry, options) {
73
+ await mkdir(dirname(configPath), { recursive: true });
74
+ let doc = { mcpServers: {} };
75
+ const exists = await fileExists(configPath);
76
+ if (exists) {
77
+ const text = await readFile(configPath, "utf8");
78
+ try {
79
+ doc = JSON.parse(text);
80
+ }
81
+ catch {
82
+ throw new Error(`配置文件不是合法 JSON:${configPath}`);
83
+ }
84
+ await copyFile(configPath, `${configPath}.bak`);
85
+ }
86
+ if (!doc.mcpServers || typeof doc.mcpServers !== "object") {
87
+ doc.mcpServers = {};
88
+ }
89
+ if (doc.mcpServers[serverName] != null) {
90
+ const existing = JSON.stringify(doc.mcpServers[serverName]);
91
+ const next = JSON.stringify(entry);
92
+ if (existing === next)
93
+ return "skipped";
94
+ if (!options?.force) {
95
+ return "skipped";
96
+ }
97
+ }
98
+ doc.mcpServers[serverName] = entry;
99
+ await writeFile(configPath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
100
+ return exists ? "updated" : "created";
101
+ }
102
+ export async function listConfiguredClients() {
103
+ const found = [];
104
+ for (const client of MCP_CLIENTS) {
105
+ const p = client.resolveConfigPath();
106
+ if (!p)
107
+ continue;
108
+ try {
109
+ const text = await readFile(p, "utf8");
110
+ const doc = JSON.parse(text);
111
+ if (doc.mcpServers?.[MCP_SERVER_NAME]) {
112
+ found.push(client.id);
113
+ }
114
+ }
115
+ catch {
116
+ /* not configured */
117
+ }
118
+ }
119
+ return found;
120
+ }
121
+ const ACTION_LABEL = {
122
+ created: "新建",
123
+ updated: "已更新",
124
+ skipped: "无变化(已是最新)",
125
+ };
126
+ export function formatMergeAction(action) {
127
+ return ACTION_LABEL[action] ?? action;
128
+ }
@@ -0,0 +1,24 @@
1
+ import type { AppConfig } from "../types.js";
2
+ export declare function promptProfileIdentity(options?: {
3
+ name?: string;
4
+ requireNew?: boolean;
5
+ requireExists?: boolean;
6
+ }): Promise<{
7
+ name: string;
8
+ label?: string;
9
+ }>;
10
+ export declare function promptOssConfig(options?: {
11
+ stepOffset?: number;
12
+ stepTotal?: number;
13
+ excludeProfile?: string;
14
+ }): Promise<AppConfig>;
15
+ export declare function runInteractiveSetup(): Promise<{
16
+ profileName: string;
17
+ profileLabel?: string;
18
+ config: AppConfig;
19
+ clientIds: string[];
20
+ skippedOssConfig: boolean;
21
+ bindProject: boolean;
22
+ }>;
23
+ export declare function promptForClients(): Promise<string[]>;
24
+ export declare function promptOverwriteExisting(serverLabel: string): Promise<boolean>;
@@ -0,0 +1,410 @@
1
+ import { input, password, confirm, checkbox, select, } from "@inquirer/prompts";
2
+ import { DEFAULT_ALLOWED, normalizePrefix } from "../config.js";
3
+ import { configExists, indexExists, listProfileNames, loadConfigWithProfile, loadProfileFile, profileExists, validateProfileName, } from "../config-profiles.js";
4
+ import { MCP_CLIENTS } from "./mcp-registry.js";
5
+ import { inquirerTheme, line, printHint, printStep, maskSecret, printWelcome, ui, } from "./ui.js";
6
+ import { getPackageVersion } from "./mcp-registry.js";
7
+ const iq = { theme: inquirerTheme };
8
+ const OSS_REGIONS = [
9
+ { value: "oss-cn-hangzhou", label: "华东 1(杭州)", description: "oss-cn-hangzhou" },
10
+ { value: "oss-cn-shanghai", label: "华东 2(上海)", description: "oss-cn-shanghai" },
11
+ { value: "oss-cn-nanjing", label: "华东 5(南京)", description: "oss-cn-nanjing" },
12
+ { value: "oss-cn-beijing", label: "华北 2(北京)", description: "oss-cn-beijing" },
13
+ { value: "oss-cn-qingdao", label: "华北 1(青岛)", description: "oss-cn-qingdao" },
14
+ { value: "oss-cn-shenzhen", label: "华南 1(深圳)", description: "oss-cn-shenzhen" },
15
+ { value: "oss-cn-guangzhou", label: "华南 3(广州)", description: "oss-cn-guangzhou" },
16
+ { value: "oss-cn-chengdu", label: "西南 1(成都)", description: "oss-cn-chengdu" },
17
+ { value: "oss-cn-hongkong", label: "中国香港", description: "oss-cn-hongkong" },
18
+ { value: "__custom__", label: "其他地域(手动输入)", description: "自定义 region" },
19
+ ];
20
+ const EXTENSION_GROUPS = [
21
+ { name: "图片", extensions: ["png", "jpg", "jpeg", "gif", "webp"] },
22
+ {
23
+ name: "文档",
24
+ extensions: ["pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx"],
25
+ },
26
+ { name: "视频 / 压缩包", extensions: ["mp4", "mov", "zip"] },
27
+ ];
28
+ function validateBucket(name) {
29
+ const v = name.trim();
30
+ if (!v)
31
+ return "Bucket 名称不能为空";
32
+ if (v.length < 3 || v.length > 63)
33
+ return "长度应在 3~63 个字符之间";
34
+ if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$/.test(v)) {
35
+ return "只能包含小写字母、数字和连字符,且首尾不能为连字符";
36
+ }
37
+ return true;
38
+ }
39
+ function validateAccessKeyId(v) {
40
+ const s = v.trim();
41
+ if (!s)
42
+ return "AccessKey ID 不能为空";
43
+ if (s.length < 8)
44
+ return "格式似乎不正确,请检查是否复制完整";
45
+ return true;
46
+ }
47
+ async function askSetupMode() {
48
+ if (!(await indexExists()))
49
+ return "add";
50
+ const rows = await listProfileNames();
51
+ const names = rows.map((r) => r.name).join("、");
52
+ printHint(`已有 ${rows.length} 个账号:${names}`);
53
+ return select({
54
+ ...iq,
55
+ message: "接下来要做什么?",
56
+ choices: [
57
+ { value: "add", name: "新增一个账号" },
58
+ { value: "edit", name: "编辑已有账号" },
59
+ { value: "mcp-only", name: "只更新 MCP 注册(不修改任何 profile)" },
60
+ { value: "cancel", name: "退出" },
61
+ ],
62
+ });
63
+ }
64
+ export async function promptProfileIdentity(options) {
65
+ const name = await input({
66
+ ...iq,
67
+ message: "Profile 名称(小写,默认 default)",
68
+ default: options?.name ?? "default",
69
+ validate: async (v) => {
70
+ const base = validateProfileName(v);
71
+ if (base !== true)
72
+ return base;
73
+ const n = v.trim();
74
+ if (options?.requireNew && (await profileExists(n))) {
75
+ return `账号 ${n} 已存在,请换名或使用 ossput profile 编辑 ${n}`;
76
+ }
77
+ if (options?.requireExists && !(await profileExists(n))) {
78
+ return `账号 ${n} 不存在`;
79
+ }
80
+ return true;
81
+ },
82
+ });
83
+ const labelInput = await input({
84
+ ...iq,
85
+ message: "显示名称(可选,便于区分)",
86
+ default: "",
87
+ });
88
+ const label = labelInput.trim() || undefined;
89
+ return { name: name.trim(), label };
90
+ }
91
+ async function promptRegion() {
92
+ const picked = await select({
93
+ ...iq,
94
+ message: "Bucket 所在地域(Region)",
95
+ choices: OSS_REGIONS.map((r) => ({
96
+ value: r.value,
97
+ name: r.value === "__custom__"
98
+ ? r.label
99
+ : `${r.label} ${ui.dim(r.value)}`,
100
+ })),
101
+ pageSize: 10,
102
+ });
103
+ if (picked !== "__custom__")
104
+ return picked;
105
+ return input({
106
+ ...iq,
107
+ message: "请输入 Region(如 oss-cn-hangzhou)",
108
+ validate: (v) => {
109
+ const s = v.trim();
110
+ if (!s)
111
+ return "不能为空";
112
+ if (!/^oss-[a-z0-9-]+$/.test(s)) {
113
+ return "一般以 oss- 开头,例如 oss-cn-hangzhou";
114
+ }
115
+ return true;
116
+ },
117
+ });
118
+ }
119
+ async function promptManualCredentials() {
120
+ printHint("请使用 RAM 子账号,并仅授权目标 Bucket 前缀的 PutObject / HeadObject。");
121
+ const accessKeyId = await input({
122
+ ...iq,
123
+ message: "AccessKey ID",
124
+ validate: validateAccessKeyId,
125
+ });
126
+ const accessKeySecret = await password({
127
+ ...iq,
128
+ message: "AccessKey Secret",
129
+ mask: "*",
130
+ validate: (v) => (v.trim() ? true : "不能为空"),
131
+ });
132
+ return {
133
+ accessKeyId: accessKeyId.trim(),
134
+ accessKeySecret: accessKeySecret.trim(),
135
+ };
136
+ }
137
+ async function promptReuseOssputProfile(excludeProfile) {
138
+ const rows = (await listProfileNames()).filter((r) => r.name !== excludeProfile);
139
+ if (rows.length === 0) {
140
+ throw new Error("没有可复用的 profile");
141
+ }
142
+ const sourceName = rows.length === 1
143
+ ? rows[0].name
144
+ : await select({
145
+ ...iq,
146
+ message: "选择要复用凭证的 profile",
147
+ choices: rows.map((r) => ({
148
+ value: r.name,
149
+ name: r.label ? `${r.name} — ${r.label}` : r.name,
150
+ })),
151
+ });
152
+ const source = await loadProfileFile(sourceName);
153
+ printHint(`将复用 profile「${ui.bold(sourceName)}」的 AccessKey ${maskSecret(source.accessKeyId)}`);
154
+ return {
155
+ accessKeyId: source.accessKeyId,
156
+ accessKeySecret: source.accessKeySecret,
157
+ };
158
+ }
159
+ async function promptCredentials(options) {
160
+ const existingProfiles = (await listProfileNames()).filter((r) => r.name !== options?.excludeProfile);
161
+ if (existingProfiles.length === 0) {
162
+ return promptManualCredentials();
163
+ }
164
+ const hint = existingProfiles.length === 1
165
+ ? `(${existingProfiles[0].name})`
166
+ : `(${existingProfiles.length} 个可选)`;
167
+ const mode = await select({
168
+ ...iq,
169
+ message: "凭证来源",
170
+ choices: [
171
+ {
172
+ value: "reuse",
173
+ name: `复用已有 profile 的 AccessKey${hint}`,
174
+ },
175
+ {
176
+ value: "manual",
177
+ name: "手动输入 AccessKey(RAM 子账号推荐)",
178
+ },
179
+ ],
180
+ });
181
+ if (mode === "reuse") {
182
+ return promptReuseOssputProfile(options?.excludeProfile);
183
+ }
184
+ return promptManualCredentials();
185
+ }
186
+ async function promptAllowedExtensions() {
187
+ const useDefault = await confirm({
188
+ ...iq,
189
+ message: "使用默认可上传类型?(图片、Office、PDF、MP4、ZIP)",
190
+ default: true,
191
+ });
192
+ if (useDefault)
193
+ return [...DEFAULT_ALLOWED];
194
+ const choices = EXTENSION_GROUPS.flatMap((g) => g.extensions.map((ext) => ({
195
+ name: `${ext}`,
196
+ value: ext,
197
+ checked: DEFAULT_ALLOWED.includes(ext),
198
+ })));
199
+ const selected = await checkbox({
200
+ ...iq,
201
+ message: "允许上传的扩展名(空格切换,回车确认)",
202
+ choices,
203
+ required: true,
204
+ loop: false,
205
+ });
206
+ return selected.map((e) => e.toLowerCase());
207
+ }
208
+ export async function promptOssConfig(options) {
209
+ const base = options?.stepOffset ?? 1;
210
+ const total = options?.stepTotal ?? 4;
211
+ printStep(base, total, "Bucket 与路径");
212
+ const region = await promptRegion();
213
+ const bucket = await input({
214
+ ...iq,
215
+ message: "Bucket 名称",
216
+ validate: validateBucket,
217
+ });
218
+ let prefix = await input({
219
+ ...iq,
220
+ message: "对象前缀(/ 表示 Bucket 根目录,或填 uploads/)",
221
+ default: "/",
222
+ validate: (v) => {
223
+ const t = v.trim();
224
+ if (!t || t === "/")
225
+ return true;
226
+ if (t.includes(".."))
227
+ return "前缀不能包含 ..";
228
+ return true;
229
+ },
230
+ });
231
+ const normalized = normalizePrefix(prefix);
232
+ if (!normalized) {
233
+ printHint("将使用 Bucket 根目录(无前缀)");
234
+ }
235
+ else if (normalized !== prefix.trim() && !prefix.trim().endsWith("/")) {
236
+ printHint(`已自动规范前缀为:${ui.bold(normalized)}`);
237
+ }
238
+ prefix = normalized;
239
+ printStep(base + 1, total, "访问凭证");
240
+ const creds = await promptCredentials({
241
+ excludeProfile: options?.excludeProfile,
242
+ });
243
+ printStep(base + 2, total, "高级选项(可选)");
244
+ const configureAdvanced = await confirm({
245
+ ...iq,
246
+ message: "配置高级选项?(Endpoint、签名有效期、文件类型)",
247
+ default: false,
248
+ });
249
+ let endpoint = null;
250
+ let presignExpiresSec = 900;
251
+ let allowedExtensions = [...DEFAULT_ALLOWED];
252
+ if (configureAdvanced) {
253
+ const endpointInput = await input({
254
+ ...iq,
255
+ message: "自定义 Endpoint(内网 / VPC 时填写,公网留空)",
256
+ default: "",
257
+ });
258
+ endpoint = endpointInput.trim() || null;
259
+ const expiresChoice = await select({
260
+ ...iq,
261
+ message: "Presigned URL 有效期",
262
+ choices: [
263
+ { value: 900, name: "15 分钟(推荐)" },
264
+ { value: 1800, name: "30 分钟" },
265
+ { value: 3600, name: "1 小时" },
266
+ ],
267
+ });
268
+ presignExpiresSec = expiresChoice;
269
+ allowedExtensions = await promptAllowedExtensions();
270
+ }
271
+ printStep(base + 3, total, "公网与删除(可选)");
272
+ const publicBaseUrlInput = await input({
273
+ ...iq,
274
+ message: "CDN 公网域名 publicBaseUrl(可选,如 https://cdn.example.com)",
275
+ default: "",
276
+ });
277
+ const publicBaseUrl = publicBaseUrlInput.trim()
278
+ ? publicBaseUrlInput.trim().replace(/\/+$/, "")
279
+ : null;
280
+ const allowDelete = await confirm({
281
+ ...iq,
282
+ message: "允许 delete_object / ossput rm(allowDelete,默认关闭)?",
283
+ default: false,
284
+ });
285
+ return {
286
+ region: region.trim(),
287
+ bucket: bucket.trim(),
288
+ prefix,
289
+ accessKeyId: creds.accessKeyId,
290
+ accessKeySecret: creds.accessKeySecret,
291
+ presignExpiresSec,
292
+ allowedExtensions,
293
+ endpoint,
294
+ publicBaseUrl,
295
+ allowDelete: allowDelete === true,
296
+ };
297
+ }
298
+ export async function runInteractiveSetup() {
299
+ const version = await getPackageVersion();
300
+ printWelcome(version);
301
+ const mode = await askSetupMode();
302
+ if (mode === "cancel") {
303
+ console.log("");
304
+ line(ui.dim("已退出安装。"));
305
+ console.log("");
306
+ process.exit(0);
307
+ }
308
+ let profileName;
309
+ let profileLabel;
310
+ let config;
311
+ let skippedOssConfig = false;
312
+ if (mode === "mcp-only") {
313
+ if (!(await configExists())) {
314
+ throw new Error("尚无账号配置,请先新增账号");
315
+ }
316
+ const loaded = await loadConfigWithProfile();
317
+ profileName = loaded.resolved.name;
318
+ config = loaded.config;
319
+ skippedOssConfig = true;
320
+ printHint(`使用账号 ${ui.bold(profileName)},跳过 OSS 设置。`);
321
+ }
322
+ else if (mode === "edit") {
323
+ const rows = await listProfileNames();
324
+ profileName = await select({
325
+ ...iq,
326
+ message: "选择要编辑的账号",
327
+ choices: rows.map((r) => ({
328
+ value: r.name,
329
+ name: r.label ? `${r.name} — ${r.label}` : r.name,
330
+ })),
331
+ });
332
+ const row = rows.find((r) => r.name === profileName);
333
+ profileLabel = row?.label;
334
+ config = await promptOssConfig({
335
+ stepOffset: 2,
336
+ stepTotal: 4,
337
+ excludeProfile: profileName,
338
+ });
339
+ }
340
+ else {
341
+ printStep(1, 6, "账号标识");
342
+ const identity = await promptProfileIdentity({ requireNew: true });
343
+ profileName = identity.name;
344
+ profileLabel = identity.label;
345
+ config = await promptOssConfig({
346
+ stepOffset: 2,
347
+ stepTotal: 6,
348
+ excludeProfile: profileName,
349
+ });
350
+ }
351
+ let clientIds = [];
352
+ let bindProject = false;
353
+ if (mode === "mcp-only") {
354
+ printStep(1, 1, "MCP 客户端");
355
+ }
356
+ else {
357
+ const mcpStep = mode === "edit" ? 4 : 5;
358
+ const mcpTotal = mode === "edit" ? 4 : 6;
359
+ printStep(mcpStep, mcpTotal, "MCP 客户端");
360
+ }
361
+ printHint("将写入 mcp.json,不包含密钥。");
362
+ clientIds = await promptForClients();
363
+ if (!skippedOssConfig && mode !== "edit") {
364
+ bindProject = await confirm({
365
+ ...iq,
366
+ message: `在当前目录写入 .ossput.json,绑定 profile「${profileName}」?`,
367
+ default: true,
368
+ });
369
+ }
370
+ else if (!skippedOssConfig && mode === "edit") {
371
+ bindProject = await confirm({
372
+ ...iq,
373
+ message: `在当前目录写入 .ossput.json,绑定 profile「${profileName}」?`,
374
+ default: false,
375
+ });
376
+ }
377
+ return {
378
+ profileName,
379
+ profileLabel,
380
+ config,
381
+ clientIds,
382
+ skippedOssConfig,
383
+ bindProject,
384
+ };
385
+ }
386
+ export async function promptForClients() {
387
+ const choices = MCP_CLIENTS.map((c) => {
388
+ const p = c.resolveConfigPath();
389
+ return {
390
+ name: p ? `${c.displayName} ${p}` : `${c.displayName}(当前系统不可用)`,
391
+ value: c.id,
392
+ checked: c.id === "cursor" || c.id === "claude-code",
393
+ disabled: p ? false : true,
394
+ };
395
+ });
396
+ return checkbox({
397
+ ...iq,
398
+ message: "要注册 MCP 的客户端(空格勾选,回车确认)",
399
+ choices,
400
+ required: true,
401
+ loop: false,
402
+ });
403
+ }
404
+ export async function promptOverwriteExisting(serverLabel) {
405
+ return confirm({
406
+ ...iq,
407
+ message: `${serverLabel} 已有 ossput 配置且内容不同,是否覆盖?`,
408
+ default: true,
409
+ });
410
+ }
@@ -0,0 +1,9 @@
1
+ export interface SetupOptions {
2
+ nonInteractive?: boolean;
3
+ configPath?: string;
4
+ profileName?: string;
5
+ clientIds?: string[];
6
+ skipConnectivity?: boolean;
7
+ skipSkillInstall?: boolean;
8
+ }
9
+ export declare function runSetup(options?: SetupOptions): Promise<void>;