@quikcommit/cli 1.0.0 → 2.0.0
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/README.md +242 -0
- package/dist/index.js +407 -44
- package/package.json +3 -3
package/README.md
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# @quikcommit/cli
|
|
2
|
+
|
|
3
|
+
AI-powered conventional commit messages. Stage your changes, run `qc`, get a perfect commit.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
git add .
|
|
7
|
+
qc
|
|
8
|
+
# → feat(auth): add OAuth2 login with GitHub and Google providers
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
[](https://www.npmjs.com/package/@quikcommit/cli)
|
|
12
|
+
[](https://opensource.org/licenses/MIT)
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install -g @quikcommit/cli
|
|
20
|
+
# or
|
|
21
|
+
bun add -g @quikcommit/cli
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Pre-built binaries** (no Node/Bun required) — download from [GitHub Releases](https://github.com/Quikcommit-Internal/public/releases/latest):
|
|
25
|
+
|
|
26
|
+
| Platform | File |
|
|
27
|
+
|----------|------|
|
|
28
|
+
| macOS Apple Silicon | `qc-darwin-arm64` |
|
|
29
|
+
| macOS Intel | `qc-darwin-x64` |
|
|
30
|
+
| Linux x64 | `qc-linux-x64` |
|
|
31
|
+
| Linux ARM64 | `qc-linux-arm64` |
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Example: macOS Apple Silicon
|
|
35
|
+
curl -fsSL https://github.com/Quikcommit-Internal/public/releases/latest/download/qc-darwin-arm64 \
|
|
36
|
+
-o /usr/local/bin/qc && chmod +x /usr/local/bin/qc
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# 1. Sign in (opens browser — GitHub or Google)
|
|
45
|
+
qc login
|
|
46
|
+
|
|
47
|
+
# 2. Stage your changes
|
|
48
|
+
git add .
|
|
49
|
+
|
|
50
|
+
# 3. Generate + commit
|
|
51
|
+
qc
|
|
52
|
+
|
|
53
|
+
# That's it.
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Usage
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
qc [command] [options]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Generate a commit (default)
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
qc # generate + commit
|
|
68
|
+
qc --message-only # preview message, don't commit
|
|
69
|
+
qc --push # generate, commit, and push
|
|
70
|
+
qc --model llama-3.3-70b # use a specific model
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Auth & status
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
qc login # sign in via browser
|
|
77
|
+
qc logout # clear credentials
|
|
78
|
+
qc status # show plan, usage, auth
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### PR descriptions *(Pro+)*
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
qc pr # generate PR description from branch commits
|
|
85
|
+
qc pr --base develop # compare against a different base branch
|
|
86
|
+
qc pr --create # generate + open PR with gh CLI
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Changelog *(Pro+)*
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
qc changelog # generate since last tag
|
|
93
|
+
qc changelog --from v1.0.0 --to HEAD # specific range
|
|
94
|
+
qc changelog --write --version 1.1.0 # write to CHANGELOG.md
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Git hook
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
qc init # install prepare-commit-msg hook
|
|
101
|
+
qc init --uninstall # remove hook
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
After `qc init`, every `git commit` auto-generates a message for you to review before saving.
|
|
105
|
+
|
|
106
|
+
### Team *(Team+ plan)*
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
qc team info # show team and members
|
|
110
|
+
qc team rules # view team commit rules
|
|
111
|
+
qc team rules --push # push local commitlint config to team
|
|
112
|
+
qc team invite alice@corp.com # invite a teammate
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Configuration
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
qc config # show current config
|
|
119
|
+
qc config set model qwen25-coder-32b
|
|
120
|
+
qc config set api_url https://api.quikcommit.dev
|
|
121
|
+
qc config reset # reset to defaults
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Plans
|
|
127
|
+
|
|
128
|
+
| Plan | Commits/mo | PR descriptions | Changelog | Team rules |
|
|
129
|
+
|------|-----------|----------------|-----------|------------|
|
|
130
|
+
| **Free** | 50 | — | — | — |
|
|
131
|
+
| **Pro** | 500 | ✓ | ✓ | — |
|
|
132
|
+
| **Team** | 2,000 | ✓ | ✓ | ✓ |
|
|
133
|
+
| **Scale** | Unlimited | ✓ | ✓ | ✓ |
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
qc upgrade # open billing page
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Models
|
|
142
|
+
|
|
143
|
+
| Model ID | Description | Min Plan |
|
|
144
|
+
|----------|-------------|----------|
|
|
145
|
+
| `qwen3-30b` | Fast, good quality | Free |
|
|
146
|
+
| `qwen25-coder-32b` | Best code understanding | Pro |
|
|
147
|
+
| `deepseek-r1-32b` | Reasoning model | Pro |
|
|
148
|
+
| `llama-3.3-70b` | Large, strong understanding | Pro |
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
qc --model qwen25-coder-32b # one-time override
|
|
152
|
+
qc config set model qwen25-coder-32b # set permanently
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Local / Self-Hosted (no subscription needed)
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
qc --use-ollama # Ollama on localhost:11434 (codellama by default)
|
|
161
|
+
qc --use-lmstudio # LM Studio on localhost:1234
|
|
162
|
+
qc --use-openrouter # OpenRouter (set api_url + credentials)
|
|
163
|
+
qc --use-cloudflare # Your own Cloudflare Worker
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
If no API key is stored but a local provider is configured, `qc` auto-falls back to it silently.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Monorepo Support
|
|
171
|
+
|
|
172
|
+
In a pnpm / npm / yarn workspace, `qc` detects which packages have staged changes and sets the commit scope automatically:
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
packages/api/src/auth.ts staged → scope: api
|
|
176
|
+
packages/ui/src/button.tsx staged → scope: ui
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Excluding Files
|
|
182
|
+
|
|
183
|
+
Create a `.qcignore` in your repo root (same syntax as `.gitignore`) to prevent certain files from being included in the diff sent to the AI:
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
# .qcignore
|
|
187
|
+
pnpm-lock.yaml
|
|
188
|
+
dist/
|
|
189
|
+
*.min.js
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Lock files and build output are excluded by default.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Environment Variables
|
|
197
|
+
|
|
198
|
+
| Variable | Description |
|
|
199
|
+
|----------|-------------|
|
|
200
|
+
| `QC_API_KEY` | Override stored API key (useful in CI) |
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
QC_API_KEY=$QC_API_KEY qc --message-only
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Configuration File
|
|
209
|
+
|
|
210
|
+
Stored at `~/.config/qc/config.json`:
|
|
211
|
+
|
|
212
|
+
```jsonc
|
|
213
|
+
{
|
|
214
|
+
"model": "qwen25-coder-32b",
|
|
215
|
+
"apiUrl": "https://api.quikcommit.dev",
|
|
216
|
+
"provider": "saas",
|
|
217
|
+
"excludes": ["*.lock"],
|
|
218
|
+
"rules": {
|
|
219
|
+
"scopes": ["api", "ui", "auth"],
|
|
220
|
+
"types": ["feat", "fix", "docs", "chore"],
|
|
221
|
+
"maxHeaderLength": 72
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Credentials are stored separately at `~/.config/qc/credentials` (mode 600).
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Links
|
|
231
|
+
|
|
232
|
+
- [GitHub](https://github.com/Quikcommit-Internal/public)
|
|
233
|
+
- [Full Documentation](https://github.com/Quikcommit-Internal/public/tree/main/docs)
|
|
234
|
+
- [Getting Started](https://github.com/Quikcommit-Internal/public/blob/main/docs/getting-started.md)
|
|
235
|
+
- [CLI Reference](https://github.com/Quikcommit-Internal/public/blob/main/docs/cli-reference.md)
|
|
236
|
+
- [Local Providers](https://github.com/Quikcommit-Internal/public/blob/main/docs/local-providers.md)
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## License
|
|
241
|
+
|
|
242
|
+
MIT
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
#!/usr/bin/env node
|
|
3
2
|
"use strict";
|
|
4
3
|
var __create = Object.create;
|
|
5
4
|
var __defProp = Object.defineProperty;
|
|
@@ -186,6 +185,13 @@ var init_api = __esm({
|
|
|
186
185
|
);
|
|
187
186
|
return { message: data.message ?? "" };
|
|
188
187
|
}
|
|
188
|
+
async generateChangeset(req) {
|
|
189
|
+
const data = await this.request("/v1/changeset", req);
|
|
190
|
+
return {
|
|
191
|
+
packages: data.packages ?? [],
|
|
192
|
+
summary: data.summary ?? ""
|
|
193
|
+
};
|
|
194
|
+
}
|
|
189
195
|
async fetchJson(endpoint, options) {
|
|
190
196
|
if (!this.apiKey) {
|
|
191
197
|
throw new Error("Not authenticated. Run `qc login` first.");
|
|
@@ -344,6 +350,32 @@ function getCommitsSince(ref, to = "HEAD") {
|
|
|
344
350
|
return { hash: hash ?? "", subject: rest.join(" ").trim() };
|
|
345
351
|
});
|
|
346
352
|
}
|
|
353
|
+
function getChangedFilesSince(base = "main") {
|
|
354
|
+
validateRef(base, "base");
|
|
355
|
+
const output = (0, import_child_process.execFileSync)("git", ["diff", `${base}..HEAD`, "--name-only"], {
|
|
356
|
+
encoding: "utf-8",
|
|
357
|
+
maxBuffer: 10 * 1024 * 1024
|
|
358
|
+
});
|
|
359
|
+
return output.trim().split("\n").filter(Boolean);
|
|
360
|
+
}
|
|
361
|
+
function getOnlineLog(base = "main") {
|
|
362
|
+
validateRef(base, "base");
|
|
363
|
+
return (0, import_child_process.execFileSync)(
|
|
364
|
+
"git",
|
|
365
|
+
["log", `${base}..HEAD`, "--oneline", "--max-count=200"],
|
|
366
|
+
{
|
|
367
|
+
encoding: "utf-8",
|
|
368
|
+
maxBuffer: 5 * 1024 * 1024
|
|
369
|
+
}
|
|
370
|
+
).trim();
|
|
371
|
+
}
|
|
372
|
+
function getFullDiff(base = "main") {
|
|
373
|
+
validateRef(base, "base");
|
|
374
|
+
return (0, import_child_process.execFileSync)("git", ["diff", `${base}..HEAD`], {
|
|
375
|
+
encoding: "utf-8",
|
|
376
|
+
maxBuffer: 10 * 1024 * 1024
|
|
377
|
+
});
|
|
378
|
+
}
|
|
347
379
|
var import_child_process, import_fs2, import_path2, import_os2, SAFE_GIT_REF;
|
|
348
380
|
var init_git = __esm({
|
|
349
381
|
"src/git.ts"() {
|
|
@@ -357,6 +389,12 @@ var init_git = __esm({
|
|
|
357
389
|
});
|
|
358
390
|
|
|
359
391
|
// src/monorepo.ts
|
|
392
|
+
var monorepo_exports = {};
|
|
393
|
+
__export(monorepo_exports, {
|
|
394
|
+
autoDetectScope: () => autoDetectScope,
|
|
395
|
+
detectWorkspace: () => detectWorkspace,
|
|
396
|
+
getPackageForFile: () => getPackageForFile
|
|
397
|
+
});
|
|
360
398
|
function findGitRoot(start) {
|
|
361
399
|
try {
|
|
362
400
|
return (0, import_child_process2.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
|
|
@@ -440,10 +478,18 @@ function matchGlobPattern(rel, pattern) {
|
|
|
440
478
|
return null;
|
|
441
479
|
}
|
|
442
480
|
const prefix = dir + "/";
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
481
|
+
const hasGlob = /\*/.test(pattern);
|
|
482
|
+
if (hasGlob) {
|
|
483
|
+
if (rel.startsWith(prefix)) {
|
|
484
|
+
const rest = rel.slice(prefix.length);
|
|
485
|
+
const pkg = rest.split("/")[0];
|
|
486
|
+
return pkg || null;
|
|
487
|
+
}
|
|
488
|
+
} else {
|
|
489
|
+
if (rel === dir || rel.startsWith(prefix)) {
|
|
490
|
+
const segments = dir.split("/").filter(Boolean);
|
|
491
|
+
return segments[segments.length - 1] ?? null;
|
|
492
|
+
}
|
|
447
493
|
}
|
|
448
494
|
return null;
|
|
449
495
|
}
|
|
@@ -734,6 +780,312 @@ var init_changelog = __esm({
|
|
|
734
780
|
}
|
|
735
781
|
});
|
|
736
782
|
|
|
783
|
+
// src/commands/changeset.ts
|
|
784
|
+
var changeset_exports = {};
|
|
785
|
+
__export(changeset_exports, {
|
|
786
|
+
changeset: () => changeset,
|
|
787
|
+
formatChangesetFile: () => formatChangesetFile,
|
|
788
|
+
generateSlug: () => generateSlug,
|
|
789
|
+
mapFilesToPackages: () => mapFilesToPackages
|
|
790
|
+
});
|
|
791
|
+
function generateSlug() {
|
|
792
|
+
const pick = (arr) => arr[Math.floor(Math.random() * arr.length)];
|
|
793
|
+
return `${pick(ADJECTIVES)}-${pick(ANIMALS)}-${pick(VERBS)}`;
|
|
794
|
+
}
|
|
795
|
+
function mapFilesToPackages(files, workspace) {
|
|
796
|
+
const dirToName = /* @__PURE__ */ new Map();
|
|
797
|
+
for (const file of files) {
|
|
798
|
+
const dirName = getPackageForFile(file, workspace);
|
|
799
|
+
if (!dirName || dirToName.has(dirName)) continue;
|
|
800
|
+
for (const pattern of workspace.packages) {
|
|
801
|
+
const hasGlob = /\*/.test(pattern);
|
|
802
|
+
const dir = pattern.replace(/\/?\*\*?$/, "").replace(/\/$/, "");
|
|
803
|
+
const pkgJsonPath = hasGlob ? (0, import_path5.join)(workspace.root, dir, dirName, "package.json") : (0, import_path5.join)(workspace.root, pattern.replace(/\/$/, ""), "package.json");
|
|
804
|
+
try {
|
|
805
|
+
const pkg = JSON.parse((0, import_fs5.readFileSync)(pkgJsonPath, "utf-8"));
|
|
806
|
+
dirToName.set(dirName, pkg.name ?? dirName);
|
|
807
|
+
break;
|
|
808
|
+
} catch {
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
if (!dirToName.has(dirName)) {
|
|
812
|
+
dirToName.set(dirName, dirName);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
return dirToName;
|
|
816
|
+
}
|
|
817
|
+
function formatChangesetFile(packages, summary) {
|
|
818
|
+
const frontmatter = packages.map((p) => `"${p.name}": ${p.bump}`).join("\n");
|
|
819
|
+
return `---
|
|
820
|
+
${frontmatter}
|
|
821
|
+
---
|
|
822
|
+
|
|
823
|
+
${summary}
|
|
824
|
+
`;
|
|
825
|
+
}
|
|
826
|
+
async function prompt(rl, question) {
|
|
827
|
+
return new Promise((resolve) => rl.question(question, resolve));
|
|
828
|
+
}
|
|
829
|
+
async function changeset(options) {
|
|
830
|
+
const base = options.base ?? "main";
|
|
831
|
+
const apiKey = getApiKey();
|
|
832
|
+
if (!apiKey) {
|
|
833
|
+
console.error("Error: Not authenticated. Run `qc login` first.");
|
|
834
|
+
process.exit(1);
|
|
835
|
+
}
|
|
836
|
+
const { detectWorkspace: detectWorkspace2 } = await Promise.resolve().then(() => (init_monorepo(), monorepo_exports));
|
|
837
|
+
const workspace = detectWorkspace2();
|
|
838
|
+
if (!workspace) {
|
|
839
|
+
console.error(
|
|
840
|
+
"No workspace packages found. Is this a pnpm monorepo?"
|
|
841
|
+
);
|
|
842
|
+
process.exit(1);
|
|
843
|
+
}
|
|
844
|
+
const changedFiles = getChangedFilesSince(base);
|
|
845
|
+
if (changedFiles.length === 0) {
|
|
846
|
+
console.error(`No changes detected vs ${base}.`);
|
|
847
|
+
process.exit(1);
|
|
848
|
+
}
|
|
849
|
+
const packageMap = mapFilesToPackages(changedFiles, workspace);
|
|
850
|
+
const packageNames = Array.from(packageMap.values());
|
|
851
|
+
if (packageNames.length === 0) {
|
|
852
|
+
console.error("No workspace packages detected in changed files.");
|
|
853
|
+
process.exit(1);
|
|
854
|
+
}
|
|
855
|
+
const commits = getOnlineLog(base);
|
|
856
|
+
const diff = getFullDiff(base);
|
|
857
|
+
const commitCount = commits.split("\n").filter(Boolean).length;
|
|
858
|
+
console.error(
|
|
859
|
+
`Analyzing changes vs ${base}... ${commitCount} commit(s), ${packageNames.length} package(s) changed`
|
|
860
|
+
);
|
|
861
|
+
const client = new ApiClient({ apiKey });
|
|
862
|
+
let result;
|
|
863
|
+
const msg = (e) => e instanceof Error ? e.message : String(e);
|
|
864
|
+
const isTransient = (m) => /invalid json|no changeset|unexpected response|ai worker|timeout|502|503|504/i.test(m);
|
|
865
|
+
let attempts = 0;
|
|
866
|
+
while (true) {
|
|
867
|
+
try {
|
|
868
|
+
result = await client.generateChangeset({
|
|
869
|
+
diff,
|
|
870
|
+
packages: packageNames,
|
|
871
|
+
commits,
|
|
872
|
+
model: options.model
|
|
873
|
+
});
|
|
874
|
+
break;
|
|
875
|
+
} catch (err) {
|
|
876
|
+
const m = msg(err);
|
|
877
|
+
if (!isTransient(m)) {
|
|
878
|
+
console.error(m);
|
|
879
|
+
process.exit(1);
|
|
880
|
+
}
|
|
881
|
+
if (attempts === 0) {
|
|
882
|
+
attempts++;
|
|
883
|
+
continue;
|
|
884
|
+
}
|
|
885
|
+
console.error(m);
|
|
886
|
+
process.exit(1);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
const resultNames = new Set(result.packages.map((p) => p.name));
|
|
890
|
+
for (const name of packageNames) {
|
|
891
|
+
if (!resultNames.has(name)) {
|
|
892
|
+
result.packages.push({ name, bump: "patch", reason: "included in changeset" });
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
console.log("");
|
|
896
|
+
for (const pkg of result.packages) {
|
|
897
|
+
console.log(` ${pkg.name.padEnd(32)} ${pkg.bump.padEnd(7)} \u2014 ${pkg.reason}`);
|
|
898
|
+
}
|
|
899
|
+
console.log("");
|
|
900
|
+
console.log(`Summary: ${result.summary}`);
|
|
901
|
+
console.log("");
|
|
902
|
+
const rl = readline.createInterface({
|
|
903
|
+
input: process.stdin,
|
|
904
|
+
output: process.stdout
|
|
905
|
+
});
|
|
906
|
+
try {
|
|
907
|
+
const answer = (await prompt(rl, "Accept all? [Y/n/edit] > ")).trim().toLowerCase();
|
|
908
|
+
if (answer === "n") {
|
|
909
|
+
console.error("Aborted.");
|
|
910
|
+
process.exit(0);
|
|
911
|
+
}
|
|
912
|
+
if (answer === "edit") {
|
|
913
|
+
for (let i = 0; i < result.packages.length; i++) {
|
|
914
|
+
const pkg = result.packages[i];
|
|
915
|
+
const response = (await prompt(rl, ` ${pkg.name} [${pkg.bump}]: major/minor/patch? > `)).trim().toLowerCase();
|
|
916
|
+
if (response === "major" || response === "minor" || response === "patch") {
|
|
917
|
+
result.packages[i] = { ...pkg, bump: response };
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
} finally {
|
|
922
|
+
rl.close();
|
|
923
|
+
}
|
|
924
|
+
const slug = generateSlug();
|
|
925
|
+
const gitRoot = getGitRoot();
|
|
926
|
+
const changesetDir = (0, import_path5.join)(gitRoot, ".changeset");
|
|
927
|
+
if (!(0, import_fs5.existsSync)(changesetDir)) {
|
|
928
|
+
(0, import_fs5.mkdirSync)(changesetDir, { recursive: true });
|
|
929
|
+
}
|
|
930
|
+
const filePath = (0, import_path5.join)(changesetDir, `${slug}.md`);
|
|
931
|
+
const content = formatChangesetFile(result.packages, result.summary);
|
|
932
|
+
(0, import_fs5.writeFileSync)(filePath, content, "utf-8");
|
|
933
|
+
console.log(`
|
|
934
|
+
\u2713 Written .changeset/${slug}.md`);
|
|
935
|
+
}
|
|
936
|
+
var import_fs5, import_path5, readline, ADJECTIVES, ANIMALS, VERBS;
|
|
937
|
+
var init_changeset = __esm({
|
|
938
|
+
"src/commands/changeset.ts"() {
|
|
939
|
+
"use strict";
|
|
940
|
+
import_fs5 = require("fs");
|
|
941
|
+
import_path5 = require("path");
|
|
942
|
+
readline = __toESM(require("readline"));
|
|
943
|
+
init_config();
|
|
944
|
+
init_api();
|
|
945
|
+
init_git();
|
|
946
|
+
init_monorepo();
|
|
947
|
+
ADJECTIVES = [
|
|
948
|
+
"bouncy",
|
|
949
|
+
"brave",
|
|
950
|
+
"calm",
|
|
951
|
+
"clean",
|
|
952
|
+
"cool",
|
|
953
|
+
"damp",
|
|
954
|
+
"epic",
|
|
955
|
+
"fair",
|
|
956
|
+
"fast",
|
|
957
|
+
"firm",
|
|
958
|
+
"flat",
|
|
959
|
+
"free",
|
|
960
|
+
"glad",
|
|
961
|
+
"gold",
|
|
962
|
+
"good",
|
|
963
|
+
"gray",
|
|
964
|
+
"huge",
|
|
965
|
+
"keen",
|
|
966
|
+
"kind",
|
|
967
|
+
"lazy",
|
|
968
|
+
"lean",
|
|
969
|
+
"lush",
|
|
970
|
+
"mild",
|
|
971
|
+
"neat",
|
|
972
|
+
"nice",
|
|
973
|
+
"noble",
|
|
974
|
+
"pure",
|
|
975
|
+
"rare",
|
|
976
|
+
"rich",
|
|
977
|
+
"safe",
|
|
978
|
+
"sharp",
|
|
979
|
+
"slim",
|
|
980
|
+
"slow",
|
|
981
|
+
"soft",
|
|
982
|
+
"swift",
|
|
983
|
+
"tall",
|
|
984
|
+
"tame",
|
|
985
|
+
"tidy",
|
|
986
|
+
"tiny",
|
|
987
|
+
"tough",
|
|
988
|
+
"trim",
|
|
989
|
+
"true",
|
|
990
|
+
"vast",
|
|
991
|
+
"warm",
|
|
992
|
+
"wild",
|
|
993
|
+
"wise"
|
|
994
|
+
];
|
|
995
|
+
ANIMALS = [
|
|
996
|
+
"ant",
|
|
997
|
+
"bear",
|
|
998
|
+
"bee",
|
|
999
|
+
"bird",
|
|
1000
|
+
"bug",
|
|
1001
|
+
"cat",
|
|
1002
|
+
"crab",
|
|
1003
|
+
"crow",
|
|
1004
|
+
"deer",
|
|
1005
|
+
"dog",
|
|
1006
|
+
"dove",
|
|
1007
|
+
"duck",
|
|
1008
|
+
"elk",
|
|
1009
|
+
"fish",
|
|
1010
|
+
"frog",
|
|
1011
|
+
"goat",
|
|
1012
|
+
"hawk",
|
|
1013
|
+
"lamb",
|
|
1014
|
+
"lark",
|
|
1015
|
+
"lion",
|
|
1016
|
+
"lynx",
|
|
1017
|
+
"mole",
|
|
1018
|
+
"moth",
|
|
1019
|
+
"mule",
|
|
1020
|
+
"owl",
|
|
1021
|
+
"pony",
|
|
1022
|
+
"puma",
|
|
1023
|
+
"raven",
|
|
1024
|
+
"slug",
|
|
1025
|
+
"snail",
|
|
1026
|
+
"swan",
|
|
1027
|
+
"toad",
|
|
1028
|
+
"vole",
|
|
1029
|
+
"wasp",
|
|
1030
|
+
"wolf",
|
|
1031
|
+
"wren",
|
|
1032
|
+
"yak"
|
|
1033
|
+
];
|
|
1034
|
+
VERBS = [
|
|
1035
|
+
"bite",
|
|
1036
|
+
"bolt",
|
|
1037
|
+
"burn",
|
|
1038
|
+
"buzz",
|
|
1039
|
+
"call",
|
|
1040
|
+
"cast",
|
|
1041
|
+
"chase",
|
|
1042
|
+
"chew",
|
|
1043
|
+
"claw",
|
|
1044
|
+
"climb",
|
|
1045
|
+
"crawl",
|
|
1046
|
+
"dart",
|
|
1047
|
+
"dash",
|
|
1048
|
+
"dive",
|
|
1049
|
+
"draw",
|
|
1050
|
+
"drift",
|
|
1051
|
+
"drop",
|
|
1052
|
+
"eat",
|
|
1053
|
+
"fall",
|
|
1054
|
+
"find",
|
|
1055
|
+
"flee",
|
|
1056
|
+
"flip",
|
|
1057
|
+
"flow",
|
|
1058
|
+
"fly",
|
|
1059
|
+
"glow",
|
|
1060
|
+
"gnaw",
|
|
1061
|
+
"growl",
|
|
1062
|
+
"howl",
|
|
1063
|
+
"hunt",
|
|
1064
|
+
"jump",
|
|
1065
|
+
"kick",
|
|
1066
|
+
"leap",
|
|
1067
|
+
"lick",
|
|
1068
|
+
"lift",
|
|
1069
|
+
"lurk",
|
|
1070
|
+
"pace",
|
|
1071
|
+
"peck",
|
|
1072
|
+
"play",
|
|
1073
|
+
"race",
|
|
1074
|
+
"roam",
|
|
1075
|
+
"roar",
|
|
1076
|
+
"roll",
|
|
1077
|
+
"run",
|
|
1078
|
+
"skim",
|
|
1079
|
+
"sniff",
|
|
1080
|
+
"soar",
|
|
1081
|
+
"spin",
|
|
1082
|
+
"swim",
|
|
1083
|
+
"wade",
|
|
1084
|
+
"walk"
|
|
1085
|
+
];
|
|
1086
|
+
}
|
|
1087
|
+
});
|
|
1088
|
+
|
|
737
1089
|
// src/commands/init.ts
|
|
738
1090
|
var init_exports = {};
|
|
739
1091
|
__export(init_exports, {
|
|
@@ -749,12 +1101,12 @@ async function init(options) {
|
|
|
749
1101
|
console.error("Error: Not a git repository");
|
|
750
1102
|
process.exit(1);
|
|
751
1103
|
}
|
|
752
|
-
const hookPath = (0,
|
|
1104
|
+
const hookPath = (0, import_path6.join)(hooksDir, "prepare-commit-msg");
|
|
753
1105
|
if (options.uninstall) {
|
|
754
|
-
if ((0,
|
|
755
|
-
const content = (0,
|
|
1106
|
+
if ((0, import_fs6.existsSync)(hookPath)) {
|
|
1107
|
+
const content = (0, import_fs6.readFileSync)(hookPath, "utf-8");
|
|
756
1108
|
if (content.includes("QuikCommit")) {
|
|
757
|
-
(0,
|
|
1109
|
+
(0, import_fs6.unlinkSync)(hookPath);
|
|
758
1110
|
console.log("QuikCommit hook removed.");
|
|
759
1111
|
} else {
|
|
760
1112
|
console.log("Hook exists but was not installed by QuikCommit. Skipping.");
|
|
@@ -764,8 +1116,8 @@ async function init(options) {
|
|
|
764
1116
|
}
|
|
765
1117
|
return;
|
|
766
1118
|
}
|
|
767
|
-
if ((0,
|
|
768
|
-
const content = (0,
|
|
1119
|
+
if ((0, import_fs6.existsSync)(hookPath)) {
|
|
1120
|
+
const content = (0, import_fs6.readFileSync)(hookPath, "utf-8");
|
|
769
1121
|
if (content.includes("QuikCommit")) {
|
|
770
1122
|
console.log("QuikCommit hook is already installed.");
|
|
771
1123
|
return;
|
|
@@ -775,17 +1127,17 @@ async function init(options) {
|
|
|
775
1127
|
);
|
|
776
1128
|
process.exit(1);
|
|
777
1129
|
}
|
|
778
|
-
(0,
|
|
779
|
-
(0,
|
|
1130
|
+
(0, import_fs6.writeFileSync)(hookPath, HOOK_CONTENT);
|
|
1131
|
+
(0, import_fs6.chmodSync)(hookPath, 493);
|
|
780
1132
|
console.log("QuikCommit hook installed.");
|
|
781
1133
|
console.log("Now just run `git commit` and a message will be generated automatically.");
|
|
782
1134
|
}
|
|
783
|
-
var
|
|
1135
|
+
var import_fs6, import_path6, import_child_process5, HOOK_CONTENT;
|
|
784
1136
|
var init_init = __esm({
|
|
785
1137
|
"src/commands/init.ts"() {
|
|
786
1138
|
"use strict";
|
|
787
|
-
|
|
788
|
-
|
|
1139
|
+
import_fs6 = require("fs");
|
|
1140
|
+
import_path6 = require("path");
|
|
789
1141
|
import_child_process5 = require("child_process");
|
|
790
1142
|
HOOK_CONTENT = `#!/bin/sh
|
|
791
1143
|
# QuikCommit - auto-generate commit messages
|
|
@@ -860,10 +1212,10 @@ function detectLocalCommitlintRules() {
|
|
|
860
1212
|
"commitlint.config.mjs"
|
|
861
1213
|
];
|
|
862
1214
|
for (const file of files) {
|
|
863
|
-
const path = (0,
|
|
864
|
-
if (!(0,
|
|
1215
|
+
const path = (0, import_path7.join)(cwd, file);
|
|
1216
|
+
if (!(0, import_fs7.existsSync)(path)) continue;
|
|
865
1217
|
try {
|
|
866
|
-
const content = (0,
|
|
1218
|
+
const content = (0, import_fs7.readFileSync)(path, "utf-8");
|
|
867
1219
|
let parsed;
|
|
868
1220
|
if (file.endsWith(".json") || file === ".commitlintrc") {
|
|
869
1221
|
parsed = JSON.parse(content);
|
|
@@ -875,10 +1227,10 @@ function detectLocalCommitlintRules() {
|
|
|
875
1227
|
} catch {
|
|
876
1228
|
}
|
|
877
1229
|
}
|
|
878
|
-
const pkgPath = (0,
|
|
879
|
-
if ((0,
|
|
1230
|
+
const pkgPath = (0, import_path7.join)(cwd, "package.json");
|
|
1231
|
+
if ((0, import_fs7.existsSync)(pkgPath)) {
|
|
880
1232
|
try {
|
|
881
|
-
const content = (0,
|
|
1233
|
+
const content = (0, import_fs7.readFileSync)(pkgPath, "utf-8");
|
|
882
1234
|
const pkg = JSON.parse(content);
|
|
883
1235
|
if (pkg.commitlint) {
|
|
884
1236
|
const rules = mapCommitlintToRules(pkg.commitlint);
|
|
@@ -941,12 +1293,12 @@ async function team(subcommand, args) {
|
|
|
941
1293
|
process.exit(1);
|
|
942
1294
|
}
|
|
943
1295
|
}
|
|
944
|
-
var
|
|
1296
|
+
var import_fs7, import_path7;
|
|
945
1297
|
var init_team = __esm({
|
|
946
1298
|
"src/commands/team.ts"() {
|
|
947
1299
|
"use strict";
|
|
948
|
-
|
|
949
|
-
|
|
1300
|
+
import_fs7 = require("fs");
|
|
1301
|
+
import_path7 = require("path");
|
|
950
1302
|
init_api();
|
|
951
1303
|
init_config();
|
|
952
1304
|
}
|
|
@@ -1072,9 +1424,9 @@ __export(local_exports, {
|
|
|
1072
1424
|
});
|
|
1073
1425
|
function getLegacyProvider() {
|
|
1074
1426
|
try {
|
|
1075
|
-
const p = (0,
|
|
1076
|
-
if ((0,
|
|
1077
|
-
const v = (0,
|
|
1427
|
+
const p = (0, import_path8.join)(CONFIG_PATH2, "provider");
|
|
1428
|
+
if ((0, import_fs8.existsSync)(p)) {
|
|
1429
|
+
const v = (0, import_fs8.readFileSync)(p, "utf-8").trim().toLowerCase();
|
|
1078
1430
|
if (["ollama", "lmstudio", "openrouter", "custom", "cloudflare"].includes(v)) {
|
|
1079
1431
|
return v;
|
|
1080
1432
|
}
|
|
@@ -1085,9 +1437,9 @@ function getLegacyProvider() {
|
|
|
1085
1437
|
}
|
|
1086
1438
|
function getLegacyBaseUrl(provider) {
|
|
1087
1439
|
try {
|
|
1088
|
-
const p = (0,
|
|
1089
|
-
if ((0,
|
|
1090
|
-
return (0,
|
|
1440
|
+
const p = (0, import_path8.join)(CONFIG_PATH2, "base_url");
|
|
1441
|
+
if ((0, import_fs8.existsSync)(p)) {
|
|
1442
|
+
return (0, import_fs8.readFileSync)(p, "utf-8").trim();
|
|
1091
1443
|
}
|
|
1092
1444
|
} catch {
|
|
1093
1445
|
}
|
|
@@ -1095,9 +1447,9 @@ function getLegacyBaseUrl(provider) {
|
|
|
1095
1447
|
}
|
|
1096
1448
|
function getLegacyModel(provider) {
|
|
1097
1449
|
try {
|
|
1098
|
-
const p = (0,
|
|
1099
|
-
if ((0,
|
|
1100
|
-
const v = (0,
|
|
1450
|
+
const p = (0, import_path8.join)(CONFIG_PATH2, "model");
|
|
1451
|
+
if ((0, import_fs8.existsSync)(p)) {
|
|
1452
|
+
const v = (0, import_fs8.readFileSync)(p, "utf-8").trim();
|
|
1101
1453
|
if (v) return v;
|
|
1102
1454
|
}
|
|
1103
1455
|
} catch {
|
|
@@ -1116,7 +1468,7 @@ function getLocalProviderConfig() {
|
|
|
1116
1468
|
return { provider, baseUrl, model, apiKey };
|
|
1117
1469
|
}
|
|
1118
1470
|
function buildUserPrompt(changes, diff, rules) {
|
|
1119
|
-
let
|
|
1471
|
+
let prompt2 = `Generate a commit message for these changes:
|
|
1120
1472
|
|
|
1121
1473
|
## File changes:
|
|
1122
1474
|
<file_changes>
|
|
@@ -1130,14 +1482,14 @@ ${diff}
|
|
|
1130
1482
|
|
|
1131
1483
|
`;
|
|
1132
1484
|
if (rules && Object.keys(rules).length > 0) {
|
|
1133
|
-
|
|
1485
|
+
prompt2 += `Rules: ${JSON.stringify(rules)}
|
|
1134
1486
|
|
|
1135
1487
|
`;
|
|
1136
1488
|
}
|
|
1137
|
-
|
|
1489
|
+
prompt2 += `Important:
|
|
1138
1490
|
- Follow conventional commit format: <type>(<scope>): <subject>
|
|
1139
1491
|
- Response should be the commit message only, no explanations`;
|
|
1140
|
-
return
|
|
1492
|
+
return prompt2;
|
|
1141
1493
|
}
|
|
1142
1494
|
function buildRequest(provider, baseUrl, userContent, diff, changes, model, apiKey, rules) {
|
|
1143
1495
|
const headers = {
|
|
@@ -1147,7 +1499,7 @@ function buildRequest(provider, baseUrl, userContent, diff, changes, model, apiK
|
|
|
1147
1499
|
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
1148
1500
|
}
|
|
1149
1501
|
if (provider === "openrouter") {
|
|
1150
|
-
headers["HTTP-Referer"] = "https://github.com/
|
|
1502
|
+
headers["HTTP-Referer"] = "https://github.com/Quikcommit-Internal/public";
|
|
1151
1503
|
headers["X-Title"] = "qc - AI Commit Message Generator";
|
|
1152
1504
|
}
|
|
1153
1505
|
let url;
|
|
@@ -1284,18 +1636,18 @@ async function runLocalCommit(messageOnly, push, modelFlag) {
|
|
|
1284
1636
|
gitPush();
|
|
1285
1637
|
}
|
|
1286
1638
|
}
|
|
1287
|
-
var
|
|
1639
|
+
var import_fs8, import_path8, import_os4, CONFIG_PATH2, PROVIDER_URLS, DEFAULT_MODELS;
|
|
1288
1640
|
var init_local = __esm({
|
|
1289
1641
|
"src/local.ts"() {
|
|
1290
1642
|
"use strict";
|
|
1291
|
-
|
|
1292
|
-
|
|
1643
|
+
import_fs8 = require("fs");
|
|
1644
|
+
import_path8 = require("path");
|
|
1293
1645
|
import_os4 = require("os");
|
|
1294
1646
|
init_config();
|
|
1295
1647
|
init_dist();
|
|
1296
1648
|
init_git();
|
|
1297
1649
|
init_monorepo();
|
|
1298
|
-
CONFIG_PATH2 = (0,
|
|
1650
|
+
CONFIG_PATH2 = (0, import_path8.join)((0, import_os4.homedir)(), CONFIG_DIR);
|
|
1299
1651
|
PROVIDER_URLS = {
|
|
1300
1652
|
ollama: "http://localhost:11434",
|
|
1301
1653
|
lmstudio: "http://localhost:1234/v1",
|
|
@@ -1326,6 +1678,7 @@ Usage:
|
|
|
1326
1678
|
qc --push Commit and push to origin
|
|
1327
1679
|
qc pr Generate PR description from branch commits
|
|
1328
1680
|
qc changelog Generate changelog from commits since last tag
|
|
1681
|
+
qc changeset Automate pnpm changeset with AI
|
|
1329
1682
|
qc init Install prepare-commit-msg hook for auto-generation
|
|
1330
1683
|
qc login Sign in via browser
|
|
1331
1684
|
qc logout Clear local credentials
|
|
@@ -1337,7 +1690,7 @@ Options:
|
|
|
1337
1690
|
-m, --message-only Generate message only
|
|
1338
1691
|
-p, --push Commit and push after generating
|
|
1339
1692
|
--api-key <key> Use this API key (overrides credentials file)
|
|
1340
|
-
--base <branch> Base branch for qc pr (default: main)
|
|
1693
|
+
--base <branch> Base branch for qc pr, qc changeset (default: main)
|
|
1341
1694
|
--create Create PR with gh CLI after qc pr
|
|
1342
1695
|
--from <ref> Start ref for qc changelog (default: latest tag)
|
|
1343
1696
|
--to <ref> End ref for qc changelog (default: HEAD)
|
|
@@ -1411,6 +1764,8 @@ function parseArgs(args) {
|
|
|
1411
1764
|
command = "config";
|
|
1412
1765
|
} else if (arg === "upgrade") {
|
|
1413
1766
|
command = "upgrade";
|
|
1767
|
+
} else if (arg === "changeset") {
|
|
1768
|
+
command = "changeset";
|
|
1414
1769
|
} else if (arg === "--model" && i + 1 < args.length) {
|
|
1415
1770
|
model = args[++i];
|
|
1416
1771
|
} else if (arg === "--local" || arg === "--use-ollama" || arg === "--use-lmstudio" || arg === "--use-openrouter" || arg === "--use-cloudflare") {
|
|
@@ -1535,6 +1890,14 @@ async function main() {
|
|
|
1535
1890
|
});
|
|
1536
1891
|
return;
|
|
1537
1892
|
}
|
|
1893
|
+
if (command === "changeset") {
|
|
1894
|
+
const { changeset: changeset2 } = await Promise.resolve().then(() => (init_changeset(), changeset_exports));
|
|
1895
|
+
await changeset2({
|
|
1896
|
+
base: values.base,
|
|
1897
|
+
model: values.model ?? getConfig().model
|
|
1898
|
+
});
|
|
1899
|
+
return;
|
|
1900
|
+
}
|
|
1538
1901
|
if (command === "init") {
|
|
1539
1902
|
const { init: init2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
1540
1903
|
await init2({ uninstall: values.uninstall });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quikcommit/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "AI-powered conventional commit messages",
|
|
5
5
|
"bin": {
|
|
6
6
|
"qc": "./dist/index.js"
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"repository": {
|
|
22
22
|
"type": "git",
|
|
23
|
-
"url": "https://github.com/
|
|
23
|
+
"url": "https://github.com/Quikcommit-Internal/public"
|
|
24
24
|
},
|
|
25
25
|
"publishConfig": {
|
|
26
26
|
"access": "public"
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"vitest": "~4.0.18"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@quikcommit/shared": "
|
|
35
|
+
"@quikcommit/shared": "2.0.0"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"build": "node build.mjs",
|