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

157 lines 7.85 kB
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; /** * Git Push Tool * * Push commits to remote repository. */ import { Box, Text } from 'ink'; import React from 'react'; import { useTheme } from '../../hooks/useTheme.js'; import { jsonSchema, tool } from '../../types/core.js'; import { execGit, getCurrentBranch, getUnpushedCommits, getUpstreamBranch, remoteExists, } from './utils.js'; // ============================================================================ // Preview // ============================================================================ async function getPushPreview(args) { const remote = args.remote || 'origin'; const branch = args.branch || (await getCurrentBranch()); const upstream = await getUpstreamBranch(); const commits = await getUnpushedCommits(); const isForce = args.force || args.forceWithLease || false; const needsUpstream = !upstream && !args.setUpstream; return { remote, branch, upstream, commits, isForce, needsUpstream }; } // ============================================================================ // Execution // ============================================================================ const executeGitPush = async (args) => { try { const remote = args.remote || 'origin'; const branch = args.branch || (await getCurrentBranch()); // Validate remote exists const exists = await remoteExists(remote); if (!exists) { return `Error: Remote '${remote}' does not exist.`; } // Get commits that will be pushed const commits = await getUnpushedCommits(); // Build git command const gitArgs = ['push']; if (args.setUpstream) { gitArgs.push('-u'); } if (args.force) { gitArgs.push('--force'); } else if (args.forceWithLease) { gitArgs.push('--force-with-lease'); } gitArgs.push(remote, branch); await execGit(gitArgs); const lines = []; lines.push(`Pushed to ${remote}/${branch}`); if (commits.length > 0) { lines.push(''); lines.push(`Commits pushed (${commits.length}):`); for (const commit of commits.slice(0, 10)) { lines.push(` ${commit.shortHash} ${commit.subject}`); } if (commits.length > 10) { lines.push(` ... and ${commits.length - 10} more`); } } if (args.setUpstream) { lines.push(''); lines.push(`Upstream set to ${remote}/${branch}`); } if (args.force || args.forceWithLease) { lines.push(''); lines.push('(Force push completed)'); } return lines.join('\n'); } catch (error) { return `Error: ${error instanceof Error ? error.message : 'Unknown error'}`; } }; // ============================================================================ // Tool Definition // ============================================================================ const gitPushCoreTool = tool({ description: 'Push commits to remote repository. Use setUpstream=true to set tracking branch, force or forceWithLease for force push (use with caution!).', inputSchema: jsonSchema({ type: 'object', properties: { remote: { type: 'string', description: 'Remote name (default: origin)', }, branch: { type: 'string', description: 'Branch to push (default: current branch)', }, setUpstream: { type: 'boolean', description: 'Set upstream tracking branch (-u)', }, force: { type: 'boolean', description: 'Force push (DANGEROUS - overwrites remote history)', }, forceWithLease: { type: 'boolean', description: 'Safer force push (fails if remote has new commits)', }, }, required: [], }), // ALWAYS_APPROVE - user should see what commits will be pushed needsApproval: () => true, execute: async (args, _options) => { return await executeGitPush(args); }, }); // ============================================================================ // Formatter // ============================================================================ function GitPushFormatter({ args, result, }) { const { colors } = useTheme(); const [preview, setPreview] = React.useState(null); // Load preview before execution React.useEffect(() => { if (!result) { getPushPreview(args) .then(setPreview) .catch(() => { }); } }, [args, result]); const isForce = args.force || args.forceWithLease; return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { color: colors.tool, children: "\u2692 git_push" }), isForce && (_jsx(Box, { children: _jsx(Text, { color: colors.error, children: args.force ? '⚠️ FORCE PUSH - This will overwrite remote history!' : '⚠️ FORCE WITH LEASE - Safer but still rewrites history' }) })), preview && (_jsxs(_Fragment, { children: [_jsxs(Box, { children: [_jsx(Text, { color: colors.secondary, children: "Remote: " }), _jsx(Text, { color: colors.text, children: preview.remote })] }), _jsxs(Box, { children: [_jsx(Text, { color: colors.secondary, children: "Branch: " }), _jsx(Text, { color: colors.primary, children: preview.branch })] }), preview.commits.length > 0 && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: colors.secondary, children: ["Commits to push (", preview.commits.length, "):"] }), preview.commits.slice(0, 5).map((commit, i) => (_jsxs(Text, { color: colors.text, children: [' ', commit.shortHash] }, i))), preview.commits.length > 5 && (_jsxs(Text, { color: colors.primary, children: [' ', "... and ", preview.commits.length - 5, " more"] }))] })), preview.commits.length === 0 && !result && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.warning, children: "No commits to push (up to date)" }) })), preview.needsUpstream && (_jsx(Box, { children: _jsx(Text, { color: colors.warning, children: "No upstream set. Consider using setUpstream: true" }) }))] })), args.setUpstream && (_jsxs(Box, { children: [_jsx(Text, { color: colors.secondary, children: "Set upstream: " }), _jsx(Text, { color: colors.text, children: "yes" })] })), result?.includes('Pushed to') && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.success, children: "\u2713 Push completed successfully" }) })), result?.includes('Error:') && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.error, children: ["\u2717 ", result] }) }))] })); } const formatter = (args, result) => { return _jsx(GitPushFormatter, { args: args, result: result }); }; // ============================================================================ // Validator // ============================================================================ const validator = async (args) => { const remote = args.remote || 'origin'; // Validate remote exists const exists = await remoteExists(remote); if (!exists) { return { valid: false, error: `Remote '${remote}' does not exist` }; } return { valid: true }; }; // ============================================================================ // Export // ============================================================================ export const gitPushTool = { name: 'git_push', tool: gitPushCoreTool, formatter, validator, }; //# sourceMappingURL=git-push.js.map