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 +14 -0
- package/dist/index.js +252 -16
- package/dist/postinstall.js +24 -1
- package/docs/spec.md +59 -1
- package/package.json +1 -1
- package/src/commands/completion.ts +98 -0
- package/src/constants/messages/completion.ts +23 -0
- package/src/constants/messages/index.ts +2 -0
- 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
|
@@ -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((
|
|
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
|
-
|
|
656
|
+
resolve3({ command, exitCode: 1, error: err.message });
|
|
634
657
|
});
|
|
635
658
|
child.on("close", (code) => {
|
|
636
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
|
1839
|
-
raw = `${toolName} ${
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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((
|
|
2122
|
+
(cp) => new Promise((resolve3) => {
|
|
2100
2123
|
if (cp.exitCode !== null) {
|
|
2101
|
-
|
|
2124
|
+
resolve3();
|
|
2102
2125
|
} else {
|
|
2103
|
-
cp.on("close", () =>
|
|
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) => {
|
package/dist/postinstall.js
CHANGED
|
@@ -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
|
-
|
|
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
|
+
}
|
|
@@ -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();
|