@nanocollective/nanocoder
Version:
A local-first CLI coding agent that brings the power of agentic coding tools like Claude Code and Gemini CLI to local models or controlled APIs like OpenRouter
126 lines • 4.97 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { constants } from 'node:fs';
import { access, copyFile, stat } from 'node:fs/promises';
import { dirname, resolve } from 'node:path';
import { Box, Text } from 'ink';
import React from 'react';
import ToolMessage from '../../components/tool-message.js';
import { isNanocoderToolAlwaysAllowed } from '../../config/nanocoder-tools-config.js';
import { getCurrentMode } from '../../context/mode-context.js';
import { ThemeContext } from '../../hooks/useTheme.js';
import { jsonSchema, tool } from '../../types/core.js';
import { invalidateCache } from '../../utils/file-cache.js';
import { isValidFilePath, resolveFilePath } from '../../utils/path-validation.js';
const executeCopyFile = async (args) => {
const srcAbsPath = resolve(args.source);
const destAbsPath = resolve(args.destination);
await copyFile(srcAbsPath, destAbsPath);
invalidateCache(destAbsPath);
return `File copied: ${args.source} → ${args.destination}`;
};
const copyFileCoreTool = tool({
description: 'Copy a file to a new location. Use this instead of execute_bash with cp.',
inputSchema: jsonSchema({
type: 'object',
properties: {
source: {
type: 'string',
description: 'The relative path of the file to copy.',
},
destination: {
type: 'string',
description: 'The relative path for the copy.',
},
},
required: ['source', 'destination'],
}),
needsApproval: () => {
if (isNanocoderToolAlwaysAllowed('copy_file')) {
return false;
}
const mode = getCurrentMode();
return mode !== 'auto-accept';
},
execute: async (args, _options) => {
return await executeCopyFile(args);
},
});
const CopyFileFormatter = React.memo(({ args, result }) => {
const themeContext = React.useContext(ThemeContext);
if (!themeContext) {
throw new Error('ThemeContext is required');
}
const { colors } = themeContext;
const messageContent = (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: colors.tool, children: "\u2692 copy_file" }), _jsxs(Box, { children: [_jsx(Text, { color: colors.secondary, children: "Source: " }), _jsx(Text, { color: colors.text, children: args.source })] }), _jsxs(Box, { children: [_jsx(Text, { color: colors.secondary, children: "Destination: " }), _jsx(Text, { color: colors.text, children: args.destination })] }), result && (_jsxs(Box, { children: [_jsx(Text, { color: colors.secondary, children: "Result: " }), _jsx(Text, { color: colors.text, children: result })] }))] }));
return _jsx(ToolMessage, { message: messageContent, hideBox: true });
});
const copyFileFormatter = (args, result) => {
return _jsx(CopyFileFormatter, { args: args, result: result });
};
const copyFileValidator = async (args) => {
// Validate source path
if (!isValidFilePath(args.source)) {
return {
valid: false,
error: `⚒ Invalid source path: "${args.source}". Path must be relative and within the project directory.`,
};
}
// Validate destination path
if (!isValidFilePath(args.destination)) {
return {
valid: false,
error: `⚒ Invalid destination path: "${args.destination}". Path must be relative and within the project directory.`,
};
}
try {
const cwd = process.cwd();
resolveFilePath(args.source, cwd);
resolveFilePath(args.destination, cwd);
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return {
valid: false,
error: `⚒ Path validation failed: ${errorMessage}`,
};
}
// Check source exists
const srcAbsPath = resolve(args.source);
try {
await access(srcAbsPath, constants.F_OK);
}
catch {
return {
valid: false,
error: `⚒ Source file does not exist: "${args.source}"`,
};
}
// Check source is a file
const fileStat = await stat(srcAbsPath);
if (fileStat.isDirectory()) {
return {
valid: false,
error: `⚒ Source is a directory, not a file: "${args.source}"`,
};
}
// Check destination parent directory exists
const destAbsPath = resolve(args.destination);
const parentDir = dirname(destAbsPath);
try {
await access(parentDir, constants.F_OK);
}
catch {
return {
valid: false,
error: `⚒ Destination parent directory does not exist: "${parentDir}"`,
};
}
return { valid: true };
};
export const copyFileTool = {
name: 'copy_file',
tool: copyFileCoreTool,
formatter: copyFileFormatter,
validator: copyFileValidator,
};
//# sourceMappingURL=copy-file.js.map