Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/cli/src/agent-safe.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ const SAFE_COMMANDS = new Set([
const DANGEROUS_COMMANDS = new Set([
'rm',
'mv',
'rename-folder'
'rename-folder',
'rename-file'
]);

function defaultAgentConfig() {
Expand Down
15 changes: 15 additions & 0 deletions packages/cli/src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const {
listAll,
moveRemoteItem,
PERSONAL_ROOT_FOLDER_ID,
renameRemoteFile,
renameRemoteFolder,
searchRemoteEntries
} = require('./remote');
Expand Down Expand Up @@ -496,6 +497,20 @@ async function main(argv = process.argv.slice(2)) {
return;
}

// Custom patch: rename a remote FILE by id.
if (parsed.command === 'rename-file') {
const remoteFileId = requireArg(parsed.args[0], 'remoteFileId');
const newName = requireArg(parsed.args[1], 'newName');
const client = createClient();
await renameRemoteFile(client, remoteFileId, newName);
if (wantsJson) {
writeJsonOutput({ ok: true, fileId: String(remoteFileId), newName });
} else {
console.log(`renamed file ${remoteFileId} ${newName}`);
}
return;
}

if (parsed.command === 'quota') {
const client = createClient();
const info = await client.getUserSizeInfo();
Expand Down
15 changes: 15 additions & 0 deletions packages/cli/src/remote.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,20 @@ async function renameRemoteFolder(client, folderId, folderName) {
return client.renameFolder({ folderId, folderName });
}

// Custom patch: rename a FILE (the SDK only wraps renameFolder).
// Uses the official open API endpoint directly.
async function renameRemoteFile(client, fileId, fileName) {
const { API_URL } = require('cloud189-sdk/dist/const');
return client.request
.post(`${API_URL}/open/file/renameFile.action`, {
form: {
destFileName: fileName,
fileId: String(fileId)
}
})
.json();
}

async function runBatchTask(client, type, taskInfos, options = {}) {
const result = await client.createBatchTask({
type,
Expand Down Expand Up @@ -194,6 +208,7 @@ module.exports = {
moveRemoteItem,
listAll,
PERSONAL_ROOT_FOLDER_ID,
renameRemoteFile,
renameRemoteFolder,
runBatchTask,
resolveFolderId,
Expand Down
101 changes: 100 additions & 1 deletion packages/mcp/src/mcp-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,25 @@ const z = require('zod');
const PACKAGE_VERSION = require(path.join(__dirname, '..', 'package.json')).version;
const PERSONAL_ROOT_FOLDER_ID = '-11';

// Windows compatibility patch: resolve the cloud189 CLI as a JS entry executed by
// the current Node binary, because spawn('cloud189') cannot resolve the .cmd shim.
const CLOUD189_CLI_JS = path.join(
path.dirname(require.resolve('@codesentryai/cloud189/package.json')),
'bin',
'cloud189.js'
);

// --- helpers ----------------------------------------------------------------

// Custom patch: Tianyi Cloud binds the sessionKey to the login egress IP.
// On dual-stack networks Node may prefer IPv6 while login used IPv4,
// causing HTTP 400 "InvalidSessionKey - check ip error".
// Force IPv4-first explicitly for every CLI child process (not reliant on env).
const DNS_ARGS = ['--dns-result-order=ipv4first'];

function runCloud189(args, opts = {}) {
try {
const result = execFileSync('cloud189', [...args, '--json'], {
const result = execFileSync(process.execPath, [...DNS_ARGS, CLOUD189_CLI_JS, ...args, '--json'], {
timeout: 30000,
...opts
});
Expand Down Expand Up @@ -206,6 +220,91 @@ server.tool(
(args) => runTool(() => runCloud189(['plan', args.command, ...args.args]))
);

// --- custom patch: destructive + rename tools -------------------------------

function assertConfirmed(confirm, action) {
if (confirm !== true) {
const err = new Error(
`Refused to ${action} because confirm is not true. First call cloud189_plan to preview, then re-run with confirm: true.`
);
err.code = 'CONFIRM_REQUIRED';
throw err;
}
}

server.tool(
'cloud189_rename_folder',
'Rename a remote folder.',
{
remoteFolderId: remoteIdSchema.describe('Remote folder ID'),
newName: z.string().min(1).describe('New folder name'),
confirm: z.boolean().optional().describe('Must be true to execute')
},
(args) =>
runTool(() => {
assertConfirmed(args.confirm, 'rename folder');
return runCloud189(['rename-folder', args.remoteFolderId, args.newName]);
})
);

server.tool(
'cloud189_rename_file',
'Rename a remote file (custom patch, uses the official renameFile API).',
{
remoteFileId: remoteIdSchema.describe('Remote file ID'),
newName: z.string().min(1).describe('New file name (including extension)'),
confirm: z.boolean().optional().describe('Must be true to execute')
},
(args) =>
runTool(() => {
assertConfirmed(args.confirm, 'rename file');
return runCloud189(['rename-file', args.remoteFileId, args.newName]);
})
);

server.tool(
'cloud189_rm',
'Delete a remote file or folder. DANGEROUS. Use cloud189_plan first to preview.',
{
remoteId: remoteIdSchema.describe('Remote file or folder ID'),
dir: z.boolean().optional().describe('Set to true if deleting a folder'),
name: z.string().optional().describe('Remote item name (recommended, used for audit)'),
parent: remoteIdSchema.optional().describe('Parent folder ID (recommended, used for audit)'),
confirm: z.boolean().optional().describe('Must be true to execute')
},
(args) =>
runTool(() => {
assertConfirmed(args.confirm, 'delete');
const cmdArgs = ['rm', args.remoteId];
if (args.dir) cmdArgs.push('--dir');
if (args.name) cmdArgs.push('--name', args.name);
if (args.parent) cmdArgs.push('--parent', args.parent);
return runCloud189(cmdArgs);
})
);

server.tool(
'cloud189_mv',
'Move a remote file or folder to another folder. DANGEROUS. Use cloud189_plan first to preview.',
{
remoteId: remoteIdSchema.describe('Remote file or folder ID'),
targetFolderId: remoteIdSchema.describe('Destination folder ID'),
dir: z.boolean().optional().describe('Set to true if moving a folder'),
name: z.string().optional().describe('Remote item name (recommended, used for audit)'),
parent: remoteIdSchema.optional().describe('Current parent folder ID (recommended, used for audit)'),
confirm: z.boolean().optional().describe('Must be true to execute')
},
(args) =>
runTool(() => {
assertConfirmed(args.confirm, 'move');
const cmdArgs = ['mv', args.remoteId, args.targetFolderId];
if (args.dir) cmdArgs.push('--dir');
if (args.name) cmdArgs.push('--name', args.name);
if (args.parent) cmdArgs.push('--parent', args.parent);
return runCloud189(cmdArgs);
})
);

// --- main -------------------------------------------------------------------

async function main() {
Expand Down