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
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 自动补全引擎核心逻辑
|
|
3
|
+
* 提供文件路径补全、特殊参数补全、命令树遍历等功能
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Command } from 'commander';
|
|
7
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
8
|
+
import { join, dirname, basename } from 'node:path';
|
|
9
|
+
|
|
10
|
+
import { getProjectWorktrees } from './worktree.js';
|
|
11
|
+
import { CONFIG_DEFINITIONS } from '../constants/config.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 补全文件路径,支持子目录递归浏览
|
|
15
|
+
* 根据用户已输入的部分路径,列出匹配的文件和子目录
|
|
16
|
+
* @param {string} partial - 用户当前输入的部分路径
|
|
17
|
+
* @returns {string[]} 匹配的候选路径列表
|
|
18
|
+
*/
|
|
19
|
+
export function completeFilePath(partial: string): string[] {
|
|
20
|
+
const cwd = process.cwd();
|
|
21
|
+
// 判断输入是否包含目录前缀(如 "tasks/" 或 "tasks/my")
|
|
22
|
+
const hasDir = partial.includes('/');
|
|
23
|
+
const searchDir = hasDir ? join(cwd, dirname(partial)) : cwd;
|
|
24
|
+
const prefix = hasDir ? basename(partial) : partial;
|
|
25
|
+
|
|
26
|
+
if (!existsSync(searchDir)) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const entries = readdirSync(searchDir);
|
|
31
|
+
const results: string[] = [];
|
|
32
|
+
// 相对路径前缀,用于拼接输出
|
|
33
|
+
const dirPrefix = hasDir ? dirname(partial) + '/' : '';
|
|
34
|
+
|
|
35
|
+
for (const entry of entries) {
|
|
36
|
+
if (!entry.startsWith(prefix)) continue;
|
|
37
|
+
// 跳过隐藏文件和目录
|
|
38
|
+
if (entry.startsWith('.')) continue;
|
|
39
|
+
|
|
40
|
+
const fullPath = join(searchDir, entry);
|
|
41
|
+
try {
|
|
42
|
+
const stat = statSync(fullPath);
|
|
43
|
+
if (stat.isDirectory()) {
|
|
44
|
+
// 目录:返回带尾部斜杠的路径,便于用户继续补全
|
|
45
|
+
results.push(dirPrefix + entry + '/');
|
|
46
|
+
} else if (stat.isFile()) {
|
|
47
|
+
// 文件:直接作为候选项(不限制后缀,与 clawt run -f 行为一致)
|
|
48
|
+
results.push(dirPrefix + entry);
|
|
49
|
+
}
|
|
50
|
+
} catch {
|
|
51
|
+
// 忽略无法访问的文件(权限不足等)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return results;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 尝试对特殊参数进行动态补全(分支名、文件路径、配置键)
|
|
60
|
+
* 如果当前上下文匹配某种特殊参数,则直接输出候选项并返回 true
|
|
61
|
+
* @param {string} previousWord - 当前光标前一个词
|
|
62
|
+
* @param {string} currentWord - 当前正在输入的词
|
|
63
|
+
* @param {string[]} words - 完整的命令行词数组
|
|
64
|
+
* @returns {boolean} 是否已处理该补全
|
|
65
|
+
*/
|
|
66
|
+
export function tryCompleteSpecialArg(previousWord: string, currentWord: string, words: string[]): boolean {
|
|
67
|
+
// 分支名补全
|
|
68
|
+
if (previousWord === '-b' || previousWord === '--branch') {
|
|
69
|
+
try {
|
|
70
|
+
const worktrees = getProjectWorktrees();
|
|
71
|
+
const branches = worktrees.map(wt => wt.branch);
|
|
72
|
+
console.log(branches.filter(b => b.startsWith(currentWord)).join('\n'));
|
|
73
|
+
} catch {
|
|
74
|
+
// 忽略可能的异常,例如非 git 仓库环境
|
|
75
|
+
}
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 文件路径补全
|
|
80
|
+
if (previousWord === '-f' || previousWord === '--file') {
|
|
81
|
+
try {
|
|
82
|
+
const candidates = completeFilePath(currentWord);
|
|
83
|
+
console.log(candidates.join('\n'));
|
|
84
|
+
} catch {
|
|
85
|
+
// 忽略可能的读取异常
|
|
86
|
+
}
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// config set/get 配置键补全
|
|
91
|
+
if (previousWord === 'set' || previousWord === 'get') {
|
|
92
|
+
if (words.includes('config')) {
|
|
93
|
+
const keys = Object.keys(CONFIG_DEFINITIONS);
|
|
94
|
+
console.log(keys.filter(k => k.startsWith(currentWord)).join('\n'));
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 根据命令树遍历当前层级,生成子命令和选项候选项
|
|
104
|
+
* @param {Command} program - 根命令实例
|
|
105
|
+
* @param {string[]} words - 完整的命令行词数组
|
|
106
|
+
* @param {number} cword - 当前光标所在词的索引
|
|
107
|
+
* @param {string} currentWord - 当前正在输入的词
|
|
108
|
+
*/
|
|
109
|
+
export function completeFromCommandTree(program: Command, words: string[], cword: number, currentWord: string): void {
|
|
110
|
+
// 根据当前输入的命令上下文查找对应的 Commander.js 命令层级
|
|
111
|
+
let currentCmd = program;
|
|
112
|
+
for (let i = 1; i < cword; i++) {
|
|
113
|
+
const word = words[i];
|
|
114
|
+
const subCmd = currentCmd.commands.find(c => c.name() === word || c.aliases().includes(word));
|
|
115
|
+
if (subCmd) {
|
|
116
|
+
currentCmd = subCmd;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const completions: string[] = [];
|
|
121
|
+
|
|
122
|
+
// 如果当前正在输入选项,则提供该层级的可用选项
|
|
123
|
+
if (currentWord.startsWith('-')) {
|
|
124
|
+
currentCmd.options.forEach(opt => {
|
|
125
|
+
if (opt.short && opt.short.startsWith(currentWord)) completions.push(opt.short);
|
|
126
|
+
if (opt.long && opt.long.startsWith(currentWord)) completions.push(opt.long);
|
|
127
|
+
});
|
|
128
|
+
} else {
|
|
129
|
+
// 否则提供该层级的可用子命令
|
|
130
|
+
currentCmd.commands.forEach(cmd => {
|
|
131
|
+
const name = cmd.name();
|
|
132
|
+
if (name !== '_complete' && name.startsWith(currentWord)) {
|
|
133
|
+
completions.push(name);
|
|
134
|
+
cmd.aliases().forEach(alias => {
|
|
135
|
+
if (alias.startsWith(currentWord)) {
|
|
136
|
+
completions.push(alias);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// 也提供可以接在命令后面的选项名(作为候选)
|
|
143
|
+
if (!currentWord) {
|
|
144
|
+
currentCmd.options.forEach(opt => {
|
|
145
|
+
if (opt.long) completions.push(opt.long);
|
|
146
|
+
else if (opt.short) completions.push(opt.short);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// 输出结果(去重并用换行符分隔)
|
|
152
|
+
console.log(Array.from(new Set(completions)).join('\n'));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* 执行动态补全逻辑并输出候选项
|
|
157
|
+
* @param {Command} program - 根命令实例
|
|
158
|
+
* @param {string[]} args - 传递的上下文参数 (shell, cword, ...words)
|
|
159
|
+
*/
|
|
160
|
+
export function generateCompletions(program: Command, args: string[]): void {
|
|
161
|
+
// args 格式预估: [shell, cword, word0, word1, ..., currentWord]
|
|
162
|
+
const cword = parseInt(args[1], 10);
|
|
163
|
+
const words = args.slice(2);
|
|
164
|
+
const currentWord = words[cword] || '';
|
|
165
|
+
const previousWord = cword > 0 ? words[cword - 1] : '';
|
|
166
|
+
|
|
167
|
+
// 1. 尝试特殊参数动态补全
|
|
168
|
+
if (tryCompleteSpecialArg(previousWord, currentWord, words)) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// 2. 从命令树生成子命令和选项候选项
|
|
173
|
+
completeFromCommandTree(program, words, cword, currentWord);
|
|
174
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shell 自动补全脚本模板
|
|
3
|
+
* 提供 Bash 和 Zsh 的补全脚本生成函数
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 获取 Bash 自动补全脚本内容
|
|
8
|
+
* @returns {string} Bash 脚本字符串
|
|
9
|
+
*/
|
|
10
|
+
export function getBashScript(): string {
|
|
11
|
+
return `
|
|
12
|
+
_clawt_completion() {
|
|
13
|
+
local IFS=$'\\n'
|
|
14
|
+
local completions=$(clawt completion _complete bash "$COMP_CWORD" "\${COMP_WORDS[@]}")
|
|
15
|
+
COMPREPLY=()
|
|
16
|
+
local comp
|
|
17
|
+
while IFS= read -r comp; do
|
|
18
|
+
[ -z "$comp" ] && continue
|
|
19
|
+
COMPREPLY+=("$comp")
|
|
20
|
+
done <<< "$completions"
|
|
21
|
+
local has_dir=0
|
|
22
|
+
for comp in "\${COMPREPLY[@]}"; do
|
|
23
|
+
[[ "$comp" == */ ]] && has_dir=1 && break
|
|
24
|
+
done
|
|
25
|
+
if (( has_dir )) && type compopt &>/dev/null; then
|
|
26
|
+
compopt -o nospace
|
|
27
|
+
fi
|
|
28
|
+
}
|
|
29
|
+
complete -o nospace -F _clawt_completion clawt
|
|
30
|
+
`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 获取 Zsh 自动补全脚本内容
|
|
35
|
+
* @returns {string} Zsh 脚本字符串
|
|
36
|
+
*/
|
|
37
|
+
export function getZshScript(): string {
|
|
38
|
+
return `
|
|
39
|
+
#compdef clawt
|
|
40
|
+
_clawt_completion() {
|
|
41
|
+
local completions
|
|
42
|
+
local cword=$((CURRENT - 1))
|
|
43
|
+
completions=("\${(@f)$(clawt completion _complete zsh "$cword" "\${words[@]}")}")
|
|
44
|
+
if [[ -n "$completions" ]]; then
|
|
45
|
+
local comp
|
|
46
|
+
for comp in "\${completions[@]}"; do
|
|
47
|
+
[[ -z "$comp" ]] && continue
|
|
48
|
+
if [[ "$comp" == */ ]]; then
|
|
49
|
+
compadd -S '' -- "$comp"
|
|
50
|
+
else
|
|
51
|
+
compadd -S ' ' -- "$comp"
|
|
52
|
+
fi
|
|
53
|
+
done
|
|
54
|
+
fi
|
|
55
|
+
}
|
|
56
|
+
compdef _clawt_completion clawt
|
|
57
|
+
`;
|
|
58
|
+
}
|