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,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createGithubPR = void 0;
|
|
4
|
+
const createGithubPR = async ({ title, body, base = 'main', head, }) => {
|
|
5
|
+
try {
|
|
6
|
+
await (0, exports.createGithubPR)({ title, body, base, head });
|
|
7
|
+
}
|
|
8
|
+
catch (error) {
|
|
9
|
+
console.error('Failed to create GitHub PR.');
|
|
10
|
+
throw error;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
exports.createGithubPR = createGithubPR;
|
|
@@ -0,0 +1,130 @@
|
|
|
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.getProvider = exports.saveValueToConfig = exports.jsonConfig = exports.verboseConfig = exports.getCurrentConfig = exports.deleteConfigKey = exports.CONFIG_FILE = exports.CONFIG_DIR = exports.log = void 0;
|
|
7
|
+
const os_1 = __importDefault(require("os"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
+
const ALLOWED_KEYS = ['provider', 'model', 'localEndpoint', 'geminiApiKey'];
|
|
12
|
+
const log = (data) => console.log(data);
|
|
13
|
+
exports.log = log;
|
|
14
|
+
// config dir for storing api key
|
|
15
|
+
exports.CONFIG_DIR = path_1.default.join(os_1.default.homedir(), '.ai-ship');
|
|
16
|
+
// config file for storing api key
|
|
17
|
+
exports.CONFIG_FILE = path_1.default.join(exports.CONFIG_DIR, 'config.json');
|
|
18
|
+
const deleteConfigKey = (key) => {
|
|
19
|
+
if (!fs_1.default.existsSync(exports.CONFIG_FILE))
|
|
20
|
+
return false;
|
|
21
|
+
try {
|
|
22
|
+
const raw = fs_1.default.readFileSync(exports.CONFIG_FILE, 'utf-8');
|
|
23
|
+
const config = raw ? JSON.parse(raw) : {};
|
|
24
|
+
if (config[key]) {
|
|
25
|
+
delete config[key];
|
|
26
|
+
fs_1.default.writeFileSync(exports.CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
exports.deleteConfigKey = deleteConfigKey;
|
|
36
|
+
const getCurrentConfig = (key = 'all') => {
|
|
37
|
+
if (!fs_1.default.existsSync(exports.CONFIG_FILE))
|
|
38
|
+
return null;
|
|
39
|
+
const config = JSON.parse(fs_1.default.readFileSync(exports.CONFIG_FILE, 'utf-8'));
|
|
40
|
+
if (key === 'all')
|
|
41
|
+
return config;
|
|
42
|
+
return config[key];
|
|
43
|
+
};
|
|
44
|
+
exports.getCurrentConfig = getCurrentConfig;
|
|
45
|
+
const verboseConfig = (config) => {
|
|
46
|
+
const { provider, model, localEndpoint, geminiApiKey } = config;
|
|
47
|
+
const maskedKey = geminiApiKey
|
|
48
|
+
? geminiApiKey.slice(0, 4) + '...' + geminiApiKey.slice(-4)
|
|
49
|
+
: 'not configured';
|
|
50
|
+
console.log(chalk_1.default.bold('\nAI-Ship Configuration\n'));
|
|
51
|
+
console.log(chalk_1.default.yellow('Provider'));
|
|
52
|
+
console.log(' Current:', provider || 'not set');
|
|
53
|
+
console.log(' Description: Determines where AI runs (local via Ollama or cloud API)\n');
|
|
54
|
+
console.log(chalk_1.default.yellow('Model'));
|
|
55
|
+
console.log(' Current:', model || 'not set');
|
|
56
|
+
console.log(' Description: AI model used for commit and branch generation\n');
|
|
57
|
+
console.log(chalk_1.default.yellow('Local Model Settings'));
|
|
58
|
+
console.log(' Endpoint:', localEndpoint || 'not configured');
|
|
59
|
+
console.log(' Description: URL of the local model server (e.g. http://127.0.0.1:11434)\n');
|
|
60
|
+
console.log(chalk_1.default.yellow('Cloud Model Settings'));
|
|
61
|
+
console.log(' Gemini API Key:', maskedKey);
|
|
62
|
+
console.log(' Description: API key used when provider is set to cloud\n');
|
|
63
|
+
};
|
|
64
|
+
exports.verboseConfig = verboseConfig;
|
|
65
|
+
const jsonConfig = (config) => {
|
|
66
|
+
const safeConfig = {
|
|
67
|
+
provider: config.provider || null,
|
|
68
|
+
model: config.model || null,
|
|
69
|
+
localEndpoint: config.localEndpoint || null,
|
|
70
|
+
geminiApiKey: config.geminiApiKey
|
|
71
|
+
? config.geminiApiKey.slice(0, 4) + '...' + config.geminiApiKey.slice(-4)
|
|
72
|
+
: null,
|
|
73
|
+
};
|
|
74
|
+
console.log(JSON.stringify(safeConfig, null, 2));
|
|
75
|
+
};
|
|
76
|
+
exports.jsonConfig = jsonConfig;
|
|
77
|
+
const validateValue = (key, value) => {
|
|
78
|
+
switch (key) {
|
|
79
|
+
case 'provider':
|
|
80
|
+
if (!['local', 'cloud'].includes(value)) {
|
|
81
|
+
throw new Error("provider must be 'local' or 'cloud'");
|
|
82
|
+
}
|
|
83
|
+
break;
|
|
84
|
+
case 'model':
|
|
85
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
86
|
+
throw new Error('model must be a non-empty string');
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
case 'localEndpoint':
|
|
90
|
+
try {
|
|
91
|
+
new URL(value);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
throw new Error('localEndpoint must be a valid URL');
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
case 'geminiApiKey':
|
|
98
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
99
|
+
throw new Error('geminiApiKey must be a valid string');
|
|
100
|
+
}
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
const saveValueToConfig = (key, value) => {
|
|
105
|
+
try {
|
|
106
|
+
if (!ALLOWED_KEYS.includes(key)) {
|
|
107
|
+
throw new Error(`Invalid config key: ${key}. Allowed keys: ${ALLOWED_KEYS.join(', ')}`);
|
|
108
|
+
}
|
|
109
|
+
validateValue(key, value);
|
|
110
|
+
if (!fs_1.default.existsSync(exports.CONFIG_DIR)) {
|
|
111
|
+
fs_1.default.mkdirSync(exports.CONFIG_DIR, { recursive: true });
|
|
112
|
+
}
|
|
113
|
+
let config = {};
|
|
114
|
+
if (fs_1.default.existsSync(exports.CONFIG_FILE)) {
|
|
115
|
+
const raw = fs_1.default.readFileSync(exports.CONFIG_FILE, 'utf-8');
|
|
116
|
+
config = raw ? JSON.parse(raw) : {};
|
|
117
|
+
}
|
|
118
|
+
config[key] = value;
|
|
119
|
+
fs_1.default.writeFileSync(exports.CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');
|
|
120
|
+
console.log(chalk_1.default.green(`✔ Config updated: ${chalk_1.default.bold.green(key)}`));
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
console.error(chalk_1.default.red(`❌ Failed to save config: ${chalk_1.default.bold.red(err.message)}`));
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
exports.saveValueToConfig = saveValueToConfig;
|
|
127
|
+
const getProvider = () => {
|
|
128
|
+
return (0, exports.getCurrentConfig)('provider');
|
|
129
|
+
};
|
|
130
|
+
exports.getProvider = getProvider;
|
|
@@ -0,0 +1,20 @@
|
|
|
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.askApiKey = void 0;
|
|
7
|
+
const readline_1 = __importDefault(require("readline"));
|
|
8
|
+
const askApiKey = () => {
|
|
9
|
+
const rl = readline_1.default.createInterface({
|
|
10
|
+
input: process.stdin,
|
|
11
|
+
output: process.stdout,
|
|
12
|
+
});
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
rl.question('Enter your Gemini API key: ', (answer) => {
|
|
15
|
+
rl.close();
|
|
16
|
+
resolve(answer.trim());
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
exports.askApiKey = askApiKey;
|
|
@@ -0,0 +1,79 @@
|
|
|
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.interactivePRPrompt = exports.interactivePushPrompt = exports.interactiveRefinePrompt = void 0;
|
|
7
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
8
|
+
// @ts-ignore
|
|
9
|
+
const enquirer_1 = require("enquirer");
|
|
10
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
+
const interactiveRefinePrompt = async (itemType, initialValue) => {
|
|
12
|
+
const { action } = await inquirer_1.default.prompt([
|
|
13
|
+
{
|
|
14
|
+
type: 'list',
|
|
15
|
+
name: 'action',
|
|
16
|
+
message: `What would you like to do with this ${itemType}?`,
|
|
17
|
+
choices: ['Continue', 'Edit', 'Retry', 'Cancel'],
|
|
18
|
+
},
|
|
19
|
+
]);
|
|
20
|
+
if (action === 'Continue') {
|
|
21
|
+
return { accepted: true, value: initialValue, cancel: false };
|
|
22
|
+
}
|
|
23
|
+
if (action === 'Edit') {
|
|
24
|
+
const promptInput = new enquirer_1.Input({
|
|
25
|
+
message: `Edit your ${itemType}:`,
|
|
26
|
+
initial: initialValue,
|
|
27
|
+
});
|
|
28
|
+
const editedValue = (await promptInput.run());
|
|
29
|
+
return { accepted: true, value: editedValue, cancel: false };
|
|
30
|
+
}
|
|
31
|
+
if (action === 'Retry') {
|
|
32
|
+
console.log(chalk_1.default.yellow(`Retrying ${itemType}...\\n`));
|
|
33
|
+
return { accepted: false, value: initialValue, cancel: false };
|
|
34
|
+
}
|
|
35
|
+
// Cancel
|
|
36
|
+
console.log(chalk_1.default.yellow(`${itemType.charAt(0).toUpperCase() + itemType.slice(1)} cancelled.\\n`));
|
|
37
|
+
return { accepted: false, value: initialValue, cancel: true };
|
|
38
|
+
};
|
|
39
|
+
exports.interactiveRefinePrompt = interactiveRefinePrompt;
|
|
40
|
+
const interactivePushPrompt = async () => {
|
|
41
|
+
const { action } = await inquirer_1.default.prompt([
|
|
42
|
+
{
|
|
43
|
+
type: 'list',
|
|
44
|
+
name: 'action',
|
|
45
|
+
message: `Do you want to push your committed changes to the remote repository?`,
|
|
46
|
+
choices: ['Yes', 'No'],
|
|
47
|
+
},
|
|
48
|
+
]);
|
|
49
|
+
if (action === 'Yes') {
|
|
50
|
+
return { accepted: true, cancel: false };
|
|
51
|
+
}
|
|
52
|
+
console.log(chalk_1.default.yellow(`Push cancelled\n`));
|
|
53
|
+
return { accepted: false, cancel: true };
|
|
54
|
+
};
|
|
55
|
+
exports.interactivePushPrompt = interactivePushPrompt;
|
|
56
|
+
const interactivePRPrompt = async (defaultBase = 'main') => {
|
|
57
|
+
const { action } = await inquirer_1.default.prompt([
|
|
58
|
+
{
|
|
59
|
+
type: 'list',
|
|
60
|
+
name: 'action',
|
|
61
|
+
message: `Do you want to create a pull request?`,
|
|
62
|
+
choices: ['Yes', 'No'],
|
|
63
|
+
},
|
|
64
|
+
]);
|
|
65
|
+
if (action === 'Yes') {
|
|
66
|
+
const { base } = await inquirer_1.default.prompt([
|
|
67
|
+
{
|
|
68
|
+
type: 'input',
|
|
69
|
+
name: 'base',
|
|
70
|
+
message: `Which branch do you want to target?`,
|
|
71
|
+
default: defaultBase,
|
|
72
|
+
},
|
|
73
|
+
]);
|
|
74
|
+
return { accepted: true, cancel: false, base };
|
|
75
|
+
}
|
|
76
|
+
console.log(chalk_1.default.yellow(`PR creation cancelled\n`));
|
|
77
|
+
return { accepted: false, cancel: true, base: defaultBase };
|
|
78
|
+
};
|
|
79
|
+
exports.interactivePRPrompt = interactivePRPrompt;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.filterNoiseFiles = exports.isNoiseFile = exports.NOISE_PATTERNS = void 0;
|
|
4
|
+
const LOCK_FILES = [
|
|
5
|
+
'*.lock',
|
|
6
|
+
'package-lock.json',
|
|
7
|
+
'yarn.lock',
|
|
8
|
+
'pnpm-lock.yaml',
|
|
9
|
+
'Pipfile.lock',
|
|
10
|
+
'poetry.lock',
|
|
11
|
+
'Cargo.lock',
|
|
12
|
+
'composer.lock',
|
|
13
|
+
'Gemfile.lock',
|
|
14
|
+
'go.sum',
|
|
15
|
+
];
|
|
16
|
+
const BUILD_DIRS = [
|
|
17
|
+
'dist/**',
|
|
18
|
+
'build/**',
|
|
19
|
+
'out/**',
|
|
20
|
+
'target/**',
|
|
21
|
+
'bin/**',
|
|
22
|
+
'obj/**',
|
|
23
|
+
'.next/**',
|
|
24
|
+
'.nuxt/**',
|
|
25
|
+
];
|
|
26
|
+
const CACHE_DIRS = [
|
|
27
|
+
'.cache/**',
|
|
28
|
+
'.pytest_cache/**',
|
|
29
|
+
'.mypy_cache/**',
|
|
30
|
+
'.gradle/**',
|
|
31
|
+
'.idea/**',
|
|
32
|
+
'.vscode/**',
|
|
33
|
+
];
|
|
34
|
+
const DEP_DIRS = ['node_modules/**', 'vendor/**', '.venv/**', 'venv/**'];
|
|
35
|
+
const TEMP_FILES = ['*.log', '*.tmp', '*.temp', '*.swp'];
|
|
36
|
+
const GENERATED_FILES = ['*.map', '*.class', '*.o', '*.pyc', '*.dll', '*.exe'];
|
|
37
|
+
exports.NOISE_PATTERNS = [
|
|
38
|
+
...LOCK_FILES,
|
|
39
|
+
...BUILD_DIRS,
|
|
40
|
+
...CACHE_DIRS,
|
|
41
|
+
...DEP_DIRS,
|
|
42
|
+
...TEMP_FILES,
|
|
43
|
+
...GENERATED_FILES,
|
|
44
|
+
];
|
|
45
|
+
const minimatch_1 = require("minimatch");
|
|
46
|
+
const isNoiseFile = (file) => {
|
|
47
|
+
return exports.NOISE_PATTERNS.some((pattern) => (0, minimatch_1.minimatch)(file, pattern));
|
|
48
|
+
};
|
|
49
|
+
exports.isNoiseFile = isNoiseFile;
|
|
50
|
+
const filterNoiseFiles = (files) => {
|
|
51
|
+
const meaningful = files.filter((f) => !(0, exports.isNoiseFile)(f));
|
|
52
|
+
return meaningful.length ? meaningful : files;
|
|
53
|
+
};
|
|
54
|
+
exports.filterNoiseFiles = filterNoiseFiles;
|
|
@@ -0,0 +1,25 @@
|
|
|
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.printFileList = void 0;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const helper_1 = require("./helper");
|
|
9
|
+
const printFileList = (filesList) => {
|
|
10
|
+
filesList.forEach(({ status, file }) => {
|
|
11
|
+
if (status === '??' || status === 'A') {
|
|
12
|
+
(0, helper_1.log)(chalk_1.default.green(file));
|
|
13
|
+
}
|
|
14
|
+
else if (status === 'M') {
|
|
15
|
+
(0, helper_1.log)(chalk_1.default.yellow(file));
|
|
16
|
+
}
|
|
17
|
+
else if (status === 'D') {
|
|
18
|
+
(0, helper_1.log)(chalk_1.default.red(file));
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
(0, helper_1.log)(file);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
exports.printFileList = printFileList;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.pRPrompt = exports.buildBranchPromptGemma = exports.buildCommitPromptGemma = exports.buildBranchPrompt = exports.buildCommitPrompt = void 0;
|
|
4
|
+
const buildCommitPrompt = (summary) => {
|
|
5
|
+
const formatted = summary
|
|
6
|
+
.map((s) => `
|
|
7
|
+
File: ${s.file}
|
|
8
|
+
+${s.additions} -${s.deletions}
|
|
9
|
+
Signals: ${s.signals.join(', ')}
|
|
10
|
+
|
|
11
|
+
Snippet:
|
|
12
|
+
${s.snippet.join('\n')}
|
|
13
|
+
`)
|
|
14
|
+
.join('\n');
|
|
15
|
+
return `
|
|
16
|
+
Generate a conventional commit message.
|
|
17
|
+
|
|
18
|
+
Rules:
|
|
19
|
+
- one line
|
|
20
|
+
- under 72 characters
|
|
21
|
+
- conventional commit format
|
|
22
|
+
|
|
23
|
+
Changes:
|
|
24
|
+
${formatted}
|
|
25
|
+
`;
|
|
26
|
+
};
|
|
27
|
+
exports.buildCommitPrompt = buildCommitPrompt;
|
|
28
|
+
const buildBranchPrompt = (summary, existingBranches, currentBranch, commitMessage) => {
|
|
29
|
+
const changeList = summary
|
|
30
|
+
.map((s, i) => {
|
|
31
|
+
const signals = s.signals.length ? s.signals.join(', ') : 'code change';
|
|
32
|
+
return `${i + 1}. ${s.file} : ${signals}`;
|
|
33
|
+
})
|
|
34
|
+
.join('\n');
|
|
35
|
+
const branchList = existingBranches.slice(0, 30).join('\n'); // limit tokens
|
|
36
|
+
return `
|
|
37
|
+
You are a Git expert.
|
|
38
|
+
|
|
39
|
+
Generate a short git branch name that summarizes the main intent of the change.
|
|
40
|
+
Focus on the most important modification.
|
|
41
|
+
|
|
42
|
+
Rules:
|
|
43
|
+
- use kebab-case
|
|
44
|
+
- max 40 characters
|
|
45
|
+
- prefix with:
|
|
46
|
+
feature/
|
|
47
|
+
fix/
|
|
48
|
+
refactor/
|
|
49
|
+
chore/
|
|
50
|
+
docs/
|
|
51
|
+
- avoid existing branch names
|
|
52
|
+
- return ONLY the branch name
|
|
53
|
+
- no explanation
|
|
54
|
+
- no quotes
|
|
55
|
+
|
|
56
|
+
Current commit message:
|
|
57
|
+
${commitMessage}
|
|
58
|
+
|
|
59
|
+
Current branch:
|
|
60
|
+
${currentBranch}
|
|
61
|
+
|
|
62
|
+
Existing branches:
|
|
63
|
+
${branchList}
|
|
64
|
+
|
|
65
|
+
Changes:
|
|
66
|
+
${changeList}
|
|
67
|
+
|
|
68
|
+
Examples:
|
|
69
|
+
feature/add-commit-generator
|
|
70
|
+
fix/git-diff-parser
|
|
71
|
+
refactor/commit-analysis-pipeline
|
|
72
|
+
chore/update-config
|
|
73
|
+
docs/update-readme
|
|
74
|
+
`;
|
|
75
|
+
};
|
|
76
|
+
exports.buildBranchPrompt = buildBranchPrompt;
|
|
77
|
+
const buildCommitPromptGemma = (summary) => {
|
|
78
|
+
const formatted = summary
|
|
79
|
+
.map((s) => `
|
|
80
|
+
File: ${s.file}
|
|
81
|
+
Additions: ${s.additions}
|
|
82
|
+
Deletions: ${s.deletions}
|
|
83
|
+
Signals: ${s.signals.join(', ')}
|
|
84
|
+
`)
|
|
85
|
+
.join('\n');
|
|
86
|
+
return `
|
|
87
|
+
You generate git commit messages.
|
|
88
|
+
|
|
89
|
+
Task:
|
|
90
|
+
Write a Conventional Commit message describing the changes.
|
|
91
|
+
|
|
92
|
+
Rules:
|
|
93
|
+
- one line only
|
|
94
|
+
- under 72 characters
|
|
95
|
+
- imperative tense
|
|
96
|
+
- lowercase commit type
|
|
97
|
+
- no explanation
|
|
98
|
+
- no additional text
|
|
99
|
+
|
|
100
|
+
Format:
|
|
101
|
+
type: message
|
|
102
|
+
|
|
103
|
+
Examples:
|
|
104
|
+
feat: add commit generation using AI
|
|
105
|
+
fix: correct git diff parsing
|
|
106
|
+
refactor: simplify diff analyzer logic
|
|
107
|
+
chore: update dependencies
|
|
108
|
+
|
|
109
|
+
Changes:
|
|
110
|
+
${formatted}
|
|
111
|
+
|
|
112
|
+
Output exactly one line like:
|
|
113
|
+
feat: add ollama integration
|
|
114
|
+
`;
|
|
115
|
+
};
|
|
116
|
+
exports.buildCommitPromptGemma = buildCommitPromptGemma;
|
|
117
|
+
const buildBranchPromptGemma = (summary, existingBranches, currentBranch, commitMessage) => {
|
|
118
|
+
const changeList = summary
|
|
119
|
+
.map((s, i) => {
|
|
120
|
+
const signals = s.signals.length ? s.signals.join(', ') : 'code change';
|
|
121
|
+
return `${i + 1}. ${s.file}: ${signals}`;
|
|
122
|
+
})
|
|
123
|
+
.join('\n');
|
|
124
|
+
const branchList = existingBranches.slice(0, 30).join('\n');
|
|
125
|
+
return `
|
|
126
|
+
You generate git branch names.
|
|
127
|
+
|
|
128
|
+
Goal:
|
|
129
|
+
Create a concise branch name describing the change.
|
|
130
|
+
|
|
131
|
+
Rules:
|
|
132
|
+
- kebab-case
|
|
133
|
+
- max 40 characters
|
|
134
|
+
- must start with:
|
|
135
|
+
feature/
|
|
136
|
+
fix/
|
|
137
|
+
refactor/
|
|
138
|
+
chore/
|
|
139
|
+
docs/
|
|
140
|
+
- do not match existing branches
|
|
141
|
+
- no explanation
|
|
142
|
+
- no quotes
|
|
143
|
+
|
|
144
|
+
Commit message:
|
|
145
|
+
${commitMessage}
|
|
146
|
+
|
|
147
|
+
Current branch:
|
|
148
|
+
${currentBranch}
|
|
149
|
+
|
|
150
|
+
Existing branches:
|
|
151
|
+
${branchList}
|
|
152
|
+
|
|
153
|
+
Changed files:
|
|
154
|
+
${changeList}
|
|
155
|
+
|
|
156
|
+
Output format:
|
|
157
|
+
feature/branch-name
|
|
158
|
+
|
|
159
|
+
Examples:
|
|
160
|
+
feature/add-commit-generator
|
|
161
|
+
fix/git-diff-parser
|
|
162
|
+
refactor/commit-analysis-pipeline
|
|
163
|
+
chore/update-config
|
|
164
|
+
docs/update-readme
|
|
165
|
+
|
|
166
|
+
Return ONLY the branch name.
|
|
167
|
+
`;
|
|
168
|
+
};
|
|
169
|
+
exports.buildBranchPromptGemma = buildBranchPromptGemma;
|
|
170
|
+
const pRPrompt = ({ commitMessage, branchName, summary, }) => {
|
|
171
|
+
return `
|
|
172
|
+
You are an expert software engineer.
|
|
173
|
+
|
|
174
|
+
Based on the following code changes, generate a high-quality pull request.
|
|
175
|
+
|
|
176
|
+
Inputs:
|
|
177
|
+
- Commit message: ${commitMessage}
|
|
178
|
+
- Branch name: ${branchName}
|
|
179
|
+
- File changes:
|
|
180
|
+
${JSON.stringify(summary, null, 2)}
|
|
181
|
+
|
|
182
|
+
Instructions:
|
|
183
|
+
- Write a clear and concise PR title
|
|
184
|
+
- Write a structured PR description
|
|
185
|
+
- Do NOT include unnecessary explanations
|
|
186
|
+
- Do NOT repeat the same information
|
|
187
|
+
- Keep it professional and minimal
|
|
188
|
+
- Keep total description under 120 words
|
|
189
|
+
|
|
190
|
+
Output format:
|
|
191
|
+
|
|
192
|
+
TITLE:
|
|
193
|
+
<one-line PR title>
|
|
194
|
+
|
|
195
|
+
DESCRIPTION:
|
|
196
|
+
## Summary
|
|
197
|
+
<what this PR does>
|
|
198
|
+
|
|
199
|
+
## Changes
|
|
200
|
+
<bullet list of key changes>
|
|
201
|
+
|
|
202
|
+
## Notes
|
|
203
|
+
<optional, only if needed>
|
|
204
|
+
`;
|
|
205
|
+
};
|
|
206
|
+
exports.pRPrompt = pRPrompt;
|
|
@@ -0,0 +1,17 @@
|
|
|
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 startCommit_1 = __importDefault(require("../commands/commit/startCommit"));
|
|
7
|
+
const git_1 = require("./git");
|
|
8
|
+
exports.default = async (payload) => {
|
|
9
|
+
console.log({ payload });
|
|
10
|
+
if (payload?.length) {
|
|
11
|
+
await (0, git_1.stageFiles)(payload);
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
await (0, git_1.stageAll)();
|
|
15
|
+
}
|
|
16
|
+
await (0, startCommit_1.default)();
|
|
17
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
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("./helper");
|
|
8
|
+
const inputs_1 = require("./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
|
+
console.log('args');
|
|
14
|
+
if (args['show']) {
|
|
15
|
+
console.log('SHOWING');
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (args['add-key']) {
|
|
19
|
+
const apiKey = await (0, inputs_1.askApiKey)();
|
|
20
|
+
(0, helper_1.saveApiKey)(apiKey);
|
|
21
|
+
(0, helper_1.log)('API key saved!');
|
|
22
|
+
}
|
|
23
|
+
else if (args['delete-key']) {
|
|
24
|
+
if ((0, helper_1.deleteApiKey)()) {
|
|
25
|
+
(0, helper_1.log)(chalk_1.default.green('API Key Deleted'));
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
(0, helper_1.log)(chalk_1.default.red('API Key Could Not Be Deleted. API KEY NOT FOUND!'));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
(0, helper_1.log)(chalk_1.default.yellow('Unrecognized config option. Here are the extracted args for your logic:'));
|
|
33
|
+
console.log(args);
|
|
34
|
+
}
|
|
35
|
+
};
|
package/docs/commands.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# AI-Ship CLI Commands
|
|
2
|
+
|
|
3
|
+
AI-Ship is a CLI tool that uses AI + Git diff intelligence to automate commits and branches.
|
|
4
|
+
|
|
5
|
+
This document outlines all the available commands and flags you can use with the `ai-ship` CLI.
|
|
6
|
+
|
|
7
|
+
## Core Commands
|
|
8
|
+
|
|
9
|
+
### `ai-ship commit`
|
|
10
|
+
|
|
11
|
+
Generates an AI-powered commit message based on your staged Git changes, automatically commits the changes, and optionally generates and checks out a new branch based on the commit context.
|
|
12
|
+
|
|
13
|
+
AI-Ship analyzes actual Git diffs (not just filenames) to generate context-aware commit messages.
|
|
14
|
+
|
|
15
|
+
**Usage:**
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
ai-ship commit [file1] [file2] ... [flags]
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
_Note: If no files are specified, it will stage all changed files (`git add .`). If files are specified, only those specific files are staged before running the commit process._
|
|
22
|
+
|
|
23
|
+
**Available Flags:**
|
|
24
|
+
|
|
25
|
+
- `--model <provider>`: Overrides your default config to use a specific AI provider for this run (e.g., `--model local` to use Ollama/Gemma, or `--model cloud` for Gemini).
|
|
26
|
+
- `--new-branch`: Automatically generates a relevant branch name from the AI analysis, creates the new branch, and checks it out.
|
|
27
|
+
- `--push`: Automatically pushes the committed changes to your remote tracking repository. If an upstream branch is not found, it automatically creates one for you (`git push --set-upstream origin <branchName>`).
|
|
28
|
+
- `--yes`: Skips the interactive prompts for both commit message generation and branch creation, automatically accepting the first AI-generated suggestion.
|
|
29
|
+
- `--dry-run`: Simulates the process. It will generate the commit message and branch name, but will intentionally skip actually committing the files and creating the branch. Also unstages files if they were tracked during the command run.
|
|
30
|
+
|
|
31
|
+
### `ai-ship config`
|
|
32
|
+
|
|
33
|
+
Manages the global configuration for your `ai-ship` setup (such as API keys, default models, and user preferences).
|
|
34
|
+
|
|
35
|
+
**Usage:**
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
ai-ship config <sub-command> [flags]
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
#### `config set`
|
|
42
|
+
|
|
43
|
+
Sets a specific configuration key.
|
|
44
|
+
|
|
45
|
+
**Usage:**
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
ai-ship config set <key> <value>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
_Example: `ai-ship config set default-model local`_
|
|
52
|
+
|
|
53
|
+
#### `config get`
|
|
54
|
+
|
|
55
|
+
Retrieves the value of a specific configuration key.
|
|
56
|
+
|
|
57
|
+
**Usage:**
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
ai-ship config get <key>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
#### `config show`
|
|
64
|
+
|
|
65
|
+
Displays your entire active configuration.
|
|
66
|
+
|
|
67
|
+
**Available Flags:**
|
|
68
|
+
|
|
69
|
+
- `--verbose`: Pretty-prints the config with color and readable formatting.
|
|
70
|
+
- `--json`: Prints the config as raw JSON.
|
|
71
|
+
|
|
72
|
+
#### Configuration Flags
|
|
73
|
+
|
|
74
|
+
Other utility flags applicable when invoking `ai-ship config`:
|
|
75
|
+
|
|
76
|
+
- `--add-key`: Interactively prompts you to add your AI Provider API key (e.g. Gemini API Key).
|
|
77
|
+
- `--delete-key`: Instantly removes your stored API key.
|
|
78
|
+
|
|
79
|
+
## Quick Examples
|
|
80
|
+
|
|
81
|
+
**1. Fast auto-commit:**
|
|
82
|
+
Stages everything, generates a message, generates a branch and skips all interactive approvals.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
ai-ship commit --new-branch --yes
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**2. Preview what the AI thinks:**
|
|
89
|
+
See the AI-generated summary without altering the Git history.
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
ai-ship commit --dry-run
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**3. Override the default model:**
|
|
96
|
+
Normally you use cloud, but today you want to test generating with your local Ollama setup.
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
ai-ship commit --model local
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Coming Soon
|
|
103
|
+
|
|
104
|
+
### `ai-ship pr`
|
|
105
|
+
|
|
106
|
+
Generate pull request titles and descriptions using commit history and diff analysis.
|