nolo-cli 0.64.1 → 0.65.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
CHANGED
|
@@ -282,6 +282,7 @@ npm pack
|
|
|
282
282
|
- `nolo-cli` 作为入口包,包含一个 Node.js 兼容的 `index.js` 引导脚本
|
|
283
283
|
- 引导脚本优先调用原生二进制;缺失时回退到 `bun index.ts`
|
|
284
284
|
- **原生二进制通过 S3 下载站分发**(`install-nolo.sh`),不走 npm 可选依赖——npm 包始终是纯 JS/Bun 版本
|
|
285
|
+
- `install-nolo.sh` 会把安装目录写进 shell 启动文件(已存在的 `~/.zshrc`/`~/.bashrc`/`~/.bash_profile`/`~/.profile`,缺失时创建登录 shell 对应的那个),bash/zsh 新终端开箱即用 `nolo`,无需手动改 PATH;写入以 `# nolo-cli-path` 为标记、可重复执行、可用 `NOLO_NO_MODIFY_PATH=1` 关闭
|
|
285
286
|
|
|
286
287
|
支持的平台:macOS Apple Silicon(`darwin-arm64`)。
|
|
287
288
|
|
package/index.js
CHANGED
|
@@ -815,7 +815,7 @@ async function runScript(script, forwardedArgs, env) {
|
|
|
815
815
|
return proc.exited;
|
|
816
816
|
}
|
|
817
817
|
async function launchTuiWorkspace(args2) {
|
|
818
|
-
const { startTuiWorkspace } = await import("./readlineWorkspace-
|
|
818
|
+
const { startTuiWorkspace } = await import("./readlineWorkspace-7FNJDYLD.js");
|
|
819
819
|
const { createTuiSummaryLlmCaller } = await import("./tuiSummaryLlmCaller-F3DPFFZM.js");
|
|
820
820
|
return startTuiWorkspace({
|
|
821
821
|
...args2,
|
package/package.json
CHANGED
|
@@ -2215,7 +2215,7 @@ function createHistoryOutputStream(history, onUpdate) {
|
|
|
2215
2215
|
};
|
|
2216
2216
|
return stream;
|
|
2217
2217
|
}
|
|
2218
|
-
function
|
|
2218
|
+
function computeScrollMetrics(history, output, inputLines, reservedRows = 0) {
|
|
2219
2219
|
const tty = output;
|
|
2220
2220
|
const rows = tty.rows ?? 24;
|
|
2221
2221
|
const columns = tty.columns ?? 80;
|
|
@@ -2230,6 +2230,15 @@ function applyScrollAction(history, action, output, inputLines, reservedRows = 0
|
|
|
2230
2230
|
totalLines += currentTurnSeparator(history) + countTurnLines(history.currentRole, history.currentContent, contentWidth);
|
|
2231
2231
|
}
|
|
2232
2232
|
const maxScrollTop = Math.max(0, totalLines - visibleHeight);
|
|
2233
|
+
return { visibleHeight, contentWidth, totalLines, maxScrollTop };
|
|
2234
|
+
}
|
|
2235
|
+
function applyScrollAction(history, action, output, inputLines, reservedRows = 0) {
|
|
2236
|
+
const { visibleHeight, maxScrollTop } = computeScrollMetrics(
|
|
2237
|
+
history,
|
|
2238
|
+
output,
|
|
2239
|
+
inputLines,
|
|
2240
|
+
reservedRows
|
|
2241
|
+
);
|
|
2233
2242
|
history.followBottom = false;
|
|
2234
2243
|
switch (action) {
|
|
2235
2244
|
case "page-up":
|
|
@@ -8214,7 +8223,118 @@ function createTuiRender(host) {
|
|
|
8214
8223
|
renderHistoryToOutput,
|
|
8215
8224
|
renderHistoryUnderDialog,
|
|
8216
8225
|
scheduleRender,
|
|
8217
|
-
flushPendingRender
|
|
8226
|
+
flushPendingRender,
|
|
8227
|
+
// 立即绘制一帧同步帧(BSU/ESU + 光标隐藏)。流式路径走 33ms 节流的
|
|
8228
|
+
// scheduleRender;平滑滚动推进器按 16ms 自己的节奏调用这里,避免被节流
|
|
8229
|
+
// 抹平成"每 33ms 一跳"。
|
|
8230
|
+
paintImmediate: paintSyncedFrame
|
|
8231
|
+
};
|
|
8232
|
+
}
|
|
8233
|
+
|
|
8234
|
+
// packages/cli/tui/tuiScrollAnimation.ts
|
|
8235
|
+
var SCROLL_ANIMATION_TICK_MS = 16;
|
|
8236
|
+
var DEFAULT_SCROLL_MAX_LINES_PER_TICK = 6;
|
|
8237
|
+
function readPositiveInt(raw, fallback, max) {
|
|
8238
|
+
if (raw === void 0 || raw.trim() === "") return fallback;
|
|
8239
|
+
const parsed = Number.parseInt(raw, 10);
|
|
8240
|
+
if (!Number.isFinite(parsed) || parsed < 1) return fallback;
|
|
8241
|
+
return Math.min(parsed, max);
|
|
8242
|
+
}
|
|
8243
|
+
function resolveScrollTuning(env = process.env) {
|
|
8244
|
+
return {
|
|
8245
|
+
wheelLines: readPositiveInt(env.NOLO_TUI_WHEEL_LINES, WHEEL_SCROLL_LINES, 50),
|
|
8246
|
+
maxLinesPerTick: readPositiveInt(
|
|
8247
|
+
env.NOLO_TUI_SCROLL_MAX_STEP,
|
|
8248
|
+
DEFAULT_SCROLL_MAX_LINES_PER_TICK,
|
|
8249
|
+
100
|
|
8250
|
+
)
|
|
8251
|
+
};
|
|
8252
|
+
}
|
|
8253
|
+
var defaultTickTimer = {
|
|
8254
|
+
every(ms, fn) {
|
|
8255
|
+
const handle = setInterval(fn, ms);
|
|
8256
|
+
handle.unref?.();
|
|
8257
|
+
return () => clearInterval(handle);
|
|
8258
|
+
}
|
|
8259
|
+
};
|
|
8260
|
+
function createScrollAnimator(deps) {
|
|
8261
|
+
const { wheelLines, maxLinesPerTick } = resolveScrollTuning(
|
|
8262
|
+
deps.env ?? process.env
|
|
8263
|
+
);
|
|
8264
|
+
const timer = deps.tickTimer ?? defaultTickTimer;
|
|
8265
|
+
const history = deps.history;
|
|
8266
|
+
let goal = null;
|
|
8267
|
+
let intent = null;
|
|
8268
|
+
let stopTicker = null;
|
|
8269
|
+
const metrics = () => computeScrollMetrics(
|
|
8270
|
+
history,
|
|
8271
|
+
deps.output,
|
|
8272
|
+
deps.getInputLines(),
|
|
8273
|
+
deps.getReservedRows?.() ?? 0
|
|
8274
|
+
);
|
|
8275
|
+
const stop = () => {
|
|
8276
|
+
if (stopTicker) {
|
|
8277
|
+
stopTicker();
|
|
8278
|
+
stopTicker = null;
|
|
8279
|
+
}
|
|
8280
|
+
goal = null;
|
|
8281
|
+
intent = null;
|
|
8282
|
+
};
|
|
8283
|
+
const settle = (maxScrollTop) => {
|
|
8284
|
+
if (intent === "down" && history.scrollTop >= maxScrollTop) {
|
|
8285
|
+
history.followBottom = true;
|
|
8286
|
+
}
|
|
8287
|
+
stop();
|
|
8288
|
+
};
|
|
8289
|
+
const tick = () => {
|
|
8290
|
+
if (goal === null) {
|
|
8291
|
+
stop();
|
|
8292
|
+
return;
|
|
8293
|
+
}
|
|
8294
|
+
const { maxScrollTop } = metrics();
|
|
8295
|
+
if (goal > maxScrollTop) goal = maxScrollTop;
|
|
8296
|
+
const delta = goal - history.scrollTop;
|
|
8297
|
+
if (delta === 0) {
|
|
8298
|
+
settle(maxScrollTop);
|
|
8299
|
+
return;
|
|
8300
|
+
}
|
|
8301
|
+
const step = Math.sign(delta) * Math.min(Math.abs(delta), maxLinesPerTick);
|
|
8302
|
+
history.scrollTop += step;
|
|
8303
|
+
deps.onPaint();
|
|
8304
|
+
if (history.scrollTop === goal) {
|
|
8305
|
+
settle(maxScrollTop);
|
|
8306
|
+
}
|
|
8307
|
+
};
|
|
8308
|
+
const ensureTicker = () => {
|
|
8309
|
+
if (!stopTicker) {
|
|
8310
|
+
stopTicker = timer.every(SCROLL_ANIMATION_TICK_MS, tick);
|
|
8311
|
+
}
|
|
8312
|
+
};
|
|
8313
|
+
return {
|
|
8314
|
+
/**
|
|
8315
|
+
* 一条滚轮报告:折算为目标位置并保证推进器在跑。
|
|
8316
|
+
* followBottom 先退出(与 applyScrollAction 的 wheel 分支一致)。
|
|
8317
|
+
*/
|
|
8318
|
+
wheel(direction) {
|
|
8319
|
+
const isOpposing = intent !== null && intent !== direction;
|
|
8320
|
+
const base = isOpposing ? history.scrollTop : goal ?? history.scrollTop;
|
|
8321
|
+
const next = direction === "up" ? base - wheelLines : base + wheelLines;
|
|
8322
|
+
goal = Math.max(0, next);
|
|
8323
|
+
intent = direction;
|
|
8324
|
+
if (goal === history.scrollTop) {
|
|
8325
|
+
stop();
|
|
8326
|
+
return;
|
|
8327
|
+
}
|
|
8328
|
+
history.followBottom = false;
|
|
8329
|
+
ensureTicker();
|
|
8330
|
+
},
|
|
8331
|
+
/** 外部直接改了 scrollTop(键盘滚动、选区拖拽自动滚动)前调用,避免两套写入打架。 */
|
|
8332
|
+
cancel() {
|
|
8333
|
+
stop();
|
|
8334
|
+
},
|
|
8335
|
+
isAnimating() {
|
|
8336
|
+
return goal !== null;
|
|
8337
|
+
}
|
|
8218
8338
|
};
|
|
8219
8339
|
}
|
|
8220
8340
|
|
|
@@ -10053,6 +10173,15 @@ async function runTuiWorkspace(options) {
|
|
|
10053
10173
|
onRecordsPolled: (records) => runCompletionWatcher.observe(records)
|
|
10054
10174
|
});
|
|
10055
10175
|
const history = createTurnHistory();
|
|
10176
|
+
let handleTranscriptScroll = (action) => {
|
|
10177
|
+
applyScrollAction(
|
|
10178
|
+
history,
|
|
10179
|
+
action,
|
|
10180
|
+
output,
|
|
10181
|
+
fixedInput.getInputLines(),
|
|
10182
|
+
dialogHost.getReservedRows()
|
|
10183
|
+
);
|
|
10184
|
+
};
|
|
10056
10185
|
const dialogHost = createDialogHost({
|
|
10057
10186
|
composer: {
|
|
10058
10187
|
pause: () => fixedInput.pause(),
|
|
@@ -10073,13 +10202,7 @@ async function runTuiWorkspace(options) {
|
|
|
10073
10202
|
fixedInput.repaint(buffer, cursorPos);
|
|
10074
10203
|
},
|
|
10075
10204
|
onTranscriptScroll: (action) => {
|
|
10076
|
-
|
|
10077
|
-
history,
|
|
10078
|
-
action,
|
|
10079
|
-
output,
|
|
10080
|
-
fixedInput.getInputLines(),
|
|
10081
|
-
dialogHost.getReservedRows()
|
|
10082
|
-
);
|
|
10205
|
+
handleTranscriptScroll(action);
|
|
10083
10206
|
},
|
|
10084
10207
|
output,
|
|
10085
10208
|
// The decoder-drain hook is only bound once the interactive raw-mode
|
|
@@ -10096,7 +10219,8 @@ async function runTuiWorkspace(options) {
|
|
|
10096
10219
|
renderHistoryToOutput,
|
|
10097
10220
|
renderHistoryUnderDialog,
|
|
10098
10221
|
scheduleRender,
|
|
10099
|
-
flushPendingRender
|
|
10222
|
+
flushPendingRender,
|
|
10223
|
+
paintImmediate
|
|
10100
10224
|
} = createTuiRender({
|
|
10101
10225
|
output,
|
|
10102
10226
|
history,
|
|
@@ -10112,6 +10236,28 @@ async function runTuiWorkspace(options) {
|
|
|
10112
10236
|
},
|
|
10113
10237
|
getReservedRows: () => dialogHost.getReservedRows()
|
|
10114
10238
|
});
|
|
10239
|
+
const scrollAnimator = createScrollAnimator({
|
|
10240
|
+
history,
|
|
10241
|
+
output,
|
|
10242
|
+
getInputLines: () => fixedInput.getInputLines(),
|
|
10243
|
+
getReservedRows: () => dialogHost.getReservedRows(),
|
|
10244
|
+
onPaint: () => paintImmediate(),
|
|
10245
|
+
env: effectiveEnv
|
|
10246
|
+
});
|
|
10247
|
+
handleTranscriptScroll = (action) => {
|
|
10248
|
+
if (action === "wheel-up" || action === "wheel-down") {
|
|
10249
|
+
scrollAnimator.wheel(action === "wheel-up" ? "up" : "down");
|
|
10250
|
+
return;
|
|
10251
|
+
}
|
|
10252
|
+
scrollAnimator.cancel();
|
|
10253
|
+
applyScrollAction(
|
|
10254
|
+
history,
|
|
10255
|
+
action,
|
|
10256
|
+
output,
|
|
10257
|
+
fixedInput.getInputLines(),
|
|
10258
|
+
dialogHost.getReservedRows()
|
|
10259
|
+
);
|
|
10260
|
+
};
|
|
10115
10261
|
const refreshGitStatus = () => {
|
|
10116
10262
|
if ((options.env?.NOLO_CLI_GIT_STATUS ?? process.env.NOLO_CLI_GIT_STATUS) === "0") return;
|
|
10117
10263
|
void detectGitStatusAsync(state.cwd).then((gitStatus) => {
|
|
@@ -10433,6 +10579,7 @@ async function runTuiWorkspace(options) {
|
|
|
10433
10579
|
if (done) return;
|
|
10434
10580
|
done = true;
|
|
10435
10581
|
sessionEnded = true;
|
|
10582
|
+
scrollAnimator.cancel();
|
|
10436
10583
|
if (resizeTimer !== null) {
|
|
10437
10584
|
clearTimeout(resizeTimer);
|
|
10438
10585
|
resizeTimer = null;
|
|
@@ -10561,6 +10708,7 @@ ${err.message}` : ""}`
|
|
|
10561
10708
|
const startAutoScroll = (direction, mouseX) => {
|
|
10562
10709
|
autoScrollDirection = direction;
|
|
10563
10710
|
lastDragMouseX = mouseX;
|
|
10711
|
+
scrollAnimator.cancel();
|
|
10564
10712
|
if (autoScrollTimer) return;
|
|
10565
10713
|
autoScrollTimer = setInterval(() => {
|
|
10566
10714
|
if (!selectionState.dragging || !selectionState.anchor || fixedInput.isPaused()) {
|
|
@@ -10771,9 +10919,9 @@ ${merged}` : merged;
|
|
|
10771
10919
|
if (mouseEvent) {
|
|
10772
10920
|
if (fixedInput.isPaused()) return;
|
|
10773
10921
|
if (mouseEvent.kind === "wheel") {
|
|
10774
|
-
|
|
10775
|
-
|
|
10776
|
-
|
|
10922
|
+
scrollAnimator.wheel(
|
|
10923
|
+
mouseEvent.wheelDirection === "down" ? "down" : "up"
|
|
10924
|
+
);
|
|
10777
10925
|
return;
|
|
10778
10926
|
}
|
|
10779
10927
|
const tty = output;
|
|
@@ -10793,6 +10941,7 @@ ${merged}` : merged;
|
|
|
10793
10941
|
const screenCol = mouseEvent.x - 1;
|
|
10794
10942
|
if (mouseEvent.kind === "press" && mouseEvent.button === "left") {
|
|
10795
10943
|
stopAutoScroll();
|
|
10944
|
+
scrollAnimator.cancel();
|
|
10796
10945
|
if (screenRow < visibleHeight && screenCol < contentWidth) {
|
|
10797
10946
|
const hit = hitTestHistory(
|
|
10798
10947
|
history,
|
|
@@ -10872,6 +11021,7 @@ ${merged}` : merged;
|
|
|
10872
11021
|
const scrollAction = parseScrollAction(sequence);
|
|
10873
11022
|
if (scrollAction) {
|
|
10874
11023
|
if (fixedInput.isPaused()) return;
|
|
11024
|
+
scrollAnimator.cancel();
|
|
10875
11025
|
applyScrollAction(history, scrollAction, output, fixedInput.getInputLines());
|
|
10876
11026
|
paintFrame(buffer);
|
|
10877
11027
|
return;
|