qkpr 1.0.5 → 1.0.7

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 CHANGED
@@ -24,6 +24,38 @@ pnpm add -g qkpr
24
24
  yarn global add qkpr
25
25
  ```
26
26
 
27
+ ## Claude Code Integration
28
+
29
+ qkpr provides a Claude Code skill for better AI assistant integration. To install:
30
+
31
+ ### Global Installation (Recommended)
32
+
33
+ ```bash
34
+ # From npm (if installed globally)
35
+ cp -r $(npm root -g)/qkpr/skills/qkpr ~/.claude/skills/
36
+
37
+ # From pnpm
38
+ cp -r $(pnpm root -g)/qkpr/skills/qkpr ~/.claude/skills/
39
+
40
+ # Or download directly
41
+ mkdir -p ~/.claude/skills/qkpr
42
+ curl -o ~/.claude/skills/qkpr/SKILL.md \
43
+ https://raw.githubusercontent.com/KazooTTT/qkpr/main/skills/qkpr/SKILL.md
44
+ ```
45
+
46
+ ### Project-Specific Installation
47
+
48
+ ```bash
49
+ mkdir -p .claude/skills/qkpr
50
+ curl -o .claude/skills/qkpr/SKILL.md \
51
+ https://raw.githubusercontent.com/KazooTTT/qkpr/main/skills/qkpr/SKILL.md
52
+ ```
53
+
54
+ Once installed, you can ask Claude Code to:
55
+ - "Create a PR for this branch using qkpr"
56
+ - "Generate a commit message with qkpr"
57
+ - "Suggest a branch name for my changes"
58
+
27
59
  ## Usage
28
60
 
29
61
  ### Interactive Menu
@@ -48,6 +80,8 @@ You can directly access the PR creation feature:
48
80
 
49
81
  ```bash
50
82
  qkpr pr
83
+ # or specify the target branch directly
84
+ qkpr pr main
51
85
  ```
52
86
 
53
87
  The CLI will interactively guide you through creating a pull request:
@@ -189,7 +223,11 @@ Shows an interactive menu to choose from all available features
189
223
  qkpr pr
190
224
  ```
191
225
 
192
- Directly create a pull request with interactive branch selection
226
+ Directly create a pull request with interactive branch selection. You can also specify the target branch directly:
227
+
228
+ ```bash
229
+ qkpr pr [branch]
230
+ ```
193
231
 
194
232
  ### Generate Commit Message
195
233
 
package/dist/index.mjs CHANGED
@@ -133,9 +133,10 @@ function getBranchCategory(branchName) {
133
133
  return "other";
134
134
  }
135
135
  /**
136
- * 获取分支详细信息(包含时间和分类)
136
+ * 获取分支详细信息(包含时间和分类)- 优化版本
137
137
  */
138
138
  function getBranchesWithInfo(branches) {
139
+ if (branches.length > 50) return getBranchesWithInfoBatch(branches);
139
140
  return branches.map((branchName) => {
140
141
  const { timestamp, formatted } = getBranchLastCommitTime(branchName);
141
142
  return {
@@ -147,6 +148,75 @@ function getBranchesWithInfo(branches) {
147
148
  });
148
149
  }
149
150
  /**
151
+ * 批量获取分支信息,性能优化版本
152
+ */
153
+ function getBranchesWithInfoBatch(branches) {
154
+ try {
155
+ const timestamps = execSync(branches.map((branch) => {
156
+ return `git log -1 --format=%ct origin/${branch} 2>/dev/null || echo "0"`;
157
+ }).join("; echo \"---\";"), {
158
+ encoding: "utf-8",
159
+ stdio: [
160
+ "pipe",
161
+ "pipe",
162
+ "ignore"
163
+ ]
164
+ }).split("---").map((line) => {
165
+ const timestamp = Number.parseInt(line.trim(), 10);
166
+ return Number.isNaN(timestamp) ? 0 : timestamp;
167
+ });
168
+ return branches.map((branchName, index) => {
169
+ const timestamp = timestamps[index] || 0;
170
+ const { formatted } = formatTimestamp(timestamp);
171
+ return {
172
+ name: branchName,
173
+ lastCommitTime: timestamp,
174
+ lastCommitTimeFormatted: formatted,
175
+ category: getBranchCategory(branchName)
176
+ };
177
+ });
178
+ } catch (error) {
179
+ console.warn("Batch fetch failed, falling back to individual fetch:", error);
180
+ return branches.map((branchName) => {
181
+ const { timestamp, formatted } = getBranchLastCommitTime(branchName);
182
+ return {
183
+ name: branchName,
184
+ lastCommitTime: timestamp,
185
+ lastCommitTimeFormatted: formatted,
186
+ category: getBranchCategory(branchName)
187
+ };
188
+ });
189
+ }
190
+ }
191
+ /**
192
+ * 格式化时间戳 - 提取为独立函数供批量版本使用
193
+ */
194
+ function formatTimestamp(timestamp) {
195
+ if (timestamp === 0) return {
196
+ timestamp: 0,
197
+ formatted: "unknown"
198
+ };
199
+ const date = /* @__PURE__ */ new Date(timestamp * 1e3);
200
+ const diffMs = (/* @__PURE__ */ new Date()).getTime() - date.getTime();
201
+ const diffDays = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
202
+ let formatted;
203
+ if (diffDays === 0) {
204
+ const diffHours = Math.floor(diffMs / (1e3 * 60 * 60));
205
+ if (diffHours === 0) {
206
+ const diffMinutes = Math.floor(diffMs / (1e3 * 60));
207
+ formatted = diffMinutes <= 1 ? "just now" : `${diffMinutes}m ago`;
208
+ } else formatted = `${diffHours}h ago`;
209
+ } else if (diffDays === 1) formatted = "yesterday";
210
+ else if (diffDays < 7) formatted = `${diffDays}d ago`;
211
+ else if (diffDays < 30) formatted = `${Math.floor(diffDays / 7)}w ago`;
212
+ else if (diffDays < 365) formatted = `${Math.floor(diffDays / 30)}mo ago`;
213
+ else formatted = `${Math.floor(diffDays / 365)}y ago`;
214
+ return {
215
+ timestamp,
216
+ formatted
217
+ };
218
+ }
219
+ /**
150
220
  * 解析 Git remote URL
151
221
  */
152
222
  function parseRemoteUrl(remote) {
@@ -1506,10 +1576,7 @@ var AutocompletePinPrompt = class extends Base {
1506
1576
  this.selected = this.selected > 0 ? this.selected - 1 : len - 1;
1507
1577
  this.ensureSelectedInRange();
1508
1578
  this.render();
1509
- } else {
1510
- this.render();
1511
- if (this.lastSearchTerm !== this.rl.line) this.search(this.rl.line);
1512
- }
1579
+ } else if (this.lastSearchTerm !== this.rl.line) this.search(this.rl.line);
1513
1580
  }
1514
1581
  };
1515
1582
  /**
@@ -1567,6 +1634,21 @@ async function promptBranchSelection(branches, options) {
1567
1634
  const sortingPinnedBranches = getPinnedBranches();
1568
1635
  const searchBranches = async (_answers, input = "") => {
1569
1636
  const currentPinnedBranches = getPinnedBranches();
1637
+ const addCancelOption = (list) => {
1638
+ list.push(new inquirer.Separator(" "));
1639
+ list.push({
1640
+ name: dim(" [Cancel PR creation]"),
1641
+ value: "__CANCEL__",
1642
+ short: "Cancel"
1643
+ });
1644
+ };
1645
+ const createBranchChoice = (branch) => {
1646
+ return {
1647
+ name: `${currentPinnedBranches.includes(branch.name) ? "📌" : " "} ${branch.name.padEnd(45)} ${dim(`(${branch.lastCommitTimeFormatted})`)}`,
1648
+ value: branch.name,
1649
+ short: branch.name
1650
+ };
1651
+ };
1570
1652
  const topGroupBranches = branchInfos.filter((b) => sortingPinnedBranches.includes(b.name));
1571
1653
  const bottomGroupBranches = branchInfos.filter((b) => !sortingPinnedBranches.includes(b.name));
1572
1654
  const effectiveTopGroup = filterPinned ? [] : topGroupBranches;
@@ -1577,22 +1659,15 @@ async function promptBranchSelection(branches, options) {
1577
1659
  const displayBottomGroup = bottomGroupBranches.slice(0, 100);
1578
1660
  const choices = [];
1579
1661
  [...effectiveTopGroup, ...displayBottomGroup].forEach((branch) => {
1580
- const isPinnedNow = currentPinnedBranches.includes(branch.name);
1581
- choices.push({
1582
- name: `${isPinnedNow ? "📌" : " "} ${branch.name.padEnd(45)} ${dim(`(${branch.lastCommitTimeFormatted})`)}`,
1583
- value: branch.name,
1584
- short: branch.name
1585
- });
1662
+ choices.push(createBranchChoice(branch));
1586
1663
  });
1587
- if (!filterPinned) {
1588
- choices.push(new inquirer.Separator(" "));
1589
- choices.push({
1590
- name: dim(" [Cancel PR creation]"),
1591
- value: "__CANCEL__",
1592
- short: "Cancel"
1593
- });
1594
- }
1664
+ if (!filterPinned) addCancelOption(choices);
1595
1665
  const lowerInput = input.toLowerCase();
1666
+ if (lowerInput.trim()) {
1667
+ const searchChoices = branchInfos.filter((branch) => branch.name.toLowerCase().includes(lowerInput)).map(createBranchChoice);
1668
+ addCancelOption(searchChoices);
1669
+ return searchChoices;
1670
+ }
1596
1671
  return choices.filter((choice) => {
1597
1672
  if (!choice.value || choice.value === "__CANCEL__") return true;
1598
1673
  return choice.value.toLowerCase().includes(lowerInput);
@@ -1736,7 +1811,7 @@ async function handlePinCommand(branchName) {
1736
1811
  addPinnedBranch(branchName);
1737
1812
  console.log(green(`✅ Branch '${branchName}' has been pinned`));
1738
1813
  } else {
1739
- const { getAllBranches: getAllBranches$1 } = await import("./pr-C2AR97YR.mjs");
1814
+ const { getAllBranches: getAllBranches$1 } = await import("./pr-Cv5LeZ_A.mjs");
1740
1815
  const branches = getAllBranches$1();
1741
1816
  if (branches.length === 0) {
1742
1817
  console.log(yellow("⚠️ No branches found"));
@@ -1895,7 +1970,7 @@ async function promptForUpdate(packageName$1, result) {
1895
1970
  type: "confirm",
1896
1971
  name: "shouldUpdate",
1897
1972
  message: "Would you like to update now?",
1898
- default: false
1973
+ default: true
1899
1974
  }, {
1900
1975
  type: "list",
1901
1976
  name: "packageManager",
@@ -2134,7 +2209,7 @@ async function promptPushBranch(branchName) {
2134
2209
  /**
2135
2210
  * 处理 PR 命令
2136
2211
  */
2137
- async function handlePRCommand() {
2212
+ async function handlePRCommand(targetBranchArg) {
2138
2213
  printPRBanner();
2139
2214
  const gitInfo = getGitInfo();
2140
2215
  if (!gitInfo.isGitRepo) {
@@ -2163,7 +2238,12 @@ async function handlePRCommand() {
2163
2238
  console.log(yellow("⚠️ No branches found."));
2164
2239
  return;
2165
2240
  }
2166
- const targetBranch = await promptTargetBranch(branches, gitInfo.currentBranch);
2241
+ let targetBranch = null;
2242
+ if (targetBranchArg) if (branches.includes(targetBranchArg)) {
2243
+ targetBranch = targetBranchArg;
2244
+ console.log(green(`✅ Using specified target branch: ${targetBranch}\n`));
2245
+ } else console.log(yellow(`⚠️ Branch '${targetBranchArg}' not found. Falling back to interactive selection.`));
2246
+ if (!targetBranch) targetBranch = await promptTargetBranch(branches, gitInfo.currentBranch);
2167
2247
  if (!targetBranch) return;
2168
2248
  const prInfo = createPullRequest(gitInfo.currentBranch, targetBranch, gitInfo.remoteUrl);
2169
2249
  if (!prInfo) {
@@ -2207,8 +2287,13 @@ async function handlePRCommand() {
2207
2287
  }
2208
2288
  yargs(hideBin(process.argv)).scriptName("qkpr").usage("Usage: $0 <command> [options]").command("$0", "Show interactive menu to choose features", () => {}, async () => {
2209
2289
  await showMainMenu();
2210
- }).command("pr", "🔧 Create a Pull Request with interactive branch selection", () => {}, async () => {
2211
- await handlePRCommand();
2290
+ }).command("pr [branch]", "🔧 Create a Pull Request with interactive branch selection", (yargs$1) => {
2291
+ return yargs$1.positional("branch", {
2292
+ describe: "Target branch name",
2293
+ type: "string"
2294
+ });
2295
+ }, async (argv) => {
2296
+ await handlePRCommand(argv.branch);
2212
2297
  await checkAndNotifyUpdate(packageName, version);
2213
2298
  }).command("commit", "🤖 Generate commit message using AI", () => {}, async () => {
2214
2299
  await handleCommitCommand();
@@ -2250,4 +2335,4 @@ yargs(hideBin(process.argv)).scriptName("qkpr").usage("Usage: $0 <command> [opti
2250
2335
  }).version(version).alias("v", "version").help("h").alias("h", "help").epilog("For more information, visit https://github.com/KazooTTT/qkpr").argv;
2251
2336
 
2252
2337
  //#endregion
2253
- export { generatePRMessage as a, getBranchCategory as c, getCommitsBetweenBranches as d, getGitInfo as f, generateMergeBranchName as i, getBranchLastCommitTime as l, parseRemoteUrl as m, createMergeBranch as n, generatePRUrl as o, mergeSourceToMergeBranch as p, createPullRequest as r, getAllBranches as s, copyToClipboard as t, getBranchesWithInfo as u };
2338
+ export { generateMergeBranchName as a, getAllBranches as c, getBranchesWithInfo as d, getBranchesWithInfoBatch as f, parseRemoteUrl as g, mergeSourceToMergeBranch as h, formatTimestamp as i, getBranchCategory as l, getGitInfo as m, createMergeBranch as n, generatePRMessage as o, getCommitsBetweenBranches as p, createPullRequest as r, generatePRUrl as s, copyToClipboard as t, getBranchLastCommitTime as u };
@@ -0,0 +1,3 @@
1
+ import { a as generateMergeBranchName, c as getAllBranches, d as getBranchesWithInfo, f as getBranchesWithInfoBatch, g as parseRemoteUrl, h as mergeSourceToMergeBranch, i as formatTimestamp, l as getBranchCategory, m as getGitInfo, n as createMergeBranch, o as generatePRMessage, p as getCommitsBetweenBranches, r as createPullRequest, s as generatePRUrl, t as copyToClipboard, u as getBranchLastCommitTime } from "./index.mjs";
2
+
3
+ export { copyToClipboard, createMergeBranch, createPullRequest, formatTimestamp, generateMergeBranchName, generatePRMessage, generatePRUrl, getAllBranches, getBranchCategory, getBranchLastCommitTime, getBranchesWithInfo, getBranchesWithInfoBatch, getCommitsBetweenBranches, getGitInfo, mergeSourceToMergeBranch, parseRemoteUrl };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "qkpr",
3
3
  "type": "module",
4
- "version": "1.0.5",
4
+ "version": "1.0.7",
5
5
  "description": "Create a Pull Request with interactive branch selection",
6
6
  "author": "KazooTTT <work@kazoottt.top>",
7
7
  "license": "MIT",
@@ -1,3 +0,0 @@
1
- import { a as generatePRMessage, c as getBranchCategory, d as getCommitsBetweenBranches, f as getGitInfo, i as generateMergeBranchName, l as getBranchLastCommitTime, m as parseRemoteUrl, n as createMergeBranch, o as generatePRUrl, p as mergeSourceToMergeBranch, r as createPullRequest, s as getAllBranches, t as copyToClipboard, u as getBranchesWithInfo } from "./index.mjs";
2
-
3
- export { copyToClipboard, createMergeBranch, createPullRequest, generateMergeBranchName, generatePRMessage, generatePRUrl, getAllBranches, getBranchCategory, getBranchLastCommitTime, getBranchesWithInfo, getCommitsBetweenBranches, getGitInfo, mergeSourceToMergeBranch, parseRemoteUrl };