clawt 2.16.5 → 2.17.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 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
@@ -375,6 +375,28 @@ var ALIAS_MESSAGES = {
375
375
  ALIAS_LIST_TITLE: "\u5F53\u524D\u522B\u540D\u5217\u8868\uFF1A"
376
376
  };
377
377
 
378
+ // src/constants/messages/completion.ts
379
+ var COMPLETION_MESSAGES = {
380
+ /** completion 命令的主描述 */
381
+ COMPLETION_COMMAND_DESC: "\u751F\u6210\u548C\u5B89\u88C5 shell \u81EA\u52A8\u8865\u5168\u811A\u672C",
382
+ /** bash 子命令描述 */
383
+ COMPLETION_BASH_DESC: "\u8F93\u51FA bash \u81EA\u52A8\u8865\u5168\u811A\u672C",
384
+ /** zsh 子命令描述 */
385
+ COMPLETION_ZSH_DESC: "\u8F93\u51FA zsh \u81EA\u52A8\u8865\u5168\u811A\u672C",
386
+ /** install 子命令描述 */
387
+ COMPLETION_INSTALL_DESC: "\u81EA\u52A8\u5B89\u88C5\u8865\u5168\u811A\u672C\u5230\u5F53\u524D\u7528\u6237\u7684 shell \u914D\u7F6E\u6587\u4EF6",
388
+ /** 安装成功提示 */
389
+ COMPLETION_INSTALL_SUCCESS: "\u81EA\u52A8\u8865\u5168\u914D\u7F6E\u5DF2\u6210\u529F\u5199\u5165",
390
+ /** 安装失败或未知的 shell 提示 */
391
+ COMPLETION_INSTALL_UNKNOWN_SHELL: "\u672A\u77E5\u7684 Shell \u73AF\u5883\u6216\u65E0\u6CD5\u81EA\u52A8\u5B89\u88C5\uFF0C\u8BF7\u624B\u52A8\u914D\u7F6E\u3002",
392
+ /** 补全配置已存在提示 */
393
+ COMPLETION_INSTALL_EXISTS: "\u81EA\u52A8\u8865\u5168\u914D\u7F6E\u5DF2\u5B58\u5728\u4E8E\u76EE\u6807\u6587\u4EF6\u4E2D",
394
+ /** 提示用户重启生效 */
395
+ COMPLETION_INSTALL_RESTART: (filePath) => `\u8BF7\u91CD\u542F\u7EC8\u7AEF\u6216\u8FD0\u884C \`source ${filePath}\` \u4EE5\u4F7F\u8865\u5168\u751F\u6548\u3002`,
396
+ /** 安装写入失败提示 */
397
+ 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`
398
+ };
399
+
378
400
  // src/constants/messages/index.ts
379
401
  var MESSAGES = {
380
402
  ...COMMON_MESSAGES,
@@ -388,7 +410,8 @@ var MESSAGES = {
388
410
  ...RESET_MESSAGES,
389
411
  ...CONFIG_CMD_MESSAGES,
390
412
  ...STATUS_MESSAGES,
391
- ...ALIAS_MESSAGES
413
+ ...ALIAS_MESSAGES,
414
+ ...COMPLETION_MESSAGES
392
415
  };
393
416
 
394
417
  // src/constants/exitCodes.ts
@@ -622,7 +645,7 @@ function parseParallelCommands(commandString) {
622
645
  }
623
646
  function runParallelCommands(commands, options) {
624
647
  const promises = commands.map((command) => {
625
- return new Promise((resolve2) => {
648
+ return new Promise((resolve3) => {
626
649
  logger.debug(`\u5E76\u884C\u542F\u52A8\u547D\u4EE4: ${command}${options?.cwd ? ` (cwd: ${options.cwd})` : ""}`);
627
650
  const child = spawn(command, {
628
651
  cwd: options?.cwd,
@@ -630,10 +653,10 @@ function runParallelCommands(commands, options) {
630
653
  shell: true
631
654
  });
632
655
  child.on("error", (err) => {
633
- resolve2({ command, exitCode: 1, error: err.message });
656
+ resolve3({ command, exitCode: 1, error: err.message });
634
657
  });
635
658
  child.on("close", (code) => {
636
- resolve2({ command, exitCode: code ?? 1 });
659
+ resolve3({ command, exitCode: code ?? 1 });
637
660
  });
638
661
  });
639
662
  });
@@ -851,14 +874,14 @@ function printDoubleSeparator() {
851
874
  console.log(MESSAGES.DOUBLE_SEPARATOR);
852
875
  }
853
876
  function confirmAction(question) {
854
- return new Promise((resolve2) => {
877
+ return new Promise((resolve3) => {
855
878
  const rl = createInterface({
856
879
  input: process.stdin,
857
880
  output: process.stdout
858
881
  });
859
882
  rl.question(`${question} (y/N) `, (answer) => {
860
883
  rl.close();
861
- resolve2(answer.toLowerCase() === "y");
884
+ resolve3(answer.toLowerCase() === "y");
862
885
  });
863
886
  });
864
887
  }
@@ -1835,8 +1858,8 @@ function formatActivityText(kind, toolName, input, text) {
1835
1858
  const filePath = input?.file_path;
1836
1859
  const command = input?.command;
1837
1860
  if (filePath) {
1838
- const basename2 = path.basename(filePath);
1839
- raw = `${toolName} ${basename2}`;
1861
+ const basename3 = path.basename(filePath);
1862
+ raw = `${toolName} ${basename3}`;
1840
1863
  } else if (command) {
1841
1864
  const cleaned = command.replace(/[\n\r\t]+/g, " ").trim();
1842
1865
  raw = `${toolName} ${cleaned}`;
@@ -1915,7 +1938,7 @@ function executeClaudeTask(worktree, task, onActivity) {
1915
1938
  stdio: ["ignore", "pipe", "pipe"]
1916
1939
  }
1917
1940
  );
1918
- const promise = new Promise((resolve2) => {
1941
+ const promise = new Promise((resolve3) => {
1919
1942
  let stderr = "";
1920
1943
  let finalResult = null;
1921
1944
  const lineBuffer = createLineBuffer();
@@ -1951,7 +1974,7 @@ function executeClaudeTask(worktree, task, onActivity) {
1951
1974
  if (finalResult) {
1952
1975
  success = !finalResult.is_error;
1953
1976
  }
1954
- resolve2({
1977
+ resolve3({
1955
1978
  task,
1956
1979
  branch: worktree.branch,
1957
1980
  worktreePath: worktree.path,
@@ -1961,7 +1984,7 @@ function executeClaudeTask(worktree, task, onActivity) {
1961
1984
  });
1962
1985
  });
1963
1986
  child.on("error", (err) => {
1964
- resolve2({
1987
+ resolve3({
1965
1988
  task,
1966
1989
  branch: worktree.branch,
1967
1990
  worktreePath: worktree.path,
@@ -2020,7 +2043,7 @@ async function executeWithConcurrency(worktrees, tasks, concurrency, renderer, s
2020
2043
  const results = new Array(total);
2021
2044
  let nextIndex = 0;
2022
2045
  let completedCount = 0;
2023
- return new Promise((resolve2) => {
2046
+ return new Promise((resolve3) => {
2024
2047
  function launchNext() {
2025
2048
  if (nextIndex >= total || isInterrupted()) return;
2026
2049
  const index = nextIndex;
@@ -2041,7 +2064,7 @@ async function executeWithConcurrency(worktrees, tasks, concurrency, renderer, s
2041
2064
  }
2042
2065
  launchNext();
2043
2066
  if (completedCount === total) {
2044
- resolve2(results);
2067
+ resolve3(results);
2045
2068
  }
2046
2069
  });
2047
2070
  }
@@ -2096,11 +2119,11 @@ async function executeBatchTasks(worktrees, tasks, concurrency) {
2096
2119
  printWarning(MESSAGES.INTERRUPTED);
2097
2120
  killAllChildProcesses(childProcesses);
2098
2121
  await Promise.allSettled(childProcesses.map(
2099
- (cp) => new Promise((resolve2) => {
2122
+ (cp) => new Promise((resolve3) => {
2100
2123
  if (cp.exitCode !== null) {
2101
- resolve2();
2124
+ resolve3();
2102
2125
  } else {
2103
- cp.on("close", () => resolve2());
2126
+ cp.on("close", () => resolve3());
2104
2127
  }
2105
2128
  })
2106
2129
  ));
@@ -3388,6 +3411,218 @@ function registerAliasCommand(program2) {
3388
3411
  });
3389
3412
  }
3390
3413
 
3414
+ // src/commands/completion.ts
3415
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync10 } from "fs";
3416
+ import { resolve as resolve2 } from "path";
3417
+ import { homedir as homedir2 } from "os";
3418
+
3419
+ // src/utils/completion-scripts.ts
3420
+ function getBashScript() {
3421
+ return `
3422
+ _clawt_completion() {
3423
+ local IFS=$'\\n'
3424
+ local completions=$(clawt completion _complete bash "$COMP_CWORD" "\${COMP_WORDS[@]}")
3425
+ COMPREPLY=()
3426
+ local comp
3427
+ while IFS= read -r comp; do
3428
+ [ -z "$comp" ] && continue
3429
+ COMPREPLY+=("$comp")
3430
+ done <<< "$completions"
3431
+ local has_dir=0
3432
+ for comp in "\${COMPREPLY[@]}"; do
3433
+ [[ "$comp" == */ ]] && has_dir=1 && break
3434
+ done
3435
+ if (( has_dir )) && type compopt &>/dev/null; then
3436
+ compopt -o nospace
3437
+ fi
3438
+ }
3439
+ complete -o nospace -F _clawt_completion clawt
3440
+ `;
3441
+ }
3442
+ function getZshScript() {
3443
+ return `
3444
+ #compdef clawt
3445
+ _clawt_completion() {
3446
+ local completions
3447
+ local cword=$((CURRENT - 1))
3448
+ completions=("\${(@f)$(clawt completion _complete zsh "$cword" "\${words[@]}")}")
3449
+ if [[ -n "$completions" ]]; then
3450
+ local comp
3451
+ for comp in "\${completions[@]}"; do
3452
+ [[ -z "$comp" ]] && continue
3453
+ if [[ "$comp" == */ ]]; then
3454
+ compadd -S '' -- "$comp"
3455
+ else
3456
+ compadd -S ' ' -- "$comp"
3457
+ fi
3458
+ done
3459
+ fi
3460
+ }
3461
+ compdef _clawt_completion clawt
3462
+ `;
3463
+ }
3464
+
3465
+ // src/utils/completion-engine.ts
3466
+ import { existsSync as existsSync9, readdirSync as readdirSync5, statSync as statSync2 } from "fs";
3467
+ import { join as join6, dirname, basename as basename2 } from "path";
3468
+ function completeFilePath(partial) {
3469
+ const cwd = process.cwd();
3470
+ const hasDir = partial.includes("/");
3471
+ const searchDir = hasDir ? join6(cwd, dirname(partial)) : cwd;
3472
+ const prefix = hasDir ? basename2(partial) : partial;
3473
+ if (!existsSync9(searchDir)) {
3474
+ return [];
3475
+ }
3476
+ const entries = readdirSync5(searchDir);
3477
+ const results = [];
3478
+ const dirPrefix = hasDir ? dirname(partial) + "/" : "";
3479
+ for (const entry of entries) {
3480
+ if (!entry.startsWith(prefix)) continue;
3481
+ if (entry.startsWith(".")) continue;
3482
+ const fullPath = join6(searchDir, entry);
3483
+ try {
3484
+ const stat = statSync2(fullPath);
3485
+ if (stat.isDirectory()) {
3486
+ results.push(dirPrefix + entry + "/");
3487
+ } else if (stat.isFile()) {
3488
+ results.push(dirPrefix + entry);
3489
+ }
3490
+ } catch {
3491
+ }
3492
+ }
3493
+ return results;
3494
+ }
3495
+ function tryCompleteSpecialArg(previousWord, currentWord, words) {
3496
+ if (previousWord === "-b" || previousWord === "--branch") {
3497
+ try {
3498
+ const worktrees = getProjectWorktrees();
3499
+ const branches = worktrees.map((wt) => wt.branch);
3500
+ console.log(branches.filter((b) => b.startsWith(currentWord)).join("\n"));
3501
+ } catch {
3502
+ }
3503
+ return true;
3504
+ }
3505
+ if (previousWord === "-f" || previousWord === "--file") {
3506
+ try {
3507
+ const candidates = completeFilePath(currentWord);
3508
+ console.log(candidates.join("\n"));
3509
+ } catch {
3510
+ }
3511
+ return true;
3512
+ }
3513
+ if (previousWord === "set" || previousWord === "get") {
3514
+ if (words.includes("config")) {
3515
+ const keys = Object.keys(CONFIG_DEFINITIONS);
3516
+ console.log(keys.filter((k) => k.startsWith(currentWord)).join("\n"));
3517
+ return true;
3518
+ }
3519
+ }
3520
+ return false;
3521
+ }
3522
+ function completeFromCommandTree(program2, words, cword, currentWord) {
3523
+ let currentCmd = program2;
3524
+ for (let i = 1; i < cword; i++) {
3525
+ const word = words[i];
3526
+ const subCmd = currentCmd.commands.find((c) => c.name() === word || c.aliases().includes(word));
3527
+ if (subCmd) {
3528
+ currentCmd = subCmd;
3529
+ }
3530
+ }
3531
+ const completions = [];
3532
+ if (currentWord.startsWith("-")) {
3533
+ currentCmd.options.forEach((opt) => {
3534
+ if (opt.short && opt.short.startsWith(currentWord)) completions.push(opt.short);
3535
+ if (opt.long && opt.long.startsWith(currentWord)) completions.push(opt.long);
3536
+ });
3537
+ } else {
3538
+ currentCmd.commands.forEach((cmd) => {
3539
+ const name = cmd.name();
3540
+ if (name !== "_complete" && name.startsWith(currentWord)) {
3541
+ completions.push(name);
3542
+ cmd.aliases().forEach((alias) => {
3543
+ if (alias.startsWith(currentWord)) {
3544
+ completions.push(alias);
3545
+ }
3546
+ });
3547
+ }
3548
+ });
3549
+ if (!currentWord) {
3550
+ currentCmd.options.forEach((opt) => {
3551
+ if (opt.long) completions.push(opt.long);
3552
+ else if (opt.short) completions.push(opt.short);
3553
+ });
3554
+ }
3555
+ }
3556
+ console.log(Array.from(new Set(completions)).join("\n"));
3557
+ }
3558
+ function generateCompletions(program2, args) {
3559
+ const cword = parseInt(args[1], 10);
3560
+ const words = args.slice(2);
3561
+ const currentWord = words[cword] || "";
3562
+ const previousWord = cword > 0 ? words[cword - 1] : "";
3563
+ if (tryCompleteSpecialArg(previousWord, currentWord, words)) {
3564
+ return;
3565
+ }
3566
+ completeFromCommandTree(program2, words, cword, currentWord);
3567
+ }
3568
+
3569
+ // src/commands/completion.ts
3570
+ function appendToFile(filePath, content) {
3571
+ if (existsSync10(filePath)) {
3572
+ const current = readFileSync4(filePath, "utf-8");
3573
+ if (current.includes("clawt completion")) {
3574
+ printInfo(MESSAGES.COMPLETION_INSTALL_EXISTS + ": " + filePath);
3575
+ return;
3576
+ }
3577
+ content = current + content;
3578
+ }
3579
+ writeFileSync3(filePath, content, "utf-8");
3580
+ printSuccess(MESSAGES.COMPLETION_INSTALL_SUCCESS + ": " + filePath);
3581
+ printInfo(MESSAGES.COMPLETION_INSTALL_RESTART(filePath));
3582
+ }
3583
+ function installCompletions() {
3584
+ const shell = process.env.SHELL || "";
3585
+ const home = homedir2();
3586
+ try {
3587
+ if (shell.includes("zsh")) {
3588
+ const rcPath = resolve2(home, ".zshrc");
3589
+ const script = `
3590
+ # clawt completion
3591
+ source <(clawt completion zsh)
3592
+ `;
3593
+ appendToFile(rcPath, script);
3594
+ } else if (shell.includes("bash")) {
3595
+ const rcPath = resolve2(home, ".bashrc");
3596
+ const script = `
3597
+ # clawt completion
3598
+ eval "$(clawt completion bash)"
3599
+ `;
3600
+ appendToFile(rcPath, script);
3601
+ } else {
3602
+ printWarning(MESSAGES.COMPLETION_INSTALL_UNKNOWN_SHELL);
3603
+ }
3604
+ } catch (error) {
3605
+ const filePath = shell.includes("zsh") ? resolve2(home, ".zshrc") : resolve2(home, ".bashrc");
3606
+ printError(MESSAGES.COMPLETION_INSTALL_WRITE_ERROR(filePath));
3607
+ }
3608
+ }
3609
+ function registerCompletionCommand(program2) {
3610
+ const completionCommand = program2.command("completion").description(MESSAGES.COMPLETION_COMMAND_DESC);
3611
+ completionCommand.command("bash").description(MESSAGES.COMPLETION_BASH_DESC).action(() => {
3612
+ console.log(getBashScript());
3613
+ });
3614
+ completionCommand.command("zsh").description(MESSAGES.COMPLETION_ZSH_DESC).action(() => {
3615
+ console.log(getZshScript());
3616
+ });
3617
+ completionCommand.command("install").description(MESSAGES.COMPLETION_INSTALL_DESC).action(() => {
3618
+ installCompletions();
3619
+ });
3620
+ 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) => {
3621
+ if (!args || args.length < 2) return;
3622
+ generateCompletions(program2, args);
3623
+ });
3624
+ }
3625
+
3391
3626
  // src/index.ts
3392
3627
  var require2 = createRequire(import.meta.url);
3393
3628
  var { version } = require2("../package.json");
@@ -3411,6 +3646,7 @@ registerSyncCommand(program);
3411
3646
  registerResetCommand(program);
3412
3647
  registerStatusCommand(program);
3413
3648
  registerAliasCommand(program);
3649
+ registerCompletionCommand(program);
3414
3650
  var config = loadConfig();
3415
3651
  applyAliases(program, config.aliases);
3416
3652
  process.on("uncaughtException", (error) => {
@@ -366,6 +366,28 @@ var ALIAS_MESSAGES = {
366
366
  ALIAS_LIST_TITLE: "\u5F53\u524D\u522B\u540D\u5217\u8868\uFF1A"
367
367
  };
368
368
 
369
+ // src/constants/messages/completion.ts
370
+ var COMPLETION_MESSAGES = {
371
+ /** completion 命令的主描述 */
372
+ COMPLETION_COMMAND_DESC: "\u751F\u6210\u548C\u5B89\u88C5 shell \u81EA\u52A8\u8865\u5168\u811A\u672C",
373
+ /** bash 子命令描述 */
374
+ COMPLETION_BASH_DESC: "\u8F93\u51FA bash \u81EA\u52A8\u8865\u5168\u811A\u672C",
375
+ /** zsh 子命令描述 */
376
+ COMPLETION_ZSH_DESC: "\u8F93\u51FA zsh \u81EA\u52A8\u8865\u5168\u811A\u672C",
377
+ /** install 子命令描述 */
378
+ COMPLETION_INSTALL_DESC: "\u81EA\u52A8\u5B89\u88C5\u8865\u5168\u811A\u672C\u5230\u5F53\u524D\u7528\u6237\u7684 shell \u914D\u7F6E\u6587\u4EF6",
379
+ /** 安装成功提示 */
380
+ COMPLETION_INSTALL_SUCCESS: "\u81EA\u52A8\u8865\u5168\u914D\u7F6E\u5DF2\u6210\u529F\u5199\u5165",
381
+ /** 安装失败或未知的 shell 提示 */
382
+ COMPLETION_INSTALL_UNKNOWN_SHELL: "\u672A\u77E5\u7684 Shell \u73AF\u5883\u6216\u65E0\u6CD5\u81EA\u52A8\u5B89\u88C5\uFF0C\u8BF7\u624B\u52A8\u914D\u7F6E\u3002",
383
+ /** 补全配置已存在提示 */
384
+ COMPLETION_INSTALL_EXISTS: "\u81EA\u52A8\u8865\u5168\u914D\u7F6E\u5DF2\u5B58\u5728\u4E8E\u76EE\u6807\u6587\u4EF6\u4E2D",
385
+ /** 提示用户重启生效 */
386
+ COMPLETION_INSTALL_RESTART: (filePath) => `\u8BF7\u91CD\u542F\u7EC8\u7AEF\u6216\u8FD0\u884C \`source ${filePath}\` \u4EE5\u4F7F\u8865\u5168\u751F\u6548\u3002`,
387
+ /** 安装写入失败提示 */
388
+ 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`
389
+ };
390
+
369
391
  // src/constants/messages/index.ts
370
392
  var MESSAGES = {
371
393
  ...COMMON_MESSAGES,
@@ -379,7 +401,8 @@ var MESSAGES = {
379
401
  ...RESET_MESSAGES,
380
402
  ...CONFIG_CMD_MESSAGES,
381
403
  ...STATUS_MESSAGES,
382
- ...ALIAS_MESSAGES
404
+ ...ALIAS_MESSAGES,
405
+ ...COMPLETION_MESSAGES
383
406
  };
384
407
 
385
408
  // 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.0",
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
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * 自动补全命令相关的提示消息常量
3
+ */
4
+ export const COMPLETION_MESSAGES = {
5
+ /** completion 命令的主描述 */
6
+ COMPLETION_COMMAND_DESC: '生成和安装 shell 自动补全脚本',
7
+ /** bash 子命令描述 */
8
+ COMPLETION_BASH_DESC: '输出 bash 自动补全脚本',
9
+ /** zsh 子命令描述 */
10
+ COMPLETION_ZSH_DESC: '输出 zsh 自动补全脚本',
11
+ /** install 子命令描述 */
12
+ COMPLETION_INSTALL_DESC: '自动安装补全脚本到当前用户的 shell 配置文件',
13
+ /** 安装成功提示 */
14
+ COMPLETION_INSTALL_SUCCESS: '自动补全配置已成功写入',
15
+ /** 安装失败或未知的 shell 提示 */
16
+ COMPLETION_INSTALL_UNKNOWN_SHELL: '未知的 Shell 环境或无法自动安装,请手动配置。',
17
+ /** 补全配置已存在提示 */
18
+ COMPLETION_INSTALL_EXISTS: '自动补全配置已存在于目标文件中',
19
+ /** 提示用户重启生效 */
20
+ COMPLETION_INSTALL_RESTART: (filePath: string) => `请重启终端或运行 \`source ${filePath}\` 以使补全生效。`,
21
+ /** 安装写入失败提示 */
22
+ COMPLETION_INSTALL_WRITE_ERROR: (filePath: string) => `无法写入文件 ${filePath},请检查文件权限或手动配置。`,
23
+ } as const;
@@ -12,6 +12,7 @@ import { CONFIG_CMD_MESSAGES, CONFIG_ALIAS_DISABLED_HINT } from './config.js';
12
12
  export { CONFIG_ALIAS_DISABLED_HINT };
13
13
  import { STATUS_MESSAGES } from './status.js';
14
14
  import { ALIAS_MESSAGES } from './alias.js';
15
+ import { COMPLETION_MESSAGES } from './completion.js';
15
16
 
16
17
  /**
17
18
  * 提示消息模板
@@ -30,4 +31,5 @@ export const MESSAGES = {
30
31
  ...CONFIG_CMD_MESSAGES,
31
32
  ...STATUS_MESSAGES,
32
33
  ...ALIAS_MESSAGES,
34
+ ...COMPLETION_MESSAGES,
33
35
  } as const;
package/src/index.ts CHANGED
@@ -16,6 +16,7 @@ import { registerSyncCommand } from './commands/sync.js';
16
16
  import { registerResetCommand } from './commands/reset.js';
17
17
  import { registerStatusCommand } from './commands/status.js';
18
18
  import { registerAliasCommand } from './commands/alias.js';
19
+ import { registerCompletionCommand } from './commands/completion.js';
19
20
 
20
21
  // 从 package.json 读取版本号,避免硬编码
21
22
  const require = createRequire(import.meta.url);
@@ -52,6 +53,7 @@ registerSyncCommand(program);
52
53
  registerResetCommand(program);
53
54
  registerStatusCommand(program);
54
55
  registerAliasCommand(program);
56
+ registerCompletionCommand(program);
55
57
 
56
58
  // 加载配置并应用命令别名
57
59
  const config = loadConfig();