@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
151 lines • 7.64 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
/**
* Git Pull Tool
*
* Pull changes from remote repository.
*/
import { Box, Text } from 'ink';
import React from 'react';
import { getCurrentMode } from '../../context/mode-context.js';
import { useTheme } from '../../hooks/useTheme.js';
import { jsonSchema, tool } from '../../types/core.js';
import { execGit, getAheadBehind, getCurrentBranch, getUpstreamBranch, hasUncommittedChanges, remoteExists, } from './utils.js';
// ============================================================================
// Execution
// ============================================================================
const executeGitPull = async (args) => {
try {
const remote = args.remote || 'origin';
const currentBranch = await getCurrentBranch();
const upstream = await getUpstreamBranch();
// Determine branch to pull
let branch = args.branch;
if (!branch && upstream) {
branch = upstream.replace(`${remote}/`, '');
}
if (!branch) {
branch = currentBranch;
}
// Check for uncommitted changes
const hasChanges = await hasUncommittedChanges();
if (hasChanges && !args.rebase) {
return 'Warning: You have uncommitted changes. Consider stashing them first or use rebase=true.';
}
// Validate remote exists
const exists = await remoteExists(remote);
if (!exists) {
return `Error: Remote '${remote}' does not exist.`;
}
// Get ahead/behind before pull
const { behind: behindBefore } = await getAheadBehind();
// Build git command
const gitArgs = ['pull'];
if (args.rebase) {
gitArgs.push('--rebase');
}
gitArgs.push(remote, branch);
const output = await execGit(gitArgs);
const lines = [];
// Check if already up to date
if (output.includes('Already up to date')) {
lines.push('Already up to date.');
return lines.join('\n');
}
lines.push(`Pulled from ${remote}/${branch}`);
// Parse output for stats
const statsMatch = output.match(/(\d+) files? changed(?:, (\d+) insertions?\(\+\))?(?:, (\d+) deletions?\(-\))?/);
if (statsMatch) {
const files = statsMatch[1] || '0';
const insertions = statsMatch[2] || '0';
const deletions = statsMatch[3] || '0';
lines.push(`Changes: ${files} files, +${insertions}, -${deletions}`);
}
if (behindBefore > 0) {
lines.push(`Merged ${behindBefore} commit(s)`);
}
if (args.rebase) {
lines.push('');
lines.push('(Rebased local commits on top)');
}
return lines.join('\n');
}
catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error';
// Check for merge conflicts
if (message.includes('CONFLICT') || message.includes('conflict')) {
return `Error: Merge conflicts detected. Resolve conflicts and commit, or run git_reset to abort.\n\n${message}`;
}
return `Error: ${message}`;
}
};
// ============================================================================
// Tool Definition
// ============================================================================
const gitPullCoreTool = tool({
description: 'Pull changes from remote repository. Use rebase=true to rebase local commits on top of remote changes.',
inputSchema: jsonSchema({
type: 'object',
properties: {
remote: {
type: 'string',
description: 'Remote name (default: origin)',
},
branch: {
type: 'string',
description: 'Branch to pull (default: current tracking branch)',
},
rebase: {
type: 'boolean',
description: 'Rebase local commits instead of merging',
},
},
required: [],
}),
// STANDARD - requires approval in normal mode, skipped in auto-accept
needsApproval: () => {
const mode = getCurrentMode();
return mode === 'normal';
},
execute: async (args, _options) => {
return await executeGitPull(args);
},
});
// ============================================================================
// Formatter
// ============================================================================
function GitPullFormatter({ args, result, }) {
const { colors } = useTheme();
const [preview, setPreview] = React.useState(null);
// Load preview before execution
React.useEffect(() => {
if (!result) {
(async () => {
const remote = args.remote || 'origin';
const currentBranch = await getCurrentBranch();
const upstream = await getUpstreamBranch();
let branch = args.branch;
if (!branch && upstream) {
branch = upstream.replace(`${remote}/`, '');
}
if (!branch)
branch = currentBranch;
const { behind } = await getAheadBehind();
const hasChanges = await hasUncommittedChanges();
setPreview({ remote, branch, behind, hasChanges });
})().catch(() => { });
}
}, [args, result]);
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { color: colors.tool, children: "\u2692 git_pull" }), preview && (_jsxs(_Fragment, { children: [_jsxs(Box, { children: [_jsx(Text, { color: colors.secondary, children: "Remote: " }), _jsxs(Text, { color: colors.text, children: [preview.remote, "/", preview.branch] })] }), _jsxs(Box, { children: [_jsx(Text, { color: colors.secondary, children: "Strategy: " }), _jsx(Text, { color: colors.text, children: args.rebase ? 'rebase' : 'merge' })] }), preview.behind > 0 && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.secondary, children: "Incoming: " }), _jsxs(Text, { color: colors.text, children: [preview.behind, " commit(s)"] })] })), preview.behind === 0 && !result && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.success, children: "\u2713 Already up to date" }) })), preview.hasChanges && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.warning, children: "Uncommitted changes detected" }) }))] })), result?.includes('Pulled from') && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.success, children: "\u2713 Pull completed successfully" }) })), result?.includes('Already up to date') && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.success, children: "\u2713 Already up to date" }) })), result?.includes('CONFLICT') && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.error, children: "\u2717 Merge conflicts detected!" }) })), result?.includes('Error:') && !result.includes('CONFLICT') && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.error, children: ["\u2717 ", result] }) }))] }));
}
const formatter = (args, result) => {
return _jsx(GitPullFormatter, { args: args, result: result });
};
// ============================================================================
// Export
// ============================================================================
export const gitPullTool = {
name: 'git_pull',
tool: gitPullCoreTool,
formatter,
};
//# sourceMappingURL=git-pull.js.map