clawt 2.16.5 → 2.17.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/README.md CHANGED
@@ -238,6 +238,20 @@ clawt config set terminalApp iterm2
238
238
  clawt config get maxConcurrency
239
239
  ```
240
240
 
241
+ ### `clawt completion` — Shell 自动补全
242
+
243
+ 为终端提供命令、子命令、选项,甚至分支名和配置项的自动补全功能。
244
+
245
+ ```bash
246
+ # 自动安装补全脚本(推荐)
247
+ clawt completion install
248
+
249
+ # 或手动将脚本添加到你的 shell 配置文件
250
+ clawt completion bash >> ~/.bashrc
251
+ clawt completion zsh >> ~/.zshrc
252
+ ```
253
+ > **支持特性:** 所有子命令、选项、`-b` 参数自动补全本地 `worktree` 分支名、`-f` 参数自动补全文件路径,以及 `config set/get` 键名自动补全。
254
+
241
255
  ### `clawt alias` — 管理命令别名
242
256
 
243
257
  ```bash
package/dist/index.js CHANGED
@@ -216,9 +216,6 @@ ${branches.map((b) => ` - ${b}`).join("\n")}`,
216
216
  VALIDATE_CONFIRM_AUTO_SYNC: (branch) => `\u662F\u5426\u7ACB\u5373\u6267\u884C sync \u540C\u6B65\u4E3B\u5206\u652F\u5230 ${branch}\uFF1F`,
217
217
  /** 自动 sync 开始提示 */
218
218
  VALIDATE_AUTO_SYNC_START: (branch) => `\u6B63\u5728\u81EA\u52A8\u540C\u6B65\u4E3B\u5206\u652F\u5230 ${branch} ...`,
219
- /** 自动 sync 存在冲突,无法重试 */
220
- VALIDATE_AUTO_SYNC_CONFLICT: (worktreePath) => `\u540C\u6B65\u5B58\u5728\u51B2\u7A81\uFF0C\u8BF7\u8FDB\u5165\u76EE\u6807 worktree \u624B\u52A8\u89E3\u51B3\u51B2\u7A81\u540E\u91CD\u8BD5
221
- cd ${worktreePath}`,
222
219
  /** 用户拒绝自动 sync */
223
220
  VALIDATE_AUTO_SYNC_DECLINED: (branch) => `\u8BF7\u624B\u52A8\u6267\u884C clawt sync -b ${branch} \u540C\u6B65\u4E3B\u5206\u652F\u540E\u91CD\u8BD5`
224
221
  };
@@ -375,6 +372,28 @@ var ALIAS_MESSAGES = {
375
372
  ALIAS_LIST_TITLE: "\u5F53\u524D\u522B\u540D\u5217\u8868\uFF1A"
376
373
  };
377
374
 
375
+ // src/constants/messages/completion.ts
376
+ var COMPLETION_MESSAGES = {
377
+ /** completion 命令的主描述 */
378
+ COMPLETION_COMMAND_DESC: "\u751F\u6210\u548C\u5B89\u88C5 shell \u81EA\u52A8\u8865\u5168\u811A\u672C",
379
+ /** bash 子命令描述 */
380
+ COMPLETION_BASH_DESC: "\u8F93\u51FA bash \u81EA\u52A8\u8865\u5168\u811A\u672C",
381
+ /** zsh 子命令描述 */
382
+ COMPLETION_ZSH_DESC: "\u8F93\u51FA zsh \u81EA\u52A8\u8865\u5168\u811A\u672C",
383
+ /** install 子命令描述 */
384
+ COMPLETION_INSTALL_DESC: "\u81EA\u52A8\u5B89\u88C5\u8865\u5168\u811A\u672C\u5230\u5F53\u524D\u7528\u6237\u7684 shell \u914D\u7F6E\u6587\u4EF6",
385
+ /** 安装成功提示 */
386
+ COMPLETION_INSTALL_SUCCESS: "\u81EA\u52A8\u8865\u5168\u914D\u7F6E\u5DF2\u6210\u529F\u5199\u5165",
387
+ /** 安装失败或未知的 shell 提示 */
388
+ COMPLETION_INSTALL_UNKNOWN_SHELL: "\u672A\u77E5\u7684 Shell \u73AF\u5883\u6216\u65E0\u6CD5\u81EA\u52A8\u5B89\u88C5\uFF0C\u8BF7\u624B\u52A8\u914D\u7F6E\u3002",
389
+ /** 补全配置已存在提示 */
390
+ COMPLETION_INSTALL_EXISTS: "\u81EA\u52A8\u8865\u5168\u914D\u7F6E\u5DF2\u5B58\u5728\u4E8E\u76EE\u6807\u6587\u4EF6\u4E2D",
391
+ /** 提示用户重启生效 */
392
+ COMPLETION_INSTALL_RESTART: (filePath) => `\u8BF7\u91CD\u542F\u7EC8\u7AEF\u6216\u8FD0\u884C \`source ${filePath}\` \u4EE5\u4F7F\u8865\u5168\u751F\u6548\u3002`,
393
+ /** 安装写入失败提示 */
394
+ COMPLETION_INSTALL_WRITE_ERROR: (filePath) => `\u65E0\u6CD5\u5199\u5165\u6587\u4EF6 ${filePath}\uFF0C\u8BF7\u68C0\u67E5\u6587\u4EF6\u6743\u9650\u6216\u624B\u52A8\u914D\u7F6E\u3002`
395
+ };
396
+
378
397
  // src/constants/messages/index.ts
379
398
  var MESSAGES = {
380
399
  ...COMMON_MESSAGES,
@@ -388,7 +407,8 @@ var MESSAGES = {
388
407
  ...RESET_MESSAGES,
389
408
  ...CONFIG_CMD_MESSAGES,
390
409
  ...STATUS_MESSAGES,
391
- ...ALIAS_MESSAGES
410
+ ...ALIAS_MESSAGES,
411
+ ...COMPLETION_MESSAGES
392
412
  };
393
413
 
394
414
  // src/constants/exitCodes.ts
@@ -622,7 +642,7 @@ function parseParallelCommands(commandString) {
622
642
  }
623
643
  function runParallelCommands(commands, options) {
624
644
  const promises = commands.map((command) => {
625
- return new Promise((resolve2) => {
645
+ return new Promise((resolve3) => {
626
646
  logger.debug(`\u5E76\u884C\u542F\u52A8\u547D\u4EE4: ${command}${options?.cwd ? ` (cwd: ${options.cwd})` : ""}`);
627
647
  const child = spawn(command, {
628
648
  cwd: options?.cwd,
@@ -630,10 +650,10 @@ function runParallelCommands(commands, options) {
630
650
  shell: true
631
651
  });
632
652
  child.on("error", (err) => {
633
- resolve2({ command, exitCode: 1, error: err.message });
653
+ resolve3({ command, exitCode: 1, error: err.message });
634
654
  });
635
655
  child.on("close", (code) => {
636
- resolve2({ command, exitCode: code ?? 1 });
656
+ resolve3({ command, exitCode: code ?? 1 });
637
657
  });
638
658
  });
639
659
  });
@@ -851,14 +871,14 @@ function printDoubleSeparator() {
851
871
  console.log(MESSAGES.DOUBLE_SEPARATOR);
852
872
  }
853
873
  function confirmAction(question) {
854
- return new Promise((resolve2) => {
874
+ return new Promise((resolve3) => {
855
875
  const rl = createInterface({
856
876
  input: process.stdin,
857
877
  output: process.stdout
858
878
  });
859
879
  rl.question(`${question} (y/N) `, (answer) => {
860
880
  rl.close();
861
- resolve2(answer.toLowerCase() === "y");
881
+ resolve3(answer.toLowerCase() === "y");
862
882
  });
863
883
  });
864
884
  }
@@ -1835,8 +1855,8 @@ function formatActivityText(kind, toolName, input, text) {
1835
1855
  const filePath = input?.file_path;
1836
1856
  const command = input?.command;
1837
1857
  if (filePath) {
1838
- const basename2 = path.basename(filePath);
1839
- raw = `${toolName} ${basename2}`;
1858
+ const basename3 = path.basename(filePath);
1859
+ raw = `${toolName} ${basename3}`;
1840
1860
  } else if (command) {
1841
1861
  const cleaned = command.replace(/[\n\r\t]+/g, " ").trim();
1842
1862
  raw = `${toolName} ${cleaned}`;
@@ -1915,7 +1935,7 @@ function executeClaudeTask(worktree, task, onActivity) {
1915
1935
  stdio: ["ignore", "pipe", "pipe"]
1916
1936
  }
1917
1937
  );
1918
- const promise = new Promise((resolve2) => {
1938
+ const promise = new Promise((resolve3) => {
1919
1939
  let stderr = "";
1920
1940
  let finalResult = null;
1921
1941
  const lineBuffer = createLineBuffer();
@@ -1951,7 +1971,7 @@ function executeClaudeTask(worktree, task, onActivity) {
1951
1971
  if (finalResult) {
1952
1972
  success = !finalResult.is_error;
1953
1973
  }
1954
- resolve2({
1974
+ resolve3({
1955
1975
  task,
1956
1976
  branch: worktree.branch,
1957
1977
  worktreePath: worktree.path,
@@ -1961,7 +1981,7 @@ function executeClaudeTask(worktree, task, onActivity) {
1961
1981
  });
1962
1982
  });
1963
1983
  child.on("error", (err) => {
1964
- resolve2({
1984
+ resolve3({
1965
1985
  task,
1966
1986
  branch: worktree.branch,
1967
1987
  worktreePath: worktree.path,
@@ -2020,7 +2040,7 @@ async function executeWithConcurrency(worktrees, tasks, concurrency, renderer, s
2020
2040
  const results = new Array(total);
2021
2041
  let nextIndex = 0;
2022
2042
  let completedCount = 0;
2023
- return new Promise((resolve2) => {
2043
+ return new Promise((resolve3) => {
2024
2044
  function launchNext() {
2025
2045
  if (nextIndex >= total || isInterrupted()) return;
2026
2046
  const index = nextIndex;
@@ -2041,7 +2061,7 @@ async function executeWithConcurrency(worktrees, tasks, concurrency, renderer, s
2041
2061
  }
2042
2062
  launchNext();
2043
2063
  if (completedCount === total) {
2044
- resolve2(results);
2064
+ resolve3(results);
2045
2065
  }
2046
2066
  });
2047
2067
  }
@@ -2096,11 +2116,11 @@ async function executeBatchTasks(worktrees, tasks, concurrency) {
2096
2116
  printWarning(MESSAGES.INTERRUPTED);
2097
2117
  killAllChildProcesses(childProcesses);
2098
2118
  await Promise.allSettled(childProcesses.map(
2099
- (cp) => new Promise((resolve2) => {
2119
+ (cp) => new Promise((resolve3) => {
2100
2120
  if (cp.exitCode !== null) {
2101
- resolve2();
2121
+ resolve3();
2102
2122
  } else {
2103
- cp.on("close", () => resolve2());
2123
+ cp.on("close", () => resolve3());
2104
2124
  }
2105
2125
  })
2106
2126
  ));
@@ -2717,9 +2737,6 @@ async function handlePatchApplyFailure(targetWorktreePath, branchName) {
2717
2737
  }
2718
2738
  printInfo(MESSAGES.VALIDATE_AUTO_SYNC_START(branchName));
2719
2739
  const syncResult = executeSyncForBranch(targetWorktreePath, branchName);
2720
- if (syncResult.hasConflict) {
2721
- printWarning(MESSAGES.VALIDATE_AUTO_SYNC_CONFLICT(targetWorktreePath));
2722
- }
2723
2740
  }
2724
2741
  function saveCurrentSnapshotTree(mainWorktreePath, projectName, branchName) {
2725
2742
  gitAddAll(mainWorktreePath);
@@ -3388,6 +3405,218 @@ function registerAliasCommand(program2) {
3388
3405
  });
3389
3406
  }
3390
3407
 
3408
+ // src/commands/completion.ts
3409
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync10 } from "fs";
3410
+ import { resolve as resolve2 } from "path";
3411
+ import { homedir as homedir2 } from "os";
3412
+
3413
+ // src/utils/completion-scripts.ts
3414
+ function getBashScript() {
3415
+ return `
3416
+ _clawt_completion() {
3417
+ local IFS=$'\\n'
3418
+ local completions=$(clawt completion _complete bash "$COMP_CWORD" "\${COMP_WORDS[@]}")
3419
+ COMPREPLY=()
3420
+ local comp
3421
+ while IFS= read -r comp; do
3422
+ [ -z "$comp" ] && continue
3423
+ COMPREPLY+=("$comp")
3424
+ done <<< "$completions"
3425
+ local has_dir=0
3426
+ for comp in "\${COMPREPLY[@]}"; do
3427
+ [[ "$comp" == */ ]] && has_dir=1 && break
3428
+ done
3429
+ if (( has_dir )) && type compopt &>/dev/null; then
3430
+ compopt -o nospace
3431
+ fi
3432
+ }
3433
+ complete -o nospace -F _clawt_completion clawt
3434
+ `;
3435
+ }
3436
+ function getZshScript() {
3437
+ return `
3438
+ #compdef clawt
3439
+ _clawt_completion() {
3440
+ local completions
3441
+ local cword=$((CURRENT - 1))
3442
+ completions=("\${(@f)$(clawt completion _complete zsh "$cword" "\${words[@]}")}")
3443
+ if [[ -n "$completions" ]]; then
3444
+ local comp
3445
+ for comp in "\${completions[@]}"; do
3446
+ [[ -z "$comp" ]] && continue
3447
+ if [[ "$comp" == */ ]]; then
3448
+ compadd -S '' -- "$comp"
3449
+ else
3450
+ compadd -S ' ' -- "$comp"
3451
+ fi
3452
+ done
3453
+ fi
3454
+ }
3455
+ compdef _clawt_completion clawt
3456
+ `;
3457
+ }
3458
+
3459
+ // src/utils/completion-engine.ts
3460
+ import { existsSync as existsSync9, readdirSync as readdirSync5, statSync as statSync2 } from "fs";
3461
+ import { join as join6, dirname, basename as basename2 } from "path";
3462
+ function completeFilePath(partial) {
3463
+ const cwd = process.cwd();
3464
+ const hasDir = partial.includes("/");
3465
+ const searchDir = hasDir ? join6(cwd, dirname(partial)) : cwd;
3466
+ const prefix = hasDir ? basename2(partial) : partial;
3467
+ if (!existsSync9(searchDir)) {
3468
+ return [];
3469
+ }
3470
+ const entries = readdirSync5(searchDir);
3471
+ const results = [];
3472
+ const dirPrefix = hasDir ? dirname(partial) + "/" : "";
3473
+ for (const entry of entries) {
3474
+ if (!entry.startsWith(prefix)) continue;
3475
+ if (entry.startsWith(".")) continue;
3476
+ const fullPath = join6(searchDir, entry);
3477
+ try {
3478
+ const stat = statSync2(fullPath);
3479
+ if (stat.isDirectory()) {
3480
+ results.push(dirPrefix + entry + "/");
3481
+ } else if (stat.isFile()) {
3482
+ results.push(dirPrefix + entry);
3483
+ }
3484
+ } catch {
3485
+ }
3486
+ }
3487
+ return results;
3488
+ }
3489
+ function tryCompleteSpecialArg(previousWord, currentWord, words) {
3490
+ if (previousWord === "-b" || previousWord === "--branch") {
3491
+ try {
3492
+ const worktrees = getProjectWorktrees();
3493
+ const branches = worktrees.map((wt) => wt.branch);
3494
+ console.log(branches.filter((b) => b.startsWith(currentWord)).join("\n"));
3495
+ } catch {
3496
+ }
3497
+ return true;
3498
+ }
3499
+ if (previousWord === "-f" || previousWord === "--file") {
3500
+ try {
3501
+ const candidates = completeFilePath(currentWord);
3502
+ console.log(candidates.join("\n"));
3503
+ } catch {
3504
+ }
3505
+ return true;
3506
+ }
3507
+ if (previousWord === "set" || previousWord === "get") {
3508
+ if (words.includes("config")) {
3509
+ const keys = Object.keys(CONFIG_DEFINITIONS);
3510
+ console.log(keys.filter((k) => k.startsWith(currentWord)).join("\n"));
3511
+ return true;
3512
+ }
3513
+ }
3514
+ return false;
3515
+ }
3516
+ function completeFromCommandTree(program2, words, cword, currentWord) {
3517
+ let currentCmd = program2;
3518
+ for (let i = 1; i < cword; i++) {
3519
+ const word = words[i];
3520
+ const subCmd = currentCmd.commands.find((c) => c.name() === word || c.aliases().includes(word));
3521
+ if (subCmd) {
3522
+ currentCmd = subCmd;
3523
+ }
3524
+ }
3525
+ const completions = [];
3526
+ if (currentWord.startsWith("-")) {
3527
+ currentCmd.options.forEach((opt) => {
3528
+ if (opt.short && opt.short.startsWith(currentWord)) completions.push(opt.short);
3529
+ if (opt.long && opt.long.startsWith(currentWord)) completions.push(opt.long);
3530
+ });
3531
+ } else {
3532
+ currentCmd.commands.forEach((cmd) => {
3533
+ const name = cmd.name();
3534
+ if (name !== "_complete" && name.startsWith(currentWord)) {
3535
+ completions.push(name);
3536
+ cmd.aliases().forEach((alias) => {
3537
+ if (alias.startsWith(currentWord)) {
3538
+ completions.push(alias);
3539
+ }
3540
+ });
3541
+ }
3542
+ });
3543
+ if (!currentWord) {
3544
+ currentCmd.options.forEach((opt) => {
3545
+ if (opt.long) completions.push(opt.long);
3546
+ else if (opt.short) completions.push(opt.short);
3547
+ });
3548
+ }
3549
+ }
3550
+ console.log(Array.from(new Set(completions)).join("\n"));
3551
+ }
3552
+ function generateCompletions(program2, args) {
3553
+ const cword = parseInt(args[1], 10);
3554
+ const words = args.slice(2);
3555
+ const currentWord = words[cword] || "";
3556
+ const previousWord = cword > 0 ? words[cword - 1] : "";
3557
+ if (tryCompleteSpecialArg(previousWord, currentWord, words)) {
3558
+ return;
3559
+ }
3560
+ completeFromCommandTree(program2, words, cword, currentWord);
3561
+ }
3562
+
3563
+ // src/commands/completion.ts
3564
+ function appendToFile(filePath, content) {
3565
+ if (existsSync10(filePath)) {
3566
+ const current = readFileSync4(filePath, "utf-8");
3567
+ if (current.includes("clawt completion")) {
3568
+ printInfo(MESSAGES.COMPLETION_INSTALL_EXISTS + ": " + filePath);
3569
+ return;
3570
+ }
3571
+ content = current + content;
3572
+ }
3573
+ writeFileSync3(filePath, content, "utf-8");
3574
+ printSuccess(MESSAGES.COMPLETION_INSTALL_SUCCESS + ": " + filePath);
3575
+ printInfo(MESSAGES.COMPLETION_INSTALL_RESTART(filePath));
3576
+ }
3577
+ function installCompletions() {
3578
+ const shell = process.env.SHELL || "";
3579
+ const home = homedir2();
3580
+ try {
3581
+ if (shell.includes("zsh")) {
3582
+ const rcPath = resolve2(home, ".zshrc");
3583
+ const script = `
3584
+ # clawt completion
3585
+ source <(clawt completion zsh)
3586
+ `;
3587
+ appendToFile(rcPath, script);
3588
+ } else if (shell.includes("bash")) {
3589
+ const rcPath = resolve2(home, ".bashrc");
3590
+ const script = `
3591
+ # clawt completion
3592
+ eval "$(clawt completion bash)"
3593
+ `;
3594
+ appendToFile(rcPath, script);
3595
+ } else {
3596
+ printWarning(MESSAGES.COMPLETION_INSTALL_UNKNOWN_SHELL);
3597
+ }
3598
+ } catch (error) {
3599
+ const filePath = shell.includes("zsh") ? resolve2(home, ".zshrc") : resolve2(home, ".bashrc");
3600
+ printError(MESSAGES.COMPLETION_INSTALL_WRITE_ERROR(filePath));
3601
+ }
3602
+ }
3603
+ function registerCompletionCommand(program2) {
3604
+ const completionCommand = program2.command("completion").description(MESSAGES.COMPLETION_COMMAND_DESC);
3605
+ completionCommand.command("bash").description(MESSAGES.COMPLETION_BASH_DESC).action(() => {
3606
+ console.log(getBashScript());
3607
+ });
3608
+ completionCommand.command("zsh").description(MESSAGES.COMPLETION_ZSH_DESC).action(() => {
3609
+ console.log(getZshScript());
3610
+ });
3611
+ completionCommand.command("install").description(MESSAGES.COMPLETION_INSTALL_DESC).action(() => {
3612
+ installCompletions();
3613
+ });
3614
+ completionCommand.command("_complete [args...]").allowUnknownOption().description("\u5185\u90E8\u4F7F\u7528\u7684\u52A8\u6001\u8865\u5168\u65B9\u6CD5\uFF0C\u4E0D\u5BF9\u5916\u516C\u5F00").action((args) => {
3615
+ if (!args || args.length < 2) return;
3616
+ generateCompletions(program2, args);
3617
+ });
3618
+ }
3619
+
3391
3620
  // src/index.ts
3392
3621
  var require2 = createRequire(import.meta.url);
3393
3622
  var { version } = require2("../package.json");
@@ -3411,6 +3640,7 @@ registerSyncCommand(program);
3411
3640
  registerResetCommand(program);
3412
3641
  registerStatusCommand(program);
3413
3642
  registerAliasCommand(program);
3643
+ registerCompletionCommand(program);
3414
3644
  var config = loadConfig();
3415
3645
  applyAliases(program, config.aliases);
3416
3646
  process.on("uncaughtException", (error) => {
@@ -208,9 +208,6 @@ ${branches.map((b) => ` - ${b}`).join("\n")}`,
208
208
  VALIDATE_CONFIRM_AUTO_SYNC: (branch) => `\u662F\u5426\u7ACB\u5373\u6267\u884C sync \u540C\u6B65\u4E3B\u5206\u652F\u5230 ${branch}\uFF1F`,
209
209
  /** 自动 sync 开始提示 */
210
210
  VALIDATE_AUTO_SYNC_START: (branch) => `\u6B63\u5728\u81EA\u52A8\u540C\u6B65\u4E3B\u5206\u652F\u5230 ${branch} ...`,
211
- /** 自动 sync 存在冲突,无法重试 */
212
- VALIDATE_AUTO_SYNC_CONFLICT: (worktreePath) => `\u540C\u6B65\u5B58\u5728\u51B2\u7A81\uFF0C\u8BF7\u8FDB\u5165\u76EE\u6807 worktree \u624B\u52A8\u89E3\u51B3\u51B2\u7A81\u540E\u91CD\u8BD5
213
- cd ${worktreePath}`,
214
211
  /** 用户拒绝自动 sync */
215
212
  VALIDATE_AUTO_SYNC_DECLINED: (branch) => `\u8BF7\u624B\u52A8\u6267\u884C clawt sync -b ${branch} \u540C\u6B65\u4E3B\u5206\u652F\u540E\u91CD\u8BD5`
216
213
  };
@@ -366,6 +363,28 @@ var ALIAS_MESSAGES = {
366
363
  ALIAS_LIST_TITLE: "\u5F53\u524D\u522B\u540D\u5217\u8868\uFF1A"
367
364
  };
368
365
 
366
+ // src/constants/messages/completion.ts
367
+ var COMPLETION_MESSAGES = {
368
+ /** completion 命令的主描述 */
369
+ COMPLETION_COMMAND_DESC: "\u751F\u6210\u548C\u5B89\u88C5 shell \u81EA\u52A8\u8865\u5168\u811A\u672C",
370
+ /** bash 子命令描述 */
371
+ COMPLETION_BASH_DESC: "\u8F93\u51FA bash \u81EA\u52A8\u8865\u5168\u811A\u672C",
372
+ /** zsh 子命令描述 */
373
+ COMPLETION_ZSH_DESC: "\u8F93\u51FA zsh \u81EA\u52A8\u8865\u5168\u811A\u672C",
374
+ /** install 子命令描述 */
375
+ COMPLETION_INSTALL_DESC: "\u81EA\u52A8\u5B89\u88C5\u8865\u5168\u811A\u672C\u5230\u5F53\u524D\u7528\u6237\u7684 shell \u914D\u7F6E\u6587\u4EF6",
376
+ /** 安装成功提示 */
377
+ COMPLETION_INSTALL_SUCCESS: "\u81EA\u52A8\u8865\u5168\u914D\u7F6E\u5DF2\u6210\u529F\u5199\u5165",
378
+ /** 安装失败或未知的 shell 提示 */
379
+ COMPLETION_INSTALL_UNKNOWN_SHELL: "\u672A\u77E5\u7684 Shell \u73AF\u5883\u6216\u65E0\u6CD5\u81EA\u52A8\u5B89\u88C5\uFF0C\u8BF7\u624B\u52A8\u914D\u7F6E\u3002",
380
+ /** 补全配置已存在提示 */
381
+ COMPLETION_INSTALL_EXISTS: "\u81EA\u52A8\u8865\u5168\u914D\u7F6E\u5DF2\u5B58\u5728\u4E8E\u76EE\u6807\u6587\u4EF6\u4E2D",
382
+ /** 提示用户重启生效 */
383
+ COMPLETION_INSTALL_RESTART: (filePath) => `\u8BF7\u91CD\u542F\u7EC8\u7AEF\u6216\u8FD0\u884C \`source ${filePath}\` \u4EE5\u4F7F\u8865\u5168\u751F\u6548\u3002`,
384
+ /** 安装写入失败提示 */
385
+ COMPLETION_INSTALL_WRITE_ERROR: (filePath) => `\u65E0\u6CD5\u5199\u5165\u6587\u4EF6 ${filePath}\uFF0C\u8BF7\u68C0\u67E5\u6587\u4EF6\u6743\u9650\u6216\u624B\u52A8\u914D\u7F6E\u3002`
386
+ };
387
+
369
388
  // src/constants/messages/index.ts
370
389
  var MESSAGES = {
371
390
  ...COMMON_MESSAGES,
@@ -379,7 +398,8 @@ var MESSAGES = {
379
398
  ...RESET_MESSAGES,
380
399
  ...CONFIG_CMD_MESSAGES,
381
400
  ...STATUS_MESSAGES,
382
- ...ALIAS_MESSAGES
401
+ ...ALIAS_MESSAGES,
402
+ ...COMPLETION_MESSAGES
383
403
  };
384
404
 
385
405
  // src/constants/terminal.ts
package/docs/spec.md CHANGED
@@ -27,6 +27,7 @@
27
27
  - [5.13 重置主 Worktree 工作区和暂存区](#513-重置主-worktree-工作区和暂存区)
28
28
  - [5.14 项目全局状态总览](#514-项目全局状态总览)
29
29
  - [5.15 命令别名管理](#515-命令别名管理)
30
+ - [5.16 Shell 自动补全](#516-clawt-completion-命令)
30
31
  - [6. 错误处理规范](#6-错误处理规范)
31
32
  - [7. 非功能性需求](#7-非功能性需求)
32
33
  - [7.1 性能](#71-性能)
@@ -184,6 +185,7 @@ git show-ref --verify refs/heads/<branchName> 2>/dev/null
184
185
  | `clawt reset` | 重置主 worktree 工作区和暂存区 | 5.13 |
185
186
  | `clawt status` | 显示项目全局状态总览(支持 `--json` 格式输出) | 5.14 |
186
187
  | `clawt alias` | 管理命令别名(列出 / 设置 / 移除) | 5.15 |
188
+ | `clawt completion` | 为终端提供 shell 自动补全功能(bash/zsh) | 5.16 |
187
189
 
188
190
  **全局选项:**
189
191
 
@@ -294,7 +296,7 @@ clawt run -b <branchName>
294
296
 
295
297
  #### 任务文件格式
296
298
 
297
- 任务文件使用 Markdown 文件中嵌入 HTML 注释标签的自定义格式,标签外的任何文本都不会被解析。
299
+ 任务文件使用嵌入 HTML 注释标签的自定义格式,不限制文件类型,标签外的任何文本都不会被解析。
298
300
 
299
301
  ```markdown
300
302
  这里可以写任何说明文字,会被忽略
@@ -1758,6 +1760,62 @@ clawt alias remove l
1758
1760
 
1759
1761
  ---
1760
1762
 
1763
+ ### 5.16 `clawt completion` 命令
1764
+
1765
+ 为终端环境(bash/zsh)生成并安装 `clawt` 的命令、选项及参数的自动补全脚本。
1766
+
1767
+ #### 语法
1768
+ ```bash
1769
+ clawt completion bash
1770
+ clawt completion zsh
1771
+ clawt completion install
1772
+ ```
1773
+
1774
+ #### 子命令说明
1775
+
1776
+ | 子命令 | 说明 |
1777
+ | --------- | ----------------------------------------------------------------------------------- |
1778
+ | `bash` | 输出适用于 bash 的补全脚本(用户可重定向到 `~/.bashrc`) |
1779
+ | `zsh` | 输出适用于 zsh 的补全脚本(用户可重定向到 `~/.zshrc`) |
1780
+ | `install` | 自动检测当前 shell 类型,将补全脚本追加到对应的配置文件中 |
1781
+
1782
+ #### `install` 子命令流程
1783
+
1784
+ 1. 通过 `process.env.SHELL` 检测当前 shell 类型
1785
+ 2. 根据 shell 类型确定目标配置文件:
1786
+ - zsh → `~/.zshrc`(追加 `source <(clawt completion zsh)`)
1787
+ - bash → `~/.bashrc`(追加 `eval "$(clawt completion bash)"`)
1788
+ 3. 检查目标文件中是否已包含 `clawt completion`,已存在则跳过并提示
1789
+ 4. 追加成功后提示用户重启终端或 source 配置文件
1790
+ 5. 未知 shell 类型时输出警告,提示手动配置
1791
+
1792
+ #### 动态补全特性
1793
+
1794
+ 补全脚本通过内部子命令 `_complete` 实现动态补全,不对外公开。补全引擎基于 Commander.js 的命令树结构遍历,支持以下场景:
1795
+
1796
+ | 场景 | 补全行为 |
1797
+ | ---------------------------- | ---------------------------------------------------------- |
1798
+ | `-b` / `--branch` 参数之后 | 动态列出当前项目所有 worktree 分支名(通过 `getProjectWorktrees`) |
1799
+ | `-f` / `--file` 参数之后 | 动态列出匹配的文件和子目录(不限制文件类型,支持子目录递归浏览) |
1800
+ | `config set` / `config get` 之后 | 动态列出所有配置项键名(从 `CONFIG_DEFINITIONS` 获取) |
1801
+ | 输入以 `-` 开头 | 列出当前命令层级的可用选项(short/long) |
1802
+ | 其他情况 | 列出当前命令层级的可用子命令及别名 |
1803
+
1804
+ **文件路径补全细节:**
1805
+ - 支持子目录递归浏览(如 `tasks/` 后继续 Tab 可深入子目录)
1806
+ - 目录候选项以 `/` 结尾,补全时不自动追加空格
1807
+ - 不限制文件类型,列出所有非隐藏文件
1808
+ - 跳过隐藏文件和目录(以 `.` 开头)
1809
+
1810
+ #### 实现说明
1811
+
1812
+ - 补全命令注册函数:`registerCompletionCommand()`(在 `src/commands/completion.ts`)
1813
+ - 消息常量:`COMPLETION_MESSAGES`(在 `src/constants/messages/completion.ts`)
1814
+ - 核心函数:`generateCompletions()` 解析当前输入上下文并输出候选项,`completeFilePath()` 处理文件路径补全
1815
+ - shell 脚本生成:`getBashScript()`、`getZshScript()` 分别生成对应 shell 的补全脚本
1816
+
1817
+ ---
1818
+
1761
1819
  ## 6. 错误处理规范
1762
1820
 
1763
1821
  ### 6.1 通用错误处理
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawt",
3
- "version": "2.16.5",
3
+ "version": "2.17.1",
4
4
  "description": "本地并行执行多个Claude Code Agent任务,融合 Git Worktree 与 Claude Code CLI 的命令行工具",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -0,0 +1,98 @@
1
+ import { Command } from 'commander';
2
+ import { readFileSync, writeFileSync, existsSync } from 'node:fs';
3
+ import { resolve } from 'node:path';
4
+ import { homedir } from 'node:os';
5
+
6
+ import { MESSAGES } from '../constants/messages/index.js';
7
+ import { printSuccess, printInfo, printWarning, printError } from '../utils/index.js';
8
+ import { getBashScript, getZshScript } from '../utils/completion-scripts.js';
9
+ import { generateCompletions } from '../utils/completion-engine.js';
10
+
11
+ /**
12
+ * 向文件中追加内容(如果不存在),或者创建新文件
13
+ * @param {string} filePath - 目标文件路径
14
+ * @param {string} content - 要写入的文本内容
15
+ */
16
+ function appendToFile(filePath: string, content: string): void {
17
+ if (existsSync(filePath)) {
18
+ const current = readFileSync(filePath, 'utf-8');
19
+ if (current.includes('clawt completion')) {
20
+ printInfo(MESSAGES.COMPLETION_INSTALL_EXISTS + ': ' + filePath);
21
+ return;
22
+ }
23
+ content = current + content;
24
+ }
25
+ writeFileSync(filePath, content, 'utf-8');
26
+ printSuccess(MESSAGES.COMPLETION_INSTALL_SUCCESS + ': ' + filePath);
27
+ printInfo(MESSAGES.COMPLETION_INSTALL_RESTART(filePath));
28
+ }
29
+
30
+ /**
31
+ * 自动安装补全脚本到用户的环境变量
32
+ */
33
+ function installCompletions(): void {
34
+ const shell = process.env.SHELL || '';
35
+ const home = homedir();
36
+
37
+ try {
38
+ if (shell.includes('zsh')) {
39
+ const rcPath = resolve(home, '.zshrc');
40
+ const script = `\n# clawt completion\nsource <(clawt completion zsh)\n`;
41
+ appendToFile(rcPath, script);
42
+ } else if (shell.includes('bash')) {
43
+ const rcPath = resolve(home, '.bashrc');
44
+ // bash 3.2(macOS 默认版本)中 source <(...) 在子 shell 执行,complete 注册无法传递回父 shell
45
+ // 使用 eval "$(...)" 确保在当前 shell 中执行
46
+ const script = `\n# clawt completion\neval "$(clawt completion bash)"\n`;
47
+ appendToFile(rcPath, script);
48
+ } else {
49
+ printWarning(MESSAGES.COMPLETION_INSTALL_UNKNOWN_SHELL);
50
+ }
51
+ } catch (error) {
52
+ const filePath = shell.includes('zsh')
53
+ ? resolve(home, '.zshrc')
54
+ : resolve(home, '.bashrc');
55
+ printError(MESSAGES.COMPLETION_INSTALL_WRITE_ERROR(filePath));
56
+ }
57
+ }
58
+
59
+ /**
60
+ * 注册自动补全命令
61
+ * @param {Command} program - 根命令实例
62
+ */
63
+ export function registerCompletionCommand(program: Command): void {
64
+ const completionCommand = program
65
+ .command('completion')
66
+ .description(MESSAGES.COMPLETION_COMMAND_DESC);
67
+
68
+ completionCommand
69
+ .command('bash')
70
+ .description(MESSAGES.COMPLETION_BASH_DESC)
71
+ .action(() => {
72
+ console.log(getBashScript());
73
+ });
74
+
75
+ completionCommand
76
+ .command('zsh')
77
+ .description(MESSAGES.COMPLETION_ZSH_DESC)
78
+ .action(() => {
79
+ console.log(getZshScript());
80
+ });
81
+
82
+ completionCommand
83
+ .command('install')
84
+ .description(MESSAGES.COMPLETION_INSTALL_DESC)
85
+ .action(() => {
86
+ installCompletions();
87
+ });
88
+
89
+ completionCommand
90
+ .command('_complete [args...]')
91
+ .allowUnknownOption()
92
+ .description('内部使用的动态补全方法,不对外公开')
93
+ .action((args: string[]) => {
94
+ // args 中包含了: shell, cword, ...words
95
+ if (!args || args.length < 2) return;
96
+ generateCompletions(program, args);
97
+ });
98
+ }
@@ -188,10 +188,7 @@ async function handlePatchApplyFailure(targetWorktreePath: string, branchName: s
188
188
  printInfo(MESSAGES.VALIDATE_AUTO_SYNC_START(branchName));
189
189
  const syncResult = executeSyncForBranch(targetWorktreePath, branchName);
190
190
 
191
- if (syncResult.hasConflict) {
192
- // sync 存在冲突,提示用户手动解决
193
- printWarning(MESSAGES.VALIDATE_AUTO_SYNC_CONFLICT(targetWorktreePath));
194
- }
191
+ // sync 冲突提示已在 executeSyncForBranch 内部输出(SYNC_CONFLICT),此处无需重复提示
195
192
  }
196
193
 
197
194
  /**