@xfe-repo/cli 2.0.12 → 2.1.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/CHANGELOG.md +21 -0
- package/dist/app.js +28 -15
- package/dist/components/StatusBar.js +33 -10
- package/dist/hooks/use-elapsed-time.d.ts +1 -1
- package/dist/hooks/use-elapsed-time.js +3 -3
- package/dist/launcher.js +1 -1
- package/dist/views/MenuView.js +51 -19
- package/dist/views/RunnerView.js +2 -2
- 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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @xfe-repo/cli
|
|
2
2
|
|
|
3
|
+
## 2.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 新增deploy自动部署支持
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies
|
|
12
|
+
- @xfe-repo/cli-presets@2.1.0
|
|
13
|
+
- @xfe-repo/cli-core@2.1.0
|
|
14
|
+
|
|
15
|
+
## 2.0.13
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- 修复窗口高度计算问题
|
|
20
|
+
- Updated dependencies
|
|
21
|
+
- @xfe-repo/cli-presets@2.0.8
|
|
22
|
+
- @xfe-repo/cli-core@2.0.8
|
|
23
|
+
|
|
3
24
|
## 2.0.12
|
|
4
25
|
|
|
5
26
|
### Patch Changes
|
package/dist/app.js
CHANGED
|
@@ -20,6 +20,7 @@ import { MenuView } from './views/MenuView.js';
|
|
|
20
20
|
import { RunnerView } from './views/RunnerView.js';
|
|
21
21
|
const BOTTOM_RESERVED_ROWS = 1;
|
|
22
22
|
const MIN_VIEW_ROWS = 1;
|
|
23
|
+
const FORCE_EXIT_TIMEOUT_MS = 3000;
|
|
23
24
|
// ============================================================
|
|
24
25
|
// App
|
|
25
26
|
// ============================================================
|
|
@@ -33,21 +34,31 @@ function AppInner({ runner, toast, scriptName }) {
|
|
|
33
34
|
const store = runner.getStore();
|
|
34
35
|
const storeSnapshot = useSyncExternalStore(store.subscribe, store.getState);
|
|
35
36
|
const viewRef = useRef(view);
|
|
37
|
+
const isExitingRef = useRef(false);
|
|
36
38
|
viewRef.current = view;
|
|
37
39
|
const doExit = useCallback(() => {
|
|
40
|
+
if (isExitingRef.current)
|
|
41
|
+
return;
|
|
42
|
+
isExitingRef.current = true;
|
|
43
|
+
if (!scriptName)
|
|
44
|
+
setView({ type: 'exiting' });
|
|
38
45
|
const ctx = runner.getContext();
|
|
39
46
|
// 兜底:若异步清理被未完成的 git 等操作阻塞,强制退出
|
|
40
|
-
const forceExitTimer = setTimeout(() => process.exit(0),
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
const forceExitTimer = setTimeout(() => process.exit(0), FORCE_EXIT_TIMEOUT_MS);
|
|
48
|
+
const cleanupAndExit = async () => {
|
|
49
|
+
try {
|
|
50
|
+
await ctx.sessions.killAll();
|
|
51
|
+
await ctx.exec.killAll();
|
|
52
|
+
await runner.triggerCleanup();
|
|
53
|
+
await waitUntilRenderFlush();
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
clearTimeout(forceExitTimer);
|
|
57
|
+
exit();
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
void cleanupAndExit();
|
|
61
|
+
}, [runner, scriptName, exit, waitUntilRenderFlush]);
|
|
51
62
|
const sessionManager = useSessionManager({
|
|
52
63
|
runner,
|
|
53
64
|
directScriptName: scriptName,
|
|
@@ -166,7 +177,7 @@ function AppInner({ runner, toast, scriptName }) {
|
|
|
166
177
|
const activeLogger = activeSession?.logger ?? emptyLoggerRef.current;
|
|
167
178
|
const { entries: logEntries } = useLogs(activeLogger);
|
|
168
179
|
const { rows: terminalRows } = useWindowSize();
|
|
169
|
-
const availableViewRows = resolveAvailableViewRows({ terminalRows
|
|
180
|
+
const availableViewRows = resolveAvailableViewRows({ terminalRows });
|
|
170
181
|
// 当 session 被移除但 view 仍为 session 时,自动回退到菜单(直接命令模式不回退,等待 doExit 完成)
|
|
171
182
|
useEffect(() => {
|
|
172
183
|
if (scriptName)
|
|
@@ -189,11 +200,13 @@ function AppInner({ runner, toast, scriptName }) {
|
|
|
189
200
|
}
|
|
190
201
|
case 'exit-confirm':
|
|
191
202
|
return (_jsxs(Box, { flexDirection: "column", gap: 1, borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [_jsxs(Text, { color: "yellow", bold: true, children: ["\u5F53\u524D\u6709 ", sessionManager.runningSessions.length, " \u4E2A\u4EFB\u52A1\u6B63\u5728\u8FD0\u884C"] }), _jsx(Text, { children: "\u9000\u51FA\u5C06\u7EC8\u6B62\u6240\u6709\u8FD0\u884C\u4E2D\u7684\u4EFB\u52A1\uFF0C\u786E\u8BA4\u9000\u51FA\uFF1F(\u56DE\u8F66\u76F4\u63A5\u9000\u51FA)" }), _jsx(ConfirmInput, { onConfirm: doExit, onCancel: () => setView({ type: 'menu' }) })] }));
|
|
203
|
+
case 'exiting':
|
|
204
|
+
return (_jsxs(Box, { flexDirection: "column", gap: 1, borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [_jsx(Spinner, { label: "\u6B63\u5728\u9000\u51FA..." }), _jsx(Text, { color: "gray", children: "\u6B63\u5728\u7EC8\u6B62\u4EFB\u52A1\u5E76\u91CA\u653E\u8D44\u6E90" })] }));
|
|
192
205
|
case 'error':
|
|
193
206
|
return (_jsx(Box, { children: _jsxs(Text, { color: "red", children: ["ERROR: ", view.message] }) }));
|
|
194
207
|
}
|
|
195
208
|
};
|
|
196
|
-
return (_jsxs(_Fragment, { children: [_jsx(Static, { items: logEntries, children: (entry, i) => _jsx(LogLine, { entry: entry }, `${entry.timestamp}-${i}`) }), _jsx(Box, { flexDirection: "column",
|
|
209
|
+
return (_jsxs(_Fragment, { children: [_jsx(Static, { items: logEntries, children: (entry, i) => _jsx(LogLine, { entry: entry }, `${entry.timestamp}-${i}`) }), _jsx(Box, { flexDirection: "column", maxHeight: availableViewRows, overflow: "hidden", children: renderView(availableViewRows) })] }));
|
|
197
210
|
}
|
|
198
211
|
// ─── Render Helpers ─────────────────────────────────────────
|
|
199
212
|
function LogLine({ entry }) {
|
|
@@ -208,7 +221,7 @@ function LogLine({ entry }) {
|
|
|
208
221
|
return _jsx(Text, { color: color, children: entry.message });
|
|
209
222
|
return _jsx(Text, { children: entry.message });
|
|
210
223
|
}
|
|
211
|
-
function resolveAvailableViewRows({ terminalRows
|
|
212
|
-
return Math.max(MIN_VIEW_ROWS, terminalRows -
|
|
224
|
+
function resolveAvailableViewRows({ terminalRows }) {
|
|
225
|
+
return Math.max(MIN_VIEW_ROWS, terminalRows - BOTTOM_RESERVED_ROWS);
|
|
213
226
|
}
|
|
214
227
|
//# sourceMappingURL=app.js.map
|
|
@@ -7,23 +7,46 @@ import { Text } from 'ink';
|
|
|
7
7
|
import Link from 'ink-link';
|
|
8
8
|
import { resolveBadgeProp } from '@xfe-repo/cli-core';
|
|
9
9
|
import { useElapsedTime, formatElapsed } from '../hooks/use-elapsed-time.js';
|
|
10
|
+
const BADGE_REFRESH_START_TIME = Date.now();
|
|
10
11
|
export const StatusBar = memo(function StatusBar({ projectName, scriptName, startTime, endTime, badges, storeSnapshot }) {
|
|
11
12
|
const stopped = endTime !== undefined;
|
|
12
|
-
const elapsed = useElapsedTime(startTime, stopped);
|
|
13
13
|
const statusBadges = useMemo(() => (badges ?? []).filter((b) => matchSlot(b, 'status')), [badges]);
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
14
|
+
const elapsed = useElapsedTime(startTime, stopped);
|
|
15
|
+
return (_jsxs(Text, { dimColor: true, children: [projectName, scriptName && _jsxs(Text, { children: [" | ", scriptName] }), statusBadges.map((badge) => (_jsx(StatusBadgeItem, { badge: badge, storeSnapshot: storeSnapshot }, badge.name))), elapsed !== null && _jsxs(Text, { children: [" | ", formatElapsed(stopped ? endTime - startTime : elapsed)] })] }));
|
|
16
|
+
});
|
|
17
|
+
const StatusBadgeItem = memo(function StatusBadgeItem({ badge, storeSnapshot }) {
|
|
18
|
+
useBadgeRefresh(badge);
|
|
19
|
+
const value = storeSnapshot ? resolveBadgeProp(badge.value, storeSnapshot) : undefined;
|
|
20
|
+
if (!value)
|
|
21
|
+
return null;
|
|
22
|
+
const resolvedColor = storeSnapshot ? resolveBadgeProp(badge.color, storeSnapshot) : undefined;
|
|
23
|
+
const resolvedIcon = storeSnapshot ? resolveBadgeProp(badge.icon, storeSnapshot) : undefined;
|
|
24
|
+
const resolvedUrl = storeSnapshot ? resolveBadgeProp(badge.url, storeSnapshot) : undefined;
|
|
25
|
+
const label = (_jsxs(Text, { color: resolvedColor, children: [badge.label, ": ", value, resolvedIcon ? ` ${resolvedIcon}` : ''] }));
|
|
26
|
+
return (_jsxs(Text, { children: [' | ', resolvedUrl ? _jsx(Link, { url: resolvedUrl, children: label }) : label] }));
|
|
23
27
|
});
|
|
24
28
|
// ─── Helpers ────────────────────────────────────────────────
|
|
25
29
|
/** 判断 badge 是否匹配目标 slot(兼容单个或数组) */
|
|
26
30
|
function matchSlot(badge, target) {
|
|
27
31
|
return Array.isArray(badge.slot) ? badge.slot.includes(target) : badge.slot === target;
|
|
28
32
|
}
|
|
33
|
+
function useBadgeRefresh(badge) {
|
|
34
|
+
const interval = resolveBadgeRefreshInterval(badge);
|
|
35
|
+
useElapsedTime(interval ? BADGE_REFRESH_START_TIME : null, false, interval);
|
|
36
|
+
}
|
|
37
|
+
function resolveBadgeRefreshInterval(badge) {
|
|
38
|
+
if (!badge.interval)
|
|
39
|
+
return undefined;
|
|
40
|
+
if (!Number.isFinite(badge.interval) || badge.interval <= 0)
|
|
41
|
+
return undefined;
|
|
42
|
+
if (!hasDynamicBadgeProp(badge))
|
|
43
|
+
return undefined;
|
|
44
|
+
return badge.interval;
|
|
45
|
+
}
|
|
46
|
+
function hasDynamicBadgeProp(badge) {
|
|
47
|
+
return (typeof badge.value === 'function' ||
|
|
48
|
+
typeof badge.color === 'function' ||
|
|
49
|
+
typeof badge.icon === 'function' ||
|
|
50
|
+
typeof badge.url === 'function');
|
|
51
|
+
}
|
|
29
52
|
//# sourceMappingURL=StatusBar.js.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 根据 startTime 计算并实时更新已用时间
|
|
5
5
|
*/
|
|
6
6
|
/** 根据 startTime 实时计算经过的毫秒数,stopped 为 true 时停止计时并保留最终值 */
|
|
7
|
-
export declare function useElapsedTime(startTime?: number | null, stopped?: boolean): number | null;
|
|
7
|
+
export declare function useElapsedTime(startTime?: number | null, stopped?: boolean, intervalMs?: number): number | null;
|
|
8
8
|
/** 将毫秒格式化为 MM:SS */
|
|
9
9
|
export declare function formatElapsed(ms: number): string;
|
|
10
10
|
//# sourceMappingURL=use-elapsed-time.d.ts.map
|
|
@@ -7,7 +7,7 @@ import { useState, useEffect, useRef } from 'react';
|
|
|
7
7
|
const TIMER_INTERVAL_MS = 1000;
|
|
8
8
|
const SECONDS_PER_MINUTE = 60;
|
|
9
9
|
/** 根据 startTime 实时计算经过的毫秒数,stopped 为 true 时停止计时并保留最终值 */
|
|
10
|
-
export function useElapsedTime(startTime, stopped = false) {
|
|
10
|
+
export function useElapsedTime(startTime, stopped = false, intervalMs = TIMER_INTERVAL_MS) {
|
|
11
11
|
const [elapsed, setElapsed] = useState(null);
|
|
12
12
|
const intervalRef = useRef(null);
|
|
13
13
|
useEffect(() => {
|
|
@@ -24,9 +24,9 @@ export function useElapsedTime(startTime, stopped = false) {
|
|
|
24
24
|
return cleanup;
|
|
25
25
|
intervalRef.current = setInterval(() => {
|
|
26
26
|
setElapsed(Date.now() - startTime);
|
|
27
|
-
},
|
|
27
|
+
}, intervalMs);
|
|
28
28
|
return cleanup;
|
|
29
|
-
}, [startTime, stopped]);
|
|
29
|
+
}, [startTime, stopped, intervalMs]);
|
|
30
30
|
return elapsed;
|
|
31
31
|
}
|
|
32
32
|
/** 将毫秒格式化为 MM:SS */
|
package/dist/launcher.js
CHANGED
|
@@ -69,7 +69,7 @@ export async function launch(options) {
|
|
|
69
69
|
exitOnCtrlC: false,
|
|
70
70
|
incrementalRendering: true,
|
|
71
71
|
concurrent: true,
|
|
72
|
-
alternateScreen:
|
|
72
|
+
alternateScreen: false,
|
|
73
73
|
kittyKeyboard: { mode: !scriptName ? 'enabled' : 'disabled' },
|
|
74
74
|
});
|
|
75
75
|
await waitUntilExit();
|
package/dist/views/MenuView.js
CHANGED
|
@@ -19,6 +19,8 @@ import { Toast } from '../components/Toast.js';
|
|
|
19
19
|
import { Commands } from '../components/Commands.js';
|
|
20
20
|
// ─── Constants ──────────────────────────────────────────────
|
|
21
21
|
const LOGO_HEIGHT = 7;
|
|
22
|
+
const BADGE_REFRESH_START_TIME = Date.now();
|
|
23
|
+
const HEADER_BADGE_SEPARATOR = '·';
|
|
22
24
|
// 非建议区域的固定行数(header + commands chrome + footer 等)
|
|
23
25
|
const BASE_CHROME_HEIGHT = 12;
|
|
24
26
|
const TASK_GRID_CHROME_HEIGHT = 6;
|
|
@@ -68,29 +70,59 @@ export const MenuView = memo(function MenuView({ availableRows, projectName, com
|
|
|
68
70
|
layer: KeymapLayer.Panel,
|
|
69
71
|
isActive: effectiveFocus === 'tasks',
|
|
70
72
|
});
|
|
71
|
-
return (_jsxs(Box, { flexDirection: "column", children: [showLogo && _jsx(Logo, {}),
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
73
|
+
return (_jsxs(Box, { flexDirection: "column", children: [showLogo && _jsx(Logo, {}), _jsxs(Box, { flexDirection: "row", flexWrap: "wrap", margin: 1, flexShrink: 0, children: [_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: "cyan", bold: true, children: ["\u5F53\u524D\u9879\u76EE:", ' '] }), _jsx(Text, { bold: true, children: projectName })] }), headerBadges.map((badge) => (_jsx(HeaderBadge, { badge: badge, storeSnapshot: storeSnapshot }, badge.name)))] }), hasActiveSessions && (_jsx(Box, { flexShrink: 0, children: _jsx(TaskGrid, { activeSessions: activeSessions, isFocused: effectiveFocus === 'tasks', onFocusSession: onFocusSession }) })), _jsx(Box, { flexShrink: 1, flexDirection: "column", overflow: "hidden", children: _jsx(Commands, { commandOptions: commandOptions, isActive: effectiveFocus === 'commands', maxSuggestions: maxSuggestions, onSelect: onSelect, onUserInput: onUserInput, onExit: onExit, onTabFallthrough: handleTabFallthrough }) }), _jsxs(Box, { flexDirection: "row", flexWrap: "wrap", justifyContent: "space-between", marginLeft: 1, flexShrink: 0, children: [footerBadges.length > 0 && (_jsx(Box, { flexDirection: "row", flexWrap: "wrap", children: footerBadges.map((badge) => (_jsx(FooterBadge, { badge: badge, storeSnapshot: storeSnapshot }, badge.name))) })), toasts.length > 0 && (_jsx(Box, { flexDirection: "column", alignItems: "flex-end", children: toasts.map((t) => (_jsx(Toast, { message: t.message, level: t.level }, t.id))) }))] })] }));
|
|
74
|
+
});
|
|
75
|
+
const HeaderBadge = memo(function HeaderBadge({ badge, storeSnapshot }) {
|
|
76
|
+
useBadgeRefresh(badge);
|
|
77
|
+
const value = resolveBadgeProp(badge.value, storeSnapshot);
|
|
78
|
+
if (!value)
|
|
79
|
+
return null;
|
|
80
|
+
const resolvedColor = resolveBadgeProp(badge.color, storeSnapshot);
|
|
81
|
+
const resolvedIcon = resolveBadgeProp(badge.icon, storeSnapshot);
|
|
82
|
+
const resolvedUrl = resolveBadgeProp(badge.url, storeSnapshot);
|
|
83
|
+
const badgeValue = formatHeaderBadgeValue(value, resolvedIcon);
|
|
84
|
+
const content = (_jsxs(Text, { color: resolvedColor ?? 'gray', children: [badge.label, " ", badgeValue] }));
|
|
85
|
+
return (_jsxs(Box, { flexDirection: "row", marginLeft: 1, children: [_jsx(Text, { dimColor: true, children: HEADER_BADGE_SEPARATOR }), _jsx(Text, { children: " " }), resolvedUrl ? _jsx(Link, { url: resolvedUrl, children: content }) : content] }));
|
|
86
|
+
});
|
|
87
|
+
const FooterBadge = memo(function FooterBadge({ badge, storeSnapshot }) {
|
|
88
|
+
useBadgeRefresh(badge);
|
|
89
|
+
const value = resolveBadgeProp(badge.value, storeSnapshot);
|
|
90
|
+
if (!value)
|
|
91
|
+
return null;
|
|
92
|
+
const resolvedColor = resolveBadgeProp(badge.color, storeSnapshot);
|
|
93
|
+
const resolvedIcon = resolveBadgeProp(badge.icon, storeSnapshot);
|
|
94
|
+
const resolvedUrl = resolveBadgeProp(badge.url, storeSnapshot);
|
|
95
|
+
const prefix = resolvedIcon ?? '●';
|
|
96
|
+
const content = (_jsxs(Text, { color: resolvedColor ?? 'gray', children: [prefix, " ", value] }));
|
|
97
|
+
return _jsx(Box, { marginRight: 2, children: resolvedUrl ? _jsx(Link, { url: resolvedUrl, children: content }) : content });
|
|
90
98
|
});
|
|
91
99
|
// ─── Helpers ────────────────────────────────────────────────
|
|
92
100
|
/** 判断 badge 是否匹配目标 slot(兼容单个或数组) */
|
|
93
101
|
function matchSlot(badge, target) {
|
|
94
102
|
return Array.isArray(badge.slot) ? badge.slot.includes(target) : badge.slot === target;
|
|
95
103
|
}
|
|
104
|
+
function formatHeaderBadgeValue(value, icon) {
|
|
105
|
+
if (!icon)
|
|
106
|
+
return value;
|
|
107
|
+
return `${value} ${icon}`;
|
|
108
|
+
}
|
|
109
|
+
function useBadgeRefresh(badge) {
|
|
110
|
+
const interval = resolveBadgeRefreshInterval(badge);
|
|
111
|
+
useElapsedTime(interval ? BADGE_REFRESH_START_TIME : null, false, interval);
|
|
112
|
+
}
|
|
113
|
+
function resolveBadgeRefreshInterval(badge) {
|
|
114
|
+
if (!badge.interval)
|
|
115
|
+
return undefined;
|
|
116
|
+
if (!Number.isFinite(badge.interval) || badge.interval <= 0)
|
|
117
|
+
return undefined;
|
|
118
|
+
if (!hasDynamicBadgeProp(badge))
|
|
119
|
+
return undefined;
|
|
120
|
+
return badge.interval;
|
|
121
|
+
}
|
|
122
|
+
function hasDynamicBadgeProp(badge) {
|
|
123
|
+
return (typeof badge.value === 'function' ||
|
|
124
|
+
typeof badge.color === 'function' ||
|
|
125
|
+
typeof badge.icon === 'function' ||
|
|
126
|
+
typeof badge.url === 'function');
|
|
127
|
+
}
|
|
96
128
|
//# sourceMappingURL=MenuView.js.map
|
package/dist/views/RunnerView.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* @xfe-repo/cli - 运行视图
|
|
4
4
|
*/
|
|
@@ -26,7 +26,7 @@ export const RunnerView = memo(function RunnerView({ availableRows, projectName,
|
|
|
26
26
|
hasSpinnerStatus: isSpinnerStatusVisible(spinnerState),
|
|
27
27
|
hasStatusBar: !isSingleCommand,
|
|
28
28
|
});
|
|
29
|
-
return (_jsxs(
|
|
29
|
+
return (_jsxs(Box, { flexDirection: "column", children: [layout.showLogo && _jsx(Logo, {}), _jsxs(Box, { flexDirection: "column", gap: 1, overflowX: "hidden", children: [_jsx(SpinnerStatus, { state: spinnerState }), hasPrompt && _jsx(PromptView, { question: request.question, maxRows: layout.promptRows, onSubmit: complete }, request.id)] }), !isSingleCommand && (_jsxs(Box, { gap: 1, borderStyle: "round", borderColor: isReadOnly ? 'blue' : 'cyan', paddingX: 1, overflowX: "hidden", children: [_jsx(StatusBar, { projectName: projectName, scriptName: scriptName, startTime: startTime, endTime: endTime, badges: badges, storeSnapshot: storeSnapshot }), _jsx(StatusHint, { isReadOnly: isReadOnly, hasError: hasError, isCompleted: isCompleted })] }))] }));
|
|
30
30
|
});
|
|
31
31
|
// ─── Layout Helpers ────────────────────────────────────────
|
|
32
32
|
function resolveRunnerLayout({ availableRows, hasPrompt, hasSpinnerStatus, hasStatusBar }) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xfe-repo/cli",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "XFE CLI - Ink-based terminal UI for project scaffolding",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"ink-link": "^5.0.0",
|
|
18
18
|
"react": "^19.1.0",
|
|
19
19
|
"zod": "^4.3.6",
|
|
20
|
-
"@xfe-repo/cli-core": "2.0
|
|
21
|
-
"@xfe-repo/cli-presets": "2.0
|
|
20
|
+
"@xfe-repo/cli-core": "2.1.0",
|
|
21
|
+
"@xfe-repo/cli-presets": "2.1.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@types/node": "^24.3.0",
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* XFE CLI HTTP Backend Gateway。
|
|
3
|
-
*
|
|
4
|
-
* CLI 通过该适配器连接本地或远端 xfe server。
|
|
5
|
-
*/
|
|
6
|
-
export interface BackendGatewayOptions {
|
|
7
|
-
readonly baseUrl: string;
|
|
8
|
-
readonly tenantId?: string;
|
|
9
|
-
}
|
|
10
|
-
export interface CreateRemoteTaskOptions {
|
|
11
|
-
readonly repoUrl: string;
|
|
12
|
-
readonly taskId: string;
|
|
13
|
-
readonly description: string;
|
|
14
|
-
readonly documentUrl?: string;
|
|
15
|
-
readonly baseBranch?: string;
|
|
16
|
-
readonly branchName?: string;
|
|
17
|
-
readonly workflowId?: string;
|
|
18
|
-
readonly prepareWorkspace?: boolean;
|
|
19
|
-
}
|
|
20
|
-
export interface StartRemoteTaskOptions {
|
|
21
|
-
readonly taskId: string;
|
|
22
|
-
readonly executor?: string;
|
|
23
|
-
readonly autoApprove?: boolean;
|
|
24
|
-
}
|
|
25
|
-
export interface RespondClientRequestOptions {
|
|
26
|
-
readonly requestId: string;
|
|
27
|
-
readonly action: 'accept' | 'decline' | 'cancel';
|
|
28
|
-
readonly content?: Record<string, unknown>;
|
|
29
|
-
}
|
|
30
|
-
export interface ListClientRequestsOptions {
|
|
31
|
-
readonly status?: string;
|
|
32
|
-
}
|
|
33
|
-
export declare class HttpBackendGateway {
|
|
34
|
-
private readonly baseUrl;
|
|
35
|
-
private readonly tenantId?;
|
|
36
|
-
constructor(options: BackendGatewayOptions);
|
|
37
|
-
health(): Promise<unknown>;
|
|
38
|
-
createTask(options: CreateRemoteTaskOptions): Promise<unknown>;
|
|
39
|
-
startTask(options: StartRemoteTaskOptions): Promise<unknown>;
|
|
40
|
-
listClientRequests(options?: ListClientRequestsOptions): Promise<unknown>;
|
|
41
|
-
respondClientRequest(options: RespondClientRequestOptions): Promise<unknown>;
|
|
42
|
-
private get;
|
|
43
|
-
private post;
|
|
44
|
-
private createUrl;
|
|
45
|
-
private createHeaders;
|
|
46
|
-
}
|
|
47
|
-
//# sourceMappingURL=http-backend.d.ts.map
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* XFE CLI HTTP Backend Gateway。
|
|
3
|
-
*
|
|
4
|
-
* CLI 通过该适配器连接本地或远端 xfe server。
|
|
5
|
-
*/
|
|
6
|
-
// ─── Gateway ───────────────────────────────────────────────
|
|
7
|
-
export class HttpBackendGateway {
|
|
8
|
-
baseUrl;
|
|
9
|
-
tenantId;
|
|
10
|
-
constructor(options) {
|
|
11
|
-
this.baseUrl = trimTrailingSlash(options.baseUrl);
|
|
12
|
-
this.tenantId = options.tenantId;
|
|
13
|
-
}
|
|
14
|
-
async health() {
|
|
15
|
-
return this.get('/health');
|
|
16
|
-
}
|
|
17
|
-
async createTask(options) {
|
|
18
|
-
return this.post('/tasks', options);
|
|
19
|
-
}
|
|
20
|
-
async startTask(options) {
|
|
21
|
-
return this.post(`/tasks/${encodeURIComponent(options.taskId)}/start`, {
|
|
22
|
-
executor: options.executor,
|
|
23
|
-
autoApprove: options.autoApprove,
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
async listClientRequests(options = {}) {
|
|
27
|
-
return this.get('/client-requests', { status: options.status });
|
|
28
|
-
}
|
|
29
|
-
async respondClientRequest(options) {
|
|
30
|
-
return this.post(`/client-requests/${encodeURIComponent(options.requestId)}/respond`, {
|
|
31
|
-
action: options.action,
|
|
32
|
-
content: options.content,
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
async get(pathname, query) {
|
|
36
|
-
const response = await fetch(this.createUrl(pathname, query), { headers: this.createHeaders() });
|
|
37
|
-
return readResponse(response);
|
|
38
|
-
}
|
|
39
|
-
async post(pathname, body) {
|
|
40
|
-
const response = await fetch(this.createUrl(pathname), {
|
|
41
|
-
method: 'POST',
|
|
42
|
-
headers: { ...this.createHeaders(), 'Content-Type': 'application/json' },
|
|
43
|
-
body: JSON.stringify(body),
|
|
44
|
-
});
|
|
45
|
-
return readResponse(response);
|
|
46
|
-
}
|
|
47
|
-
createUrl(pathname, query = {}) {
|
|
48
|
-
const url = new URL(`${this.baseUrl}${pathname}`);
|
|
49
|
-
for (const [key, value] of Object.entries(query)) {
|
|
50
|
-
if (value)
|
|
51
|
-
url.searchParams.set(key, value);
|
|
52
|
-
}
|
|
53
|
-
return url.toString();
|
|
54
|
-
}
|
|
55
|
-
createHeaders() {
|
|
56
|
-
if (!this.tenantId)
|
|
57
|
-
return {};
|
|
58
|
-
return { 'x-xfe-tenant-id': this.tenantId };
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
// ─── Helpers ───────────────────────────────────────────────
|
|
62
|
-
async function readResponse(response) {
|
|
63
|
-
const body = await readJson(response);
|
|
64
|
-
if (!response.ok) {
|
|
65
|
-
const message = typeof body === 'object' && body && 'error' in body ? String(body.error) : response.statusText;
|
|
66
|
-
throw new Error(`HTTP ${response.status}: ${message}`);
|
|
67
|
-
}
|
|
68
|
-
return body;
|
|
69
|
-
}
|
|
70
|
-
async function readJson(response) {
|
|
71
|
-
const text = await response.text();
|
|
72
|
-
if (!text)
|
|
73
|
-
return undefined;
|
|
74
|
-
try {
|
|
75
|
-
return JSON.parse(text);
|
|
76
|
-
}
|
|
77
|
-
catch {
|
|
78
|
-
return text;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
function trimTrailingSlash(value) {
|
|
82
|
-
return value.replace(/\/+$/, '');
|
|
83
|
-
}
|
|
84
|
-
//# sourceMappingURL=http-backend.js.map
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* XFE CLI Server/Task 静态命令。
|
|
3
|
-
*
|
|
4
|
-
* 负责本地 server 启动、远端任务创建和 client request 交互循环。
|
|
5
|
-
*/
|
|
6
|
-
import { Command } from 'commander';
|
|
7
|
-
export declare function registerServerCommands(program: Command): void;
|
|
8
|
-
export declare function registerTaskCommands(program: Command): void;
|
|
9
|
-
//# sourceMappingURL=server-task-commands.d.ts.map
|