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 +14 -0
- package/dist/index.js +252 -22
- package/dist/postinstall.js +24 -4
- package/docs/spec.md +59 -1
- package/package.json +1 -1
- package/src/commands/completion.ts +98 -0
- package/src/commands/validate.ts +1 -4
- package/src/constants/messages/completion.ts +23 -0
- package/src/constants/messages/index.ts +2 -0
- package/src/constants/messages/validate.ts +0 -3
- package/src/index.ts +2 -0
- package/src/utils/completion-engine.ts +174 -0
- package/src/utils/completion-scripts.ts +58 -0
- package/tests/unit/commands/completion.test.ts +1116 -0
- package/.claude/agent-memory/docs-sync-updater/MEMORY.md +0 -137
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((
|
|
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
|
-
|
|
653
|
+
resolve3({ command, exitCode: 1, error: err.message });
|
|
634
654
|
});
|
|
635
655
|
child.on("close", (code) => {
|
|
636
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
|
1839
|
-
raw = `${toolName} ${
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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((
|
|
2119
|
+
(cp) => new Promise((resolve3) => {
|
|
2100
2120
|
if (cp.exitCode !== null) {
|
|
2101
|
-
|
|
2121
|
+
resolve3();
|
|
2102
2122
|
} else {
|
|
2103
|
-
cp.on("close", () =>
|
|
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) => {
|
package/dist/postinstall.js
CHANGED
|
@@ -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
|
-
|
|
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
|
@@ -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
|
+
}
|
package/src/commands/validate.ts
CHANGED
|
@@ -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
|
-
|
|
192
|
-
// sync 存在冲突,提示用户手动解决
|
|
193
|
-
printWarning(MESSAGES.VALIDATE_AUTO_SYNC_CONFLICT(targetWorktreePath));
|
|
194
|
-
}
|
|
191
|
+
// sync 冲突提示已在 executeSyncForBranch 内部输出(SYNC_CONFLICT),此处无需重复提示
|
|
195
192
|
}
|
|
196
193
|
|
|
197
194
|
/**
|