ai-ship-cli 0.1.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierignore +3 -0
- package/.prettierrc +7 -0
- package/LICENSE +21 -0
- package/README.md +156 -0
- package/dist/ai/gemini.js +34 -0
- package/dist/ai/ollama.js +36 -0
- package/dist/analyzers/analyzer.js +93 -0
- package/dist/analyzers/compressBranchSummary.js +12 -0
- package/dist/analyzers/configAnalyzer.js +18 -0
- package/dist/analyzers/detectSignals.js +18 -0
- package/dist/analyzers/markupAnalyzer.js +26 -0
- package/dist/commands/commit/customAdd.js +1 -0
- package/dist/commands/commit/startCommit.js +155 -0
- package/dist/commands/commit.js +21 -0
- package/dist/commands/config/deleteKey.js +15 -0
- package/dist/commands/config.js +65 -0
- package/dist/commands/git/startCheckout.js +62 -0
- package/dist/commands/git/startCommit.js +91 -0
- package/dist/commands/git/startPR.js +51 -0
- package/dist/commands/git/startPush.js +24 -0
- package/dist/commands/git/startWorkflow.js +71 -0
- package/dist/commands/github/github.js +63 -0
- package/dist/commands/pr.js +1 -0
- package/dist/index.js +38 -0
- package/dist/utils/ai.js +22 -0
- package/dist/utils/asyncExecuter.js +35 -0
- package/dist/utils/files.js +28 -0
- package/dist/utils/git.js +106 -0
- package/dist/utils/github.js +13 -0
- package/dist/utils/helper.js +130 -0
- package/dist/utils/inputs.js +20 -0
- package/dist/utils/inquirer.js +79 -0
- package/dist/utils/parser.js +54 -0
- package/dist/utils/print.js +25 -0
- package/dist/utils/prompts.js +206 -0
- package/dist/utils/runCommit.js +17 -0
- package/dist/utils/runConfig.js +35 -0
- package/docs/commands.md +106 -0
- package/package.json +44 -0
- package/src/ai/gemini.ts +27 -0
- package/src/ai/ollama.ts +38 -0
- package/src/analyzers/analyzer.ts +117 -0
- package/src/analyzers/compressBranchSummary.ts +16 -0
- package/src/analyzers/configAnalyzer.ts +17 -0
- package/src/analyzers/detectSignals.ts +13 -0
- package/src/analyzers/markupAnalyzer.ts +25 -0
- package/src/commands/commit.ts +18 -0
- package/src/commands/config.ts +73 -0
- package/src/commands/git/startCheckout.ts +97 -0
- package/src/commands/git/startCommit.ts +108 -0
- package/src/commands/git/startPR.ts +66 -0
- package/src/commands/git/startPush.ts +18 -0
- package/src/commands/git/startWorkflow.ts +71 -0
- package/src/commands/github/github.ts +72 -0
- package/src/commands/pr.ts +0 -0
- package/src/index.ts +40 -0
- package/src/utils/ai.ts +30 -0
- package/src/utils/asyncExecuter.ts +39 -0
- package/src/utils/files.ts +30 -0
- package/src/utils/git.ts +108 -0
- package/src/utils/github.ts +19 -0
- package/src/utils/helper.ts +145 -0
- package/src/utils/inputs.ts +15 -0
- package/src/utils/inquirer.ts +99 -0
- package/src/utils/parser.ts +58 -0
- package/src/utils/print.ts +16 -0
- package/src/utils/prompts.ts +234 -0
- package/tsconfig.json +11 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
7
|
+
const helper_1 = require("../utils/helper");
|
|
8
|
+
const inputs_1 = require("../utils/inputs");
|
|
9
|
+
exports.default = async (args) => {
|
|
10
|
+
// `args` now contains elegantly parsed flags from minimist!
|
|
11
|
+
// Example: `--model --local connection.json` becomes `{ model: true, local: 'connection.json' }`
|
|
12
|
+
// Example: `--user-model local` becomes `{ 'user-model': 'local' }`
|
|
13
|
+
const subCommand = args._[0];
|
|
14
|
+
if (subCommand === 'show') {
|
|
15
|
+
const currenConfigs = (0, helper_1.getCurrentConfig)();
|
|
16
|
+
if (args['verbose']) {
|
|
17
|
+
(0, helper_1.verboseConfig)(currenConfigs);
|
|
18
|
+
}
|
|
19
|
+
else if (args['json']) {
|
|
20
|
+
(0, helper_1.jsonConfig)(currenConfigs);
|
|
21
|
+
}
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (subCommand === 'set') {
|
|
25
|
+
const key = args._[1];
|
|
26
|
+
const value = args._[2];
|
|
27
|
+
if (!key || !value) {
|
|
28
|
+
console.log('Usage: ai-ship config set <key> <value>');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
(0, helper_1.saveValueToConfig)(key, value);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (subCommand === 'get') {
|
|
35
|
+
const key = args._[1];
|
|
36
|
+
if (!key) {
|
|
37
|
+
console.log(chalk_1.default.yellow('Usage: ai-ship config get <key>'));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const configValue = (0, helper_1.getCurrentConfig)(key);
|
|
41
|
+
if (configValue === undefined || configValue === null || configValue === '') {
|
|
42
|
+
console.log(chalk_1.default.red(`Config "${key}" not found.`));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
console.log(`${chalk_1.default.cyan(key)}: ${chalk_1.default.green(JSON.stringify(configValue, null, 2))}`);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (args['add-key']) {
|
|
49
|
+
const apiKey = await (0, inputs_1.askApiKey)();
|
|
50
|
+
(0, helper_1.saveValueToConfig)('geminiApiKey', apiKey);
|
|
51
|
+
(0, helper_1.log)('API key saved!');
|
|
52
|
+
}
|
|
53
|
+
else if (args['delete-key']) {
|
|
54
|
+
if ((0, helper_1.deleteConfigKey)('geminiApiKey')) {
|
|
55
|
+
(0, helper_1.log)(chalk_1.default.green('API Key Deleted'));
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
(0, helper_1.log)(chalk_1.default.red('API Key Could Not Be Deleted. API KEY NOT FOUND!'));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
(0, helper_1.log)(chalk_1.default.yellow('Unrecognized config option. Here are the extracted args for your logic:'));
|
|
63
|
+
console.log(args);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
7
|
+
const ora_1 = __importDefault(require("ora"));
|
|
8
|
+
const ai_1 = require("../../utils/ai");
|
|
9
|
+
const inquirer_1 = require("../../utils/inquirer");
|
|
10
|
+
const git_1 = require("../../utils/git");
|
|
11
|
+
exports.default = async ({ branchSummary, allBranches, currentBranch, commitMessage, provider, flags = {}, }) => {
|
|
12
|
+
let branchName = '';
|
|
13
|
+
let branchAccepted = false;
|
|
14
|
+
const sanitizeBranchName = (name) => {
|
|
15
|
+
return name
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.replace(/['"]/g, '')
|
|
18
|
+
.replace(/\\s+/g, '-')
|
|
19
|
+
.replace(/[^a-z0-9\\-\\/]/g, '')
|
|
20
|
+
.replace(/--+/g, '-')
|
|
21
|
+
.trim();
|
|
22
|
+
};
|
|
23
|
+
const branchPrompt = (0, ai_1.getBranchPrompt)(provider, branchSummary, allBranches, currentBranch, commitMessage);
|
|
24
|
+
const allBranchesSet = new Set(allBranches);
|
|
25
|
+
while (!branchAccepted) {
|
|
26
|
+
const branchSpinner = (0, ora_1.default)('Generating branch name...').start();
|
|
27
|
+
const rawBranch = await (0, ai_1.generateAIResponse)(provider, branchPrompt);
|
|
28
|
+
branchName = sanitizeBranchName(rawBranch);
|
|
29
|
+
// ensure branch prefix exists
|
|
30
|
+
if (!branchName.includes('/')) {
|
|
31
|
+
branchName = `feature/${branchName}`;
|
|
32
|
+
}
|
|
33
|
+
// avoid branch collision
|
|
34
|
+
let finalBranch = branchName;
|
|
35
|
+
let counter = 1;
|
|
36
|
+
while (allBranchesSet.has(finalBranch)) {
|
|
37
|
+
finalBranch = `${branchName}-${counter++}`;
|
|
38
|
+
}
|
|
39
|
+
branchName = finalBranch;
|
|
40
|
+
branchSpinner.succeed('Branch name generated:\\n');
|
|
41
|
+
console.log(chalk_1.default.magenta(branchName));
|
|
42
|
+
console.log('');
|
|
43
|
+
// auto accept if --yes
|
|
44
|
+
if (flags?.yes) {
|
|
45
|
+
branchAccepted = true;
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
const result = await (0, inquirer_1.interactiveRefinePrompt)('branch name', branchName);
|
|
49
|
+
if (result.cancel)
|
|
50
|
+
return;
|
|
51
|
+
branchAccepted = result.accepted;
|
|
52
|
+
branchName = sanitizeBranchName(result.value);
|
|
53
|
+
}
|
|
54
|
+
// dry run support
|
|
55
|
+
if (flags?.['dry-run']) {
|
|
56
|
+
console.log(chalk_1.default.yellow('Dry run enabled. Branch not created.\n'));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const branchProcessSpinner = (0, ora_1.default)('Applying branch name...').start();
|
|
60
|
+
await (0, git_1.gitCheckoutNewBranch)(branchName);
|
|
61
|
+
branchProcessSpinner.succeed(`Checked out to ${chalk_1.default.bold(branchName)}!`);
|
|
62
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
7
|
+
const helper_1 = require("../../utils/helper");
|
|
8
|
+
const git_1 = require("../../utils/git");
|
|
9
|
+
const files_1 = require("../../utils/files");
|
|
10
|
+
const parser_1 = require("../../utils/parser");
|
|
11
|
+
const analyzer_1 = require("../../analyzers/analyzer");
|
|
12
|
+
const ai_1 = require("../../utils/ai");
|
|
13
|
+
const inquirer_1 = require("../../utils/inquirer");
|
|
14
|
+
const ora_1 = __importDefault(require("ora"));
|
|
15
|
+
let provider = (0, helper_1.getProvider)();
|
|
16
|
+
exports.default = async (flags = {}) => {
|
|
17
|
+
try {
|
|
18
|
+
if (!provider) {
|
|
19
|
+
throw new Error('No provider set');
|
|
20
|
+
}
|
|
21
|
+
const runProvider = flags.model ? flags.model : provider;
|
|
22
|
+
console.log('');
|
|
23
|
+
console.log(chalk_1.default.bold.bgBlue(' 🚀 AI-SHIP ') + chalk_1.default.bold.blue(' Commit Generator '));
|
|
24
|
+
console.log(chalk_1.default.dim('==================================='));
|
|
25
|
+
console.log('');
|
|
26
|
+
// 1️⃣ Get staged files
|
|
27
|
+
const scanSpinner = (0, ora_1.default)('Scanning staged files...').start();
|
|
28
|
+
const filesChanged = await (0, git_1.getFilesChanged)();
|
|
29
|
+
if (!filesChanged.length) {
|
|
30
|
+
scanSpinner.fail(chalk_1.default.yellow('No staged files detected.'));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
// 2️⃣ Expand directories
|
|
34
|
+
const expandedFiles = (0, files_1.expandDirectories)(filesChanged);
|
|
35
|
+
// 3️⃣ Extract filenames
|
|
36
|
+
let filenames = expandedFiles.map((f) => f.file);
|
|
37
|
+
// 4️⃣ Filter noise files
|
|
38
|
+
filenames = (0, parser_1.filterNoiseFiles)(filenames);
|
|
39
|
+
scanSpinner.succeed(`Found ${filesChanged.length} staged file(s).`);
|
|
40
|
+
console.log(chalk_1.default.dim('Files changed:'));
|
|
41
|
+
filesChanged.forEach((f) => console.log(chalk_1.default.green(` + ${f.file}`)));
|
|
42
|
+
console.log('');
|
|
43
|
+
// 5️⃣ Analyze diff
|
|
44
|
+
const analyzeSpinner = (0, ora_1.default)('Analyzing file changes changes').start();
|
|
45
|
+
const diffs = await (0, git_1.getStagedDiff)(filenames);
|
|
46
|
+
const diffSummary = (0, analyzer_1.analyzeDiff)(diffs);
|
|
47
|
+
analyzeSpinner.succeed('Analysis complete.\n');
|
|
48
|
+
// 6️⃣ Commit message generation
|
|
49
|
+
let commitMessage = '';
|
|
50
|
+
let commitAccepted = false;
|
|
51
|
+
while (!commitAccepted) {
|
|
52
|
+
const commitSpinner = (0, ora_1.default)('Generating commit message...').start();
|
|
53
|
+
const prompt = (0, ai_1.getCommitPrompt)(runProvider, diffSummary);
|
|
54
|
+
commitMessage = await (0, ai_1.generateAIResponse)(runProvider, prompt);
|
|
55
|
+
commitSpinner.succeed('Commit message generated:\\n');
|
|
56
|
+
console.log(chalk_1.default.cyan(commitMessage));
|
|
57
|
+
console.log('');
|
|
58
|
+
// dry-run → exit early
|
|
59
|
+
if (flags['dry-run']) {
|
|
60
|
+
console.log(chalk_1.default.yellow('Dry run enabled. Commit not executed.\\n'));
|
|
61
|
+
const unstageSpinner = (0, ora_1.default)('Unstaging files...').start();
|
|
62
|
+
await (0, git_1.unstageFiles)();
|
|
63
|
+
unstageSpinner.succeed('Files unstaged.');
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
// skip prompt if --yes
|
|
67
|
+
if (flags['yes']) {
|
|
68
|
+
commitAccepted = true;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
const result = await (0, inquirer_1.interactiveRefinePrompt)('commit message', commitMessage);
|
|
72
|
+
if (result.cancel)
|
|
73
|
+
return;
|
|
74
|
+
commitAccepted = result.accepted;
|
|
75
|
+
commitMessage = result.value;
|
|
76
|
+
}
|
|
77
|
+
// 7️⃣ Commit
|
|
78
|
+
const commitSpinner = (0, ora_1.default)('Committing changes...').start();
|
|
79
|
+
await (0, git_1.gitCommit)(commitMessage);
|
|
80
|
+
commitSpinner.succeed('Changes successfully committed!\n');
|
|
81
|
+
return { commitMessage, diffSummary, runProvider };
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
console.log(err);
|
|
85
|
+
if (err.name === 'ExitPromptError') {
|
|
86
|
+
console.log(chalk_1.default.yellow('\nProcess aborted using user prompt.\n'));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
(0, helper_1.log)(chalk_1.default.red(`We ran into an error: ${err}`));
|
|
90
|
+
}
|
|
91
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.startPR = void 0;
|
|
7
|
+
const ora_1 = __importDefault(require("ora"));
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const prompts_1 = require("../../utils/prompts");
|
|
10
|
+
const git_1 = require("../../utils/git");
|
|
11
|
+
const github_1 = require("../github/github");
|
|
12
|
+
const helper_1 = require("../../utils/helper");
|
|
13
|
+
const ai_1 = require("../../utils/ai");
|
|
14
|
+
const startPR = async ({ diffSummary, commitMessage, provider, flags, }) => {
|
|
15
|
+
try {
|
|
16
|
+
const spinner = (0, ora_1.default)('Generating PR...').start();
|
|
17
|
+
const branchName = await (0, git_1.getCurrentBranchName)();
|
|
18
|
+
const prompt = (0, prompts_1.pRPrompt)({
|
|
19
|
+
commitMessage,
|
|
20
|
+
branchName,
|
|
21
|
+
summary: diffSummary,
|
|
22
|
+
});
|
|
23
|
+
const response = await (0, ai_1.generateAIResponse)(provider, prompt);
|
|
24
|
+
spinner.succeed('PR content generated.\n');
|
|
25
|
+
// 🔥 Parse response
|
|
26
|
+
const titleMatch = response.match(/TITLE:\s*(.*)/);
|
|
27
|
+
const descriptionMatch = response.match(/DESCRIPTION:\s*([\s\S]*)/);
|
|
28
|
+
if (!titleMatch || !descriptionMatch) {
|
|
29
|
+
throw new Error('Failed to parse PR output');
|
|
30
|
+
}
|
|
31
|
+
const title = titleMatch[1].trim();
|
|
32
|
+
const body = descriptionMatch[1].trim();
|
|
33
|
+
const createSpinner = (0, ora_1.default)('Creating PR...').start();
|
|
34
|
+
const targetBranch = flags['target-branch'] || flags['base'] || 'main';
|
|
35
|
+
const prResult = await (0, github_1.createPR)({
|
|
36
|
+
title,
|
|
37
|
+
body,
|
|
38
|
+
head: branchName,
|
|
39
|
+
base: targetBranch,
|
|
40
|
+
});
|
|
41
|
+
createSpinner.succeed(chalk_1.default.green('PR created successfully!\n'));
|
|
42
|
+
if (prResult.stdout && prResult.stdout.trim()) {
|
|
43
|
+
console.log(chalk_1.default.blue(`🔗 PR Link: ${prResult.stdout.trim()}\n`));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
console.log(err);
|
|
48
|
+
(0, helper_1.log)(chalk_1.default.red(`PR generation failed: ${err}`));
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
exports.startPR = startPR;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.gitDirectPush = exports.gitInteractivePush = void 0;
|
|
7
|
+
const ora_1 = __importDefault(require("ora"));
|
|
8
|
+
const git_1 = require("../../utils/git");
|
|
9
|
+
const inquirer_1 = require("../../utils/inquirer");
|
|
10
|
+
const gitInteractivePush = async () => {
|
|
11
|
+
const result = await (0, inquirer_1.interactivePushPrompt)();
|
|
12
|
+
if (result.accepted) {
|
|
13
|
+
const pushSpinner = (0, ora_1.default)('Pushing changes...').start();
|
|
14
|
+
await (0, git_1.push)();
|
|
15
|
+
pushSpinner.succeed('Changes pushed to remote repository.\n');
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
exports.gitInteractivePush = gitInteractivePush;
|
|
19
|
+
const gitDirectPush = async () => {
|
|
20
|
+
const pushSpinner = (0, ora_1.default)('Pushing changes...').start();
|
|
21
|
+
await (0, git_1.push)();
|
|
22
|
+
pushSpinner.succeed('Changes pushed to remote repository.\n');
|
|
23
|
+
};
|
|
24
|
+
exports.gitDirectPush = gitDirectPush;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
7
|
+
const ora_1 = __importDefault(require("ora"));
|
|
8
|
+
const startCommit_1 = __importDefault(require("./startCommit"));
|
|
9
|
+
const startCheckout_1 = __importDefault(require("./startCheckout"));
|
|
10
|
+
const startPush_1 = require("./startPush");
|
|
11
|
+
const helper_1 = require("../../utils/helper");
|
|
12
|
+
const git_1 = require("../../utils/git");
|
|
13
|
+
const compressBranchSummary_1 = require("../../analyzers/compressBranchSummary");
|
|
14
|
+
const startPR_1 = require("./startPR");
|
|
15
|
+
const inquirer_1 = require("../../utils/inquirer");
|
|
16
|
+
exports.default = async (flags = {}) => {
|
|
17
|
+
try {
|
|
18
|
+
const commitResult = await (0, startCommit_1.default)(flags);
|
|
19
|
+
// If startCommit returns void/falsy, it means it was aborted, failed, or was a dry-run
|
|
20
|
+
if (!commitResult) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const { diffSummary, commitMessage, runProvider } = commitResult;
|
|
24
|
+
if (flags['new-branch']) {
|
|
25
|
+
const branchAnalyzeSpinner = (0, ora_1.default)('Checking branches...').start();
|
|
26
|
+
await (0, git_1.gitFetch)();
|
|
27
|
+
const allBranches = await (0, git_1.getAllBranches)();
|
|
28
|
+
const currentBranch = await (0, git_1.getCurrentBranchName)();
|
|
29
|
+
const branchSummary = (0, compressBranchSummary_1.compressBranchSummary)(diffSummary);
|
|
30
|
+
branchAnalyzeSpinner.succeed('Analysis complete.\\n');
|
|
31
|
+
await (0, startCheckout_1.default)({
|
|
32
|
+
branchSummary,
|
|
33
|
+
allBranches,
|
|
34
|
+
currentBranch,
|
|
35
|
+
commitMessage,
|
|
36
|
+
provider: runProvider,
|
|
37
|
+
flags,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
if (flags['push']) {
|
|
41
|
+
await (0, startPush_1.gitDirectPush)();
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
await (0, startPush_1.gitInteractivePush)();
|
|
45
|
+
}
|
|
46
|
+
if (flags['pr']) {
|
|
47
|
+
await (0, startPR_1.startPR)({
|
|
48
|
+
diffSummary,
|
|
49
|
+
commitMessage,
|
|
50
|
+
provider: runProvider,
|
|
51
|
+
flags,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
const prPromptResult = await (0, inquirer_1.interactivePRPrompt)(flags['target-branch'] || 'main');
|
|
56
|
+
if (prPromptResult.accepted) {
|
|
57
|
+
flags['target-branch'] = prPromptResult.base;
|
|
58
|
+
await (0, startPR_1.startPR)({
|
|
59
|
+
diffSummary,
|
|
60
|
+
commitMessage,
|
|
61
|
+
provider: runProvider,
|
|
62
|
+
flags,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
console.log(err);
|
|
69
|
+
(0, helper_1.log)(chalk_1.default.red(`Workflow encountered an error: ${err}`));
|
|
70
|
+
}
|
|
71
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createPR = void 0;
|
|
7
|
+
const child_process_1 = require("child_process");
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const createPR = async ({ title, body, base = 'main', head, }) => {
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
try {
|
|
13
|
+
// 🔥 Create temp file for PR body
|
|
14
|
+
const tempFile = path_1.default.join(process.cwd(), '.ai-ship-pr.md');
|
|
15
|
+
fs_1.default.writeFileSync(tempFile, body);
|
|
16
|
+
// 🔥 Safe args (NO shell)
|
|
17
|
+
const args = [
|
|
18
|
+
'pr',
|
|
19
|
+
'create',
|
|
20
|
+
'--title',
|
|
21
|
+
title,
|
|
22
|
+
'--body-file',
|
|
23
|
+
tempFile,
|
|
24
|
+
'--base',
|
|
25
|
+
base,
|
|
26
|
+
'--head',
|
|
27
|
+
head,
|
|
28
|
+
];
|
|
29
|
+
const child = (0, child_process_1.spawn)('gh', args); // ✅ no shell
|
|
30
|
+
let stdout = '';
|
|
31
|
+
let stderr = '';
|
|
32
|
+
child.stdout?.on('data', (data) => {
|
|
33
|
+
stdout += data.toString();
|
|
34
|
+
});
|
|
35
|
+
child.stderr?.on('data', (data) => {
|
|
36
|
+
stderr += data.toString();
|
|
37
|
+
});
|
|
38
|
+
child.on('close', (code) => {
|
|
39
|
+
// 🧹 cleanup temp file
|
|
40
|
+
try {
|
|
41
|
+
fs_1.default.unlinkSync(tempFile);
|
|
42
|
+
}
|
|
43
|
+
catch { }
|
|
44
|
+
if (code === 0) {
|
|
45
|
+
resolve({ stdout, stderr });
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
const error = new Error(`Command failed: gh pr create\n${stderr}`);
|
|
49
|
+
error.stdout = stdout;
|
|
50
|
+
error.stderr = stderr;
|
|
51
|
+
reject(error);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
child.on('error', (err) => {
|
|
55
|
+
reject(err);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
reject(err);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
exports.createPR = createPR;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
require("dotenv/config");
|
|
8
|
+
const minimist_1 = __importDefault(require("minimist"));
|
|
9
|
+
const config_1 = __importDefault(require("./commands/config"));
|
|
10
|
+
const commit_1 = __importDefault(require("./commands/commit"));
|
|
11
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
12
|
+
const startCommandExecution = async () => {
|
|
13
|
+
const args = (0, minimist_1.default)(process.argv.slice(2));
|
|
14
|
+
const command = args._[0];
|
|
15
|
+
const subArgs = {
|
|
16
|
+
...args,
|
|
17
|
+
_: args._.slice(1),
|
|
18
|
+
};
|
|
19
|
+
console.log('');
|
|
20
|
+
console.log(chalk_1.default.bold.bgBlue(' 🚀 AI-SHIP ') + chalk_1.default.bold.blue(' Commit Generator '));
|
|
21
|
+
console.log(chalk_1.default.dim('==================================='));
|
|
22
|
+
console.log('');
|
|
23
|
+
switch (command) {
|
|
24
|
+
case 'commit':
|
|
25
|
+
const { _, ...flags } = subArgs;
|
|
26
|
+
await (0, commit_1.default)(_, flags);
|
|
27
|
+
break;
|
|
28
|
+
case 'config':
|
|
29
|
+
await (0, config_1.default)(subArgs);
|
|
30
|
+
break;
|
|
31
|
+
// case 'pr':
|
|
32
|
+
// await run
|
|
33
|
+
default:
|
|
34
|
+
console.log('Unknown command');
|
|
35
|
+
}
|
|
36
|
+
console.log('');
|
|
37
|
+
};
|
|
38
|
+
startCommandExecution();
|
package/dist/utils/ai.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getBranchPrompt = exports.getCommitPrompt = exports.generateAIResponse = void 0;
|
|
4
|
+
const ollama_1 = require("../ai/ollama");
|
|
5
|
+
const gemini_1 = require("../ai/gemini");
|
|
6
|
+
const prompts_1 = require("./prompts");
|
|
7
|
+
const generateAIResponse = async (provider, prompt) => {
|
|
8
|
+
return provider === 'local' ? await (0, ollama_1.generateWithGemma)(prompt) : await (0, gemini_1.generateWithGemini)(prompt);
|
|
9
|
+
};
|
|
10
|
+
exports.generateAIResponse = generateAIResponse;
|
|
11
|
+
const getCommitPrompt = (provider, diffSummary) => {
|
|
12
|
+
return provider === 'local'
|
|
13
|
+
? (0, prompts_1.buildCommitPromptGemma)(diffSummary)
|
|
14
|
+
: (0, prompts_1.buildCommitPrompt)(diffSummary);
|
|
15
|
+
};
|
|
16
|
+
exports.getCommitPrompt = getCommitPrompt;
|
|
17
|
+
const getBranchPrompt = (provider, branchSummary, allBranches, currentBranch, commitMessage) => {
|
|
18
|
+
return provider === 'local'
|
|
19
|
+
? (0, prompts_1.buildBranchPromptGemma)(branchSummary, allBranches, currentBranch, commitMessage)
|
|
20
|
+
: (0, prompts_1.buildBranchPrompt)(branchSummary, allBranches, currentBranch, commitMessage);
|
|
21
|
+
};
|
|
22
|
+
exports.getBranchPrompt = getBranchPrompt;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = asyncExecuter;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
async function asyncExecuter(command) {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
const child = (0, child_process_1.spawn)(command, { shell: true });
|
|
8
|
+
let stdout = '';
|
|
9
|
+
let stderr = '';
|
|
10
|
+
if (child.stdout) {
|
|
11
|
+
child.stdout.on('data', (data) => {
|
|
12
|
+
stdout += data.toString();
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
if (child.stderr) {
|
|
16
|
+
child.stderr.on('data', (data) => {
|
|
17
|
+
stderr += data.toString();
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
child.on('close', (code) => {
|
|
21
|
+
if (code === 0) {
|
|
22
|
+
resolve({ stdout, stderr });
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
const error = new Error(`Command failed: ${command}\n${stderr}`);
|
|
26
|
+
error.stdout = stdout;
|
|
27
|
+
error.stderr = stderr;
|
|
28
|
+
reject(error);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
child.on('error', (err) => {
|
|
32
|
+
reject(err);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.expandDirectories = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const expandDirectories = (files) => {
|
|
10
|
+
const expanded = [];
|
|
11
|
+
files.forEach(({ status, file }) => {
|
|
12
|
+
if (fs_1.default.existsSync(file) && fs_1.default.lstatSync(file).isDirectory()) {
|
|
13
|
+
const subFiles = fs_1.default.readdirSync(file);
|
|
14
|
+
subFiles.forEach((subFile) => {
|
|
15
|
+
const fullPath = path_1.default.join(file, subFile);
|
|
16
|
+
expanded.push({
|
|
17
|
+
status,
|
|
18
|
+
file: fullPath,
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
expanded.push({ status, file });
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
return expanded;
|
|
27
|
+
};
|
|
28
|
+
exports.expandDirectories = expandDirectories;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.push = exports.unstageFiles = exports.gitCheckoutNewBranch = exports.gitRenameBranch = exports.gitCommit = exports.getCurrentBranchName = exports.getAllBranches = exports.gitFetch = exports.getStagedDiff = exports.stageFiles = exports.stageAll = exports.getFilesChanged = exports.getGitRoot = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const asyncExecuter_1 = __importDefault(require("./asyncExecuter"));
|
|
9
|
+
const getGitRoot = async () => {
|
|
10
|
+
const { stdout } = await (0, asyncExecuter_1.default)('git rev-parse --show-toplevel');
|
|
11
|
+
return stdout.trim();
|
|
12
|
+
};
|
|
13
|
+
exports.getGitRoot = getGitRoot;
|
|
14
|
+
const getFilesChanged = async () => {
|
|
15
|
+
const { stdout: filesChanged } = await (0, asyncExecuter_1.default)('git diff --cached --name-status');
|
|
16
|
+
if (!filesChanged.trim())
|
|
17
|
+
return [];
|
|
18
|
+
const root = await (0, exports.getGitRoot)();
|
|
19
|
+
return filesChanged
|
|
20
|
+
.trim()
|
|
21
|
+
.split('\n')
|
|
22
|
+
.map((line) => {
|
|
23
|
+
const parts = line.split('\t');
|
|
24
|
+
const status = parts[0]?.trim();
|
|
25
|
+
let filePath;
|
|
26
|
+
if (status?.startsWith('R')) {
|
|
27
|
+
// Rename → take NEW path
|
|
28
|
+
filePath = parts[2];
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
filePath = parts[1];
|
|
32
|
+
}
|
|
33
|
+
if (!filePath) {
|
|
34
|
+
throw new Error(`Invalid git diff line: ${line}`);
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
status,
|
|
38
|
+
file: path_1.default.resolve(root, filePath.trim().replace(/^[.\\/]+/, '')),
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
exports.getFilesChanged = getFilesChanged;
|
|
43
|
+
const stageAll = async () => {
|
|
44
|
+
await (0, asyncExecuter_1.default)('git add -A');
|
|
45
|
+
};
|
|
46
|
+
exports.stageAll = stageAll;
|
|
47
|
+
const stageFiles = async (files) => {
|
|
48
|
+
if (!files || files.length === 0)
|
|
49
|
+
return;
|
|
50
|
+
const filesString = files.map((f) => `"${f}"`).join(' ');
|
|
51
|
+
await (0, asyncExecuter_1.default)(`git add ${filesString}`);
|
|
52
|
+
};
|
|
53
|
+
exports.stageFiles = stageFiles;
|
|
54
|
+
const getStagedDiff = async (files) => {
|
|
55
|
+
const filesString = files.map((f) => `"${f}"`).join(' ');
|
|
56
|
+
const { stdout } = await (0, asyncExecuter_1.default)(`git diff --cached -- ${filesString}`);
|
|
57
|
+
return stdout;
|
|
58
|
+
};
|
|
59
|
+
exports.getStagedDiff = getStagedDiff;
|
|
60
|
+
const gitFetch = async () => {
|
|
61
|
+
return await (0, asyncExecuter_1.default)('git fetch --all');
|
|
62
|
+
};
|
|
63
|
+
exports.gitFetch = gitFetch;
|
|
64
|
+
const getAllBranches = async () => {
|
|
65
|
+
const { stdout: raw } = await (0, asyncExecuter_1.default)('git branch -a');
|
|
66
|
+
const branches = raw
|
|
67
|
+
.split('\\n')
|
|
68
|
+
.map((b) => b.replace('*', '').trim())
|
|
69
|
+
.map((b) => b.replace('remotes/origin/', ''))
|
|
70
|
+
.filter(Boolean);
|
|
71
|
+
return [...new Set(branches)];
|
|
72
|
+
};
|
|
73
|
+
exports.getAllBranches = getAllBranches;
|
|
74
|
+
const getCurrentBranchName = async () => {
|
|
75
|
+
return (await (0, asyncExecuter_1.default)('git branch --show-current')).stdout.trim();
|
|
76
|
+
};
|
|
77
|
+
exports.getCurrentBranchName = getCurrentBranchName;
|
|
78
|
+
const gitCommit = async (message) => {
|
|
79
|
+
// Use child_process safely by escaping quotes properly, or just use asyncExecuter
|
|
80
|
+
const escapedMessage = message.replace(/(["'$`\\])/g, '\\\\$1');
|
|
81
|
+
await (0, asyncExecuter_1.default)(`git commit -m "${escapedMessage}"`);
|
|
82
|
+
};
|
|
83
|
+
exports.gitCommit = gitCommit;
|
|
84
|
+
const gitRenameBranch = async (branchName) => {
|
|
85
|
+
await (0, asyncExecuter_1.default)(`git branch -m "${branchName}"`);
|
|
86
|
+
};
|
|
87
|
+
exports.gitRenameBranch = gitRenameBranch;
|
|
88
|
+
const gitCheckoutNewBranch = async (branchName) => {
|
|
89
|
+
await (0, asyncExecuter_1.default)(`git checkout -b ${branchName}`);
|
|
90
|
+
};
|
|
91
|
+
exports.gitCheckoutNewBranch = gitCheckoutNewBranch;
|
|
92
|
+
const unstageFiles = async () => {
|
|
93
|
+
await (0, asyncExecuter_1.default)(`git reset`);
|
|
94
|
+
};
|
|
95
|
+
exports.unstageFiles = unstageFiles;
|
|
96
|
+
const push = async () => {
|
|
97
|
+
try {
|
|
98
|
+
await (0, asyncExecuter_1.default)(`git push`);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
const { stdout } = await (0, asyncExecuter_1.default)(`git rev-parse --abbrev-ref HEAD`);
|
|
102
|
+
const branchName = stdout.trim();
|
|
103
|
+
await (0, asyncExecuter_1.default)(`git push --set-upstream origin ${branchName}`);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
exports.push = push;
|