glmcode 0.1.16 → 0.1.17
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 +7 -0
- package/package.json +1 -1
- package/src/cli.js +25 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 0.1.17 (2026-08-29)
|
|
4
|
+
|
|
5
|
+
- **根治"任务栏/输入框复制多份"**:引入终端滚动保护区(DECSTBM),对话内容只在顶部滚动区内滚动,输入行/下拉菜单/状态栏全部画在滚动区外 → 任何输出都永远无法把固定 UI 顶进历史产生副本
|
|
6
|
+
- **任务栏永远固定在可视区最底行**:电脑终端(无键盘)恒定在最底行;手机键盘弹出时终端变矮,状态栏自动上浮到键盘正上方(键盘收起后清屏重绘,无残留)
|
|
7
|
+
- 用 xterm.js 无头渲染器三重验证:多轮对话后状态栏唯一且恒定、键盘弹出上浮、键盘收起零残留;35 项 `--selftest` 全过
|
|
8
|
+
- 版本升级至 0.1.17
|
|
9
|
+
|
|
3
10
|
## 0.1.16 (2026-08-29)
|
|
4
11
|
|
|
5
12
|
- **自适应布局**:监听终端尺寸变化(手机横竖屏切换 / 窗口拉伸),输入态下立即重绘,不再残留错乱;各元素仍按实时列宽/行高计算
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "glmcode",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17",
|
|
4
4
|
"description": "GLM coding agent CLI with shell tools, skills (SKILL.md/zip auto-extract), streaming chat, slash-command menu and bottom status bar. Auto-installs its dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/cli.js
CHANGED
|
@@ -227,7 +227,7 @@ async function handleCommand(line){
|
|
|
227
227
|
const [cmd,...rest]=line.trim().split(/\s+/), arg=rest.join(" ");
|
|
228
228
|
if(cmd.slice(1) && skills.has(cmd.slice(1))){const s=skills.get(cmd.slice(1));messages.push({role:"system",content:s.content});console.log(color(`已加载技能:${s.name}`,C.green));console.log(color("技能指令已注入,现在输入你的任务:",C.gray));return;}
|
|
229
229
|
if(cmd==="/help"){showMenu("/");}
|
|
230
|
-
else if(cmd==="/clear"){messages=[];console.log(color("已清空对话",C.green));}
|
|
230
|
+
else if(cmd==="/clear"){messages=[];TUI.input="";TUI.cursor=0;term.clear();term.moveTo(1,1);render();term.moveTo(1,Math.max(1,th()-2));console.log(color("已清空对话",C.green));}
|
|
231
231
|
else if(cmd==="/thinking"){thinkingEnabled=!thinkingEnabled;config.thinking=thinkingEnabled;saveConfig();console.log(color(`思考模式: ${thinkingEnabled?"开启":"关闭"}`,C.yellow));}
|
|
232
232
|
else if(cmd==="/model"){if(arg){config.model=arg;saveConfig();console.log(color(`模型已切换: ${arg}`,C.green));}else console.log(`当前模型: ${config.model}`);}
|
|
233
233
|
else if(cmd==="/pwd")console.log(cwd);
|
|
@@ -360,12 +360,16 @@ function render(){
|
|
|
360
360
|
const count=TUI.items.length?Math.min(TUI.items.length,MAX):0;
|
|
361
361
|
// 输入行:菜单展开时上移,把下方空间让给菜单(菜单紧贴输入框正下方);缩回时回到 h-1
|
|
362
362
|
const inputRow=h-1-count;
|
|
363
|
-
//
|
|
363
|
+
// ★ 滚动保护区:对话内容区 = 1..inputRow-1。所有对话/命令输出只在此区域内滚动,
|
|
364
|
+
// 输入行、下拉菜单、状态栏全部画在滚动区之外 → 输出永远无法把固定UI顶进历史/复制出多份
|
|
365
|
+
term.scrollingRegion(1, Math.max(2, inputRow-1));
|
|
366
|
+
// 底部状态栏(固定在"可视区域"最后一行而非终端无限底;手机键盘弹出时 term.height 变小,
|
|
367
|
+
// 触发 resize 重绘后整条状态栏上浮到键盘正上方)
|
|
364
368
|
const st=truncW(statusText(),w);const stPad=" ".repeat(Math.max(0,w-strW(st)));
|
|
365
369
|
term.moveTo(1,h);term.eraseLine();
|
|
366
370
|
term.inverse(st+stPad);term.styleReset;
|
|
367
|
-
//
|
|
368
|
-
for(let j=
|
|
371
|
+
// 只擦输入行+菜单区(inputRow..h-1),绝不触碰内容区,避免误删对话末尾
|
|
372
|
+
for(let j=h-1;j>=inputRow;j--){term.moveTo(1,j);term.eraseLine();}
|
|
369
373
|
// 下拉菜单:输入行正下方从上到下展开,最多 MAX 行,灰色反色条(与状态栏一致)
|
|
370
374
|
if(count){
|
|
371
375
|
for(let j=0;j<count;j++){
|
|
@@ -439,7 +443,9 @@ function tuiInput(){
|
|
|
439
443
|
term.grabInput(false);
|
|
440
444
|
term.hideCursor();
|
|
441
445
|
tuiActive=false;
|
|
442
|
-
|
|
446
|
+
// 收起菜单并恢复全宽滚动区,光标移回滚动区底行再换行:后续输出只在对话区滚动
|
|
447
|
+
TUI.items=[];render();
|
|
448
|
+
term.moveTo(1,Math.max(1,th()-2));term("\n");
|
|
443
449
|
if(v.trim()){TUI.history.push(v.trim());SESSION.commands.push(v.trim());}
|
|
444
450
|
resolve(v);
|
|
445
451
|
}
|
|
@@ -455,7 +461,7 @@ function tuiInput(){
|
|
|
455
461
|
else if(name==="RIGHT"){if(TUI.cursor<TUI.input.length){TUI.cursor++;}render();}
|
|
456
462
|
else if(name==="HOME"){TUI.cursor=0;render();}
|
|
457
463
|
else if(name==="END"){TUI.cursor=TUI.input.length;render();}
|
|
458
|
-
else if(name==="CTRL_C"){term.removeListener("key",onKey);term.grabInput(false);term.hideCursor(true);tuiActive=false;term("\n");resolve(null);}
|
|
464
|
+
else if(name==="CTRL_C"){term.removeListener("key",onKey);term.grabInput(false);term.hideCursor(true);tuiActive=false;term.moveTo(1,Math.max(1,th()-2));term("\n");resolve(null);}
|
|
459
465
|
else if(name==="CTRL_L"){term.clear();term.moveTo(1,1);render();}
|
|
460
466
|
};
|
|
461
467
|
// 防监听器累积:注册前先移除上一轮的 key 监听,杜绝按键被重复处理
|
|
@@ -492,8 +498,19 @@ function showSummaryAndExit(){
|
|
|
492
498
|
async function runTui(){
|
|
493
499
|
term.hideCursor();
|
|
494
500
|
term.clear();logo();
|
|
495
|
-
//
|
|
496
|
-
|
|
501
|
+
// 启动即建立滚动保护区+底部固定UI(输入行/状态栏),后续任何输出都不会顶走它们
|
|
502
|
+
render();
|
|
503
|
+
// 自适应:终端尺寸变化(手机横竖屏/键盘弹出/窗口拉伸)时立即重绘。
|
|
504
|
+
// 键盘弹出 → term.height 变小 → 状态栏/输入行自动上浮到键盘正上方
|
|
505
|
+
let lastH=th();
|
|
506
|
+
const onResize=()=>{
|
|
507
|
+
if(!tuiActive)return;
|
|
508
|
+
const h=th();
|
|
509
|
+
// 高度增大(键盘收起/横屏):先清屏再重绘,抹掉放大后露出的旧输入行/状态栏残留
|
|
510
|
+
if(h>lastH){term.clear();term.moveTo(1,1);}
|
|
511
|
+
render();
|
|
512
|
+
lastH=h;
|
|
513
|
+
};
|
|
497
514
|
term.on("resize",onResize);
|
|
498
515
|
while(true){
|
|
499
516
|
const input=await tuiInput();
|