@xfe-repo/cli 2.0.11 → 2.0.13
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/CHANGELOG.md +18 -0
- package/dist/app.js +12 -5
- package/dist/components/Prompts/FuzzyPathPrompt.js +9 -3
- package/dist/components/Prompts/ListPrompt.js +5 -3
- package/dist/components/Prompts/OptionList.d.ts +6 -0
- package/dist/components/Prompts/OptionList.js +14 -0
- package/dist/components/Prompts/SearchCheckboxPrompt.js +10 -3
- package/dist/components/Prompts/SearchListPrompt.js +9 -3
- package/dist/components/Prompts/index.d.ts +1 -0
- package/dist/components/Prompts/index.js +5 -5
- package/dist/hooks/use-adapters.js +4 -6
- package/dist/views/MenuView.d.ts +1 -0
- package/dist/views/MenuView.js +5 -8
- package/dist/views/PromptView.d.ts +7 -1
- package/dist/views/PromptView.js +6 -2
- package/dist/views/RunnerView.d.ts +1 -0
- package/dist/views/RunnerView.js +40 -3
- package/package.json +3 -3
- package/dist/backend/http-backend.d.ts +0 -47
- package/dist/backend/http-backend.js +0 -84
- package/dist/backend/server-task-commands.d.ts +0 -9
- package/dist/backend/server-task-commands.js +0 -262
- package/dist/command-catalog.d.ts +0 -19
- package/dist/command-catalog.js +0 -67
- package/dist/command.d.ts +0 -45
- package/dist/command.js +0 -88
- package/dist/completion-core.d.ts +0 -64
- package/dist/completion-core.js +0 -201
- package/dist/completion.d.ts +0 -21
- package/dist/completion.js +0 -127
package/dist/completion-core.js
DELETED
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @xfe-repo/cli - 补全核心(与 UI / shell 协议解耦)
|
|
3
|
-
*
|
|
4
|
-
* 集中维护:命令目录条目类型、查询/输入解析、option/value 建议计算、
|
|
5
|
-
* 已用 flag 过滤、命令寻址、prefilled 提取、模糊匹配。
|
|
6
|
-
*
|
|
7
|
-
* 调用方:
|
|
8
|
-
* - TUI Commands 组件复用全部能力(含模糊匹配 / 二级菜单 / 已用 flag 过滤)
|
|
9
|
-
* - shell / 其他入口可按需复用 catalog 与 option 建议部分,不依赖 UI
|
|
10
|
-
*/
|
|
11
|
-
import { parseCommandFlagTokens, splitCommandName, splitFlagTokens } from './command.js';
|
|
12
|
-
// ─── Public API ─────────────────────────────────────────────
|
|
13
|
-
/** 解析 query 是否进入二级菜单模式 */
|
|
14
|
-
export function parseQuery(query, options) {
|
|
15
|
-
if (!query)
|
|
16
|
-
return { activeGroup: null, subQuery: '', topQuery: '' };
|
|
17
|
-
const spaceIndex = query.indexOf(' ');
|
|
18
|
-
if (spaceIndex > 0) {
|
|
19
|
-
const groupPart = query.slice(0, spaceIndex);
|
|
20
|
-
const subQuery = query.slice(spaceIndex + 1).trim();
|
|
21
|
-
const group = options.find((o) => o.type === 'group' && o.name.toLowerCase() === groupPart);
|
|
22
|
-
if (group)
|
|
23
|
-
return { activeGroup: group, subQuery, topQuery: '' };
|
|
24
|
-
}
|
|
25
|
-
const exactGroup = options.find((o) => o.type === 'group' && o.name.toLowerCase() === query);
|
|
26
|
-
if (exactGroup)
|
|
27
|
-
return { activeGroup: exactGroup, subQuery: '', topQuery: '' };
|
|
28
|
-
return { activeGroup: null, subQuery: '', topQuery: query };
|
|
29
|
-
}
|
|
30
|
-
/** 顶级查询过滤 + 评分排序 */
|
|
31
|
-
export function filterTopLevel(options, query) {
|
|
32
|
-
return options
|
|
33
|
-
.filter((item) => fuzzyMatch(item.name, query))
|
|
34
|
-
.sort((a, b) => matchScore(a.name, query) - matchScore(b.name, query));
|
|
35
|
-
}
|
|
36
|
-
/** 二级子命令过滤:同时尝试匹配完整名和 action 名 */
|
|
37
|
-
export function filterSubCommands(options, query) {
|
|
38
|
-
return options
|
|
39
|
-
.filter((opt) => {
|
|
40
|
-
const action = splitCommandName(opt.name).action;
|
|
41
|
-
return fuzzyMatch(action, query) || fuzzyMatch(opt.name, query);
|
|
42
|
-
})
|
|
43
|
-
.sort((a, b) => {
|
|
44
|
-
const aAction = splitCommandName(a.name).action;
|
|
45
|
-
const bAction = splitCommandName(b.name).action;
|
|
46
|
-
return matchScore(aAction, query) - matchScore(bAction, query);
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* 识别 `<cmd> [--k=v ...] [--partial]` 形式的输入,返回 extras 模式
|
|
51
|
-
*
|
|
52
|
-
* 末尾 `--key=` 进入 value 模式;`--key` / `--` / 空 进入 name 模式
|
|
53
|
-
*/
|
|
54
|
-
export function parseExtrasMode(rawQuery, options) {
|
|
55
|
-
const split = splitCommandAndFlags(rawQuery, options);
|
|
56
|
-
if (!split || !split.command.options?.length)
|
|
57
|
-
return null;
|
|
58
|
-
const { command, flagTokens, endsWithSpace } = split;
|
|
59
|
-
if (flagTokens.length === 0 && !endsWithSpace)
|
|
60
|
-
return null;
|
|
61
|
-
const newFlag = endsWithSpace || flagTokens.length === 0;
|
|
62
|
-
const usedNames = collectUsedNames(newFlag ? flagTokens : flagTokens.slice(0, -1));
|
|
63
|
-
if (newFlag)
|
|
64
|
-
return { kind: 'name', command, partial: '', usedNames };
|
|
65
|
-
const last = flagTokens[flagTokens.length - 1];
|
|
66
|
-
if (!last.startsWith('--'))
|
|
67
|
-
return null;
|
|
68
|
-
const eqIdx = last.indexOf('=');
|
|
69
|
-
if (eqIdx >= 0) {
|
|
70
|
-
const rawKey = last.slice(2, eqIdx);
|
|
71
|
-
const partial = last.slice(eqIdx + 1);
|
|
72
|
-
const opt = command.options?.find((o) => o.name.toLowerCase() === rawKey.toLowerCase());
|
|
73
|
-
if (!opt?.enumValues?.length)
|
|
74
|
-
return null;
|
|
75
|
-
return { kind: 'value', command, key: opt.name, partial, values: opt.enumValues };
|
|
76
|
-
}
|
|
77
|
-
return { kind: 'name', command, partial: last.slice(2), usedNames };
|
|
78
|
-
}
|
|
79
|
-
/** extras 模式建议项(option 名或枚举值) */
|
|
80
|
-
export function buildExtrasSuggestions(mode) {
|
|
81
|
-
if (mode.kind === 'value') {
|
|
82
|
-
const partial = mode.partial.toLowerCase();
|
|
83
|
-
return mode.values
|
|
84
|
-
.filter((v) => !partial || v.toLowerCase().startsWith(partial))
|
|
85
|
-
.map((v) => ({ name: v, description: '', type: 'value' }));
|
|
86
|
-
}
|
|
87
|
-
const partial = mode.partial.toLowerCase();
|
|
88
|
-
return (mode.command.options ?? [])
|
|
89
|
-
.filter((o) => !mode.usedNames.has(o.name.toLowerCase()))
|
|
90
|
-
.filter((o) => !partial || o.name.toLowerCase().startsWith(partial))
|
|
91
|
-
.map((o) => ({ name: o.name, description: o.description, type: 'option' }));
|
|
92
|
-
}
|
|
93
|
-
/** extras 模式 Tab 补全:替换末尾未完成 token,或在末尾空格后追加新 flag */
|
|
94
|
-
export function applyExtrasCompletion(input, mode, selected) {
|
|
95
|
-
const lastSpace = input.lastIndexOf(' ');
|
|
96
|
-
const prefix = input.slice(0, lastSpace + 1);
|
|
97
|
-
if (mode.kind === 'value')
|
|
98
|
-
return `${prefix}--${mode.key}=${selected} `;
|
|
99
|
-
const isBoolean = mode.command.options?.find((o) => o.name === selected)?.isBoolean === true;
|
|
100
|
-
return `${prefix}${isBoolean ? `--${selected} ` : `--${selected}=`}`;
|
|
101
|
-
}
|
|
102
|
-
/** 输入包含 flag 时尝试解析对应命令;不依赖命令是否声明 options,仅用于建议回显 */
|
|
103
|
-
export function resolveMatchedCommandWithFlags(rawQuery, options) {
|
|
104
|
-
const split = splitFlagTokens(rawQuery);
|
|
105
|
-
if (!split || split.flagTokens.length === 0)
|
|
106
|
-
return null;
|
|
107
|
-
if (split.cmdTokens.length === 0 || split.cmdTokens.length > 2)
|
|
108
|
-
return null;
|
|
109
|
-
return resolveCommand(split.cmdTokens.join(' '), options);
|
|
110
|
-
}
|
|
111
|
-
/** Enter 提交时把 `/cmd --k1=v1 --k2 v2` 解析为命令 + prefilled */
|
|
112
|
-
export function extractCommandAndPrefilled(input, options) {
|
|
113
|
-
const raw = input.startsWith('/') ? input.slice(1) : '';
|
|
114
|
-
const split = splitCommandAndFlags(raw, options);
|
|
115
|
-
if (!split || split.flagTokens.length === 0)
|
|
116
|
-
return { command: null, prefilled: {} };
|
|
117
|
-
return {
|
|
118
|
-
command: split.command,
|
|
119
|
-
prefilled: parseCommandFlagTokens(split.flagTokens, split.command.options),
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
/** 判断输入是否已经包含 `--flag` token */
|
|
123
|
-
export function hasFlagToken(input) {
|
|
124
|
-
const raw = input.startsWith('/') ? input.slice(1) : input;
|
|
125
|
-
const split = splitFlagTokens(raw);
|
|
126
|
-
return !!split && split.flagTokens.length > 0;
|
|
127
|
-
}
|
|
128
|
-
// ─── Helpers ────────────────────────────────────────────────
|
|
129
|
-
/**
|
|
130
|
-
* 把 `<cmd...> [--flag ...]` 查询拆分为命令 + flag tokens
|
|
131
|
-
*
|
|
132
|
-
* 在 splitFlagTokens 基础上叠加目录解析,限制命令 tokens ≤ 2(顶级命令 或 group+action)
|
|
133
|
-
*/
|
|
134
|
-
function splitCommandAndFlags(rawQuery, options) {
|
|
135
|
-
const split = splitFlagTokens(rawQuery);
|
|
136
|
-
if (!split || split.cmdTokens.length === 0 || split.cmdTokens.length > 2)
|
|
137
|
-
return null;
|
|
138
|
-
const command = resolveCommand(split.cmdTokens.join(' '), options);
|
|
139
|
-
if (!command)
|
|
140
|
-
return null;
|
|
141
|
-
return { command, flagTokens: split.flagTokens, endsWithSpace: split.endsWithSpace };
|
|
142
|
-
}
|
|
143
|
-
/** 通过 `<name>` 或 `<group> <action>` 解析命令 */
|
|
144
|
-
function resolveCommand(cmdPart, options) {
|
|
145
|
-
const parts = cmdPart.trim().split(/\s+/);
|
|
146
|
-
if (parts.length === 1)
|
|
147
|
-
return findCommandByName(options, parts[0]);
|
|
148
|
-
if (parts.length !== 2)
|
|
149
|
-
return null;
|
|
150
|
-
const [groupName, action] = parts;
|
|
151
|
-
const group = options.find((o) => o.type === 'group' && o.name.toLowerCase() === groupName.toLowerCase());
|
|
152
|
-
if (!group?.children)
|
|
153
|
-
return null;
|
|
154
|
-
return group.children.find((c) => splitCommandName(c.name).action.toLowerCase() === action.toLowerCase()) ?? null;
|
|
155
|
-
}
|
|
156
|
-
function findCommandByName(options, name) {
|
|
157
|
-
const target = name.toLowerCase();
|
|
158
|
-
for (const opt of options) {
|
|
159
|
-
if (opt.type === 'group') {
|
|
160
|
-
const found = opt.children?.find((c) => c.name.toLowerCase() === target || splitCommandName(c.name).action.toLowerCase() === target);
|
|
161
|
-
if (found)
|
|
162
|
-
return found;
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
|
-
if (opt.name.toLowerCase() === target)
|
|
166
|
-
return opt;
|
|
167
|
-
}
|
|
168
|
-
return null;
|
|
169
|
-
}
|
|
170
|
-
function collectUsedNames(flagTokens) {
|
|
171
|
-
const used = new Set();
|
|
172
|
-
for (const t of flagTokens) {
|
|
173
|
-
if (!t.startsWith('--'))
|
|
174
|
-
continue;
|
|
175
|
-
const eq = t.indexOf('=');
|
|
176
|
-
const name = (eq >= 0 ? t.slice(2, eq) : t.slice(2)).toLowerCase();
|
|
177
|
-
if (name)
|
|
178
|
-
used.add(name);
|
|
179
|
-
}
|
|
180
|
-
return used;
|
|
181
|
-
}
|
|
182
|
-
function matchScore(text, query) {
|
|
183
|
-
const lower = text.toLowerCase();
|
|
184
|
-
if (lower.startsWith(query))
|
|
185
|
-
return 0;
|
|
186
|
-
if (lower.includes(query))
|
|
187
|
-
return 1;
|
|
188
|
-
return 2;
|
|
189
|
-
}
|
|
190
|
-
function fuzzyMatch(text, query) {
|
|
191
|
-
const lower = text.toLowerCase();
|
|
192
|
-
if (lower.includes(query))
|
|
193
|
-
return true;
|
|
194
|
-
let qi = 0;
|
|
195
|
-
for (let i = 0; i < lower.length && qi < query.length; i++) {
|
|
196
|
-
if (lower[i] === query[qi])
|
|
197
|
-
qi++;
|
|
198
|
-
}
|
|
199
|
-
return qi === query.length;
|
|
200
|
-
}
|
|
201
|
-
//# sourceMappingURL=completion-core.js.map
|
package/dist/completion.d.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @xfe-repo/cli - Shell 补全管理
|
|
3
|
-
*
|
|
4
|
-
* 使用 @bomb.sh/tab 实现 shell 自动补全
|
|
5
|
-
* 通过 Commander.js 适配器集成,支持 zsh / bash / fish
|
|
6
|
-
* install / uninstall 需手动检测 shell 类型并读写配置文件
|
|
7
|
-
*/
|
|
8
|
-
import tab from '@bomb.sh/tab/commander';
|
|
9
|
-
import type { Command } from 'commander';
|
|
10
|
-
/**
|
|
11
|
-
* 将 @bomb.sh/tab 补全能力注册到 Commander program(原地修改,包装 program.parse)
|
|
12
|
-
*
|
|
13
|
-
* 返回 RootCommand,可通过 `result.commands.get(name)?.options.get(key)?.handler = ...`
|
|
14
|
-
* 注册 option 值补全。
|
|
15
|
-
*/
|
|
16
|
-
export declare function initTabCompletion(program: Command): ReturnType<typeof tab>;
|
|
17
|
-
/** 安装 shell 自动补全到当前 shell 配置文件 */
|
|
18
|
-
export declare function installShellCompletion(): void;
|
|
19
|
-
/** 卸载 shell 自动补全(从配置文件中删除相关行) */
|
|
20
|
-
export declare function uninstallShellCompletion(): void;
|
|
21
|
-
//# sourceMappingURL=completion.d.ts.map
|
package/dist/completion.js
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @xfe-repo/cli - Shell 补全管理
|
|
3
|
-
*
|
|
4
|
-
* 使用 @bomb.sh/tab 实现 shell 自动补全
|
|
5
|
-
* 通过 Commander.js 适配器集成,支持 zsh / bash / fish
|
|
6
|
-
* install / uninstall 需手动检测 shell 类型并读写配置文件
|
|
7
|
-
*/
|
|
8
|
-
import fs from 'node:fs';
|
|
9
|
-
import os from 'node:os';
|
|
10
|
-
import path from 'node:path';
|
|
11
|
-
import { execSync } from 'node:child_process';
|
|
12
|
-
import tab from '@bomb.sh/tab/commander';
|
|
13
|
-
/** 根据 $SHELL 环境变量检测当前 shell 类型 */
|
|
14
|
-
function detectShell() {
|
|
15
|
-
const shell = process.env.SHELL ?? '';
|
|
16
|
-
if (shell.endsWith('zsh'))
|
|
17
|
-
return 'zsh';
|
|
18
|
-
if (shell.endsWith('bash'))
|
|
19
|
-
return 'bash';
|
|
20
|
-
if (shell.endsWith('fish'))
|
|
21
|
-
return 'fish';
|
|
22
|
-
return 'zsh';
|
|
23
|
-
}
|
|
24
|
-
function getShellConfig(shell) {
|
|
25
|
-
const home = os.homedir();
|
|
26
|
-
switch (shell) {
|
|
27
|
-
case 'zsh':
|
|
28
|
-
return {
|
|
29
|
-
rcFile: path.join(home, '.zshrc'),
|
|
30
|
-
sourceLine: 'source <(xfe complete zsh)',
|
|
31
|
-
};
|
|
32
|
-
case 'bash':
|
|
33
|
-
return {
|
|
34
|
-
rcFile: path.join(home, '.bashrc'),
|
|
35
|
-
sourceLine: 'source <(xfe complete bash)',
|
|
36
|
-
};
|
|
37
|
-
case 'fish':
|
|
38
|
-
return {
|
|
39
|
-
rcFile: path.join(home, '.config', 'fish', 'completions', 'xfe.fish'),
|
|
40
|
-
sourceLine: '', // fish 采用直接写入策略
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
// ============================================================
|
|
45
|
-
// 公开 API
|
|
46
|
-
// ============================================================
|
|
47
|
-
/**
|
|
48
|
-
* 将 @bomb.sh/tab 补全能力注册到 Commander program(原地修改,包装 program.parse)
|
|
49
|
-
*
|
|
50
|
-
* 返回 RootCommand,可通过 `result.commands.get(name)?.options.get(key)?.handler = ...`
|
|
51
|
-
* 注册 option 值补全。
|
|
52
|
-
*/
|
|
53
|
-
export function initTabCompletion(program) {
|
|
54
|
-
return tab(program);
|
|
55
|
-
}
|
|
56
|
-
/** 安装 shell 自动补全到当前 shell 配置文件 */
|
|
57
|
-
export function installShellCompletion() {
|
|
58
|
-
const shell = detectShell();
|
|
59
|
-
const config = getShellConfig(shell);
|
|
60
|
-
if (shell === 'fish') {
|
|
61
|
-
installFishCompletion(config.rcFile);
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
const { rcFile, sourceLine } = config;
|
|
65
|
-
// 如果配置文件中已存在补全行则幂等跳过
|
|
66
|
-
if (fs.existsSync(rcFile)) {
|
|
67
|
-
const content = fs.readFileSync(rcFile, 'utf8');
|
|
68
|
-
if (content.includes(sourceLine)) {
|
|
69
|
-
console.log(`✅ 补全已存在于 ${rcFile},无需重复安装`);
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
fs.appendFileSync(rcFile, `\n# xfe shell completion\n${sourceLine}\n`);
|
|
74
|
-
console.log(`✅ 补全已安装到 ${rcFile}`);
|
|
75
|
-
console.log(` 请运行 \`source ${rcFile}\` 或重新打开终端以生效`);
|
|
76
|
-
}
|
|
77
|
-
/** 卸载 shell 自动补全(从配置文件中删除相关行) */
|
|
78
|
-
export function uninstallShellCompletion() {
|
|
79
|
-
const shell = detectShell();
|
|
80
|
-
const config = getShellConfig(shell);
|
|
81
|
-
if (shell === 'fish') {
|
|
82
|
-
uninstallFishCompletion(config.rcFile);
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
const { rcFile, sourceLine } = config;
|
|
86
|
-
if (!fs.existsSync(rcFile)) {
|
|
87
|
-
console.log(`⚠️ 配置文件 ${rcFile} 不存在,无需卸载`);
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
const original = fs.readFileSync(rcFile, 'utf8');
|
|
91
|
-
const cleaned = original
|
|
92
|
-
.split('\n')
|
|
93
|
-
.filter((line) => line !== sourceLine && line !== '# xfe shell completion')
|
|
94
|
-
.join('\n')
|
|
95
|
-
.replace(/\n{3,}/g, '\n\n'); // 合并多余空行
|
|
96
|
-
if (original === cleaned) {
|
|
97
|
-
console.log(`⚠️ 未在 ${rcFile} 中找到 xfe 补全配置,无需卸载`);
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
fs.writeFileSync(rcFile, cleaned, 'utf8');
|
|
101
|
-
console.log(`✅ 补全已从 ${rcFile} 中移除`);
|
|
102
|
-
}
|
|
103
|
-
// ============================================================
|
|
104
|
-
// Fish 专用处理(直接写入补全文件)
|
|
105
|
-
// ============================================================
|
|
106
|
-
function installFishCompletion(completionFile) {
|
|
107
|
-
const dir = path.dirname(completionFile);
|
|
108
|
-
if (!fs.existsSync(dir))
|
|
109
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
110
|
-
try {
|
|
111
|
-
const script = execSync('xfe complete fish', { encoding: 'utf8' });
|
|
112
|
-
fs.writeFileSync(completionFile, script, 'utf8');
|
|
113
|
-
console.log(`✅ Fish 补全已安装到 ${completionFile}`);
|
|
114
|
-
}
|
|
115
|
-
catch {
|
|
116
|
-
console.error('❌ 生成 fish 补全脚本失败,请确认 xfe 已正确安装');
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
function uninstallFishCompletion(completionFile) {
|
|
120
|
-
if (!fs.existsSync(completionFile)) {
|
|
121
|
-
console.log(`⚠️ Fish 补全文件 ${completionFile} 不存在,无需卸载`);
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
fs.unlinkSync(completionFile);
|
|
125
|
-
console.log(`✅ Fish 补全文件 ${completionFile} 已删除`);
|
|
126
|
-
}
|
|
127
|
-
//# sourceMappingURL=completion.js.map
|