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