UNPKG

@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
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