glmcode 0.1.14 → 0.1.15
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 +9 -0
- package/package.json +1 -1
- package/src/cli.js +48 -14
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 0.1.15 (2026-08-29)
|
|
4
|
+
|
|
5
|
+
- **命令菜单移至输入框下方**:菜单紧贴输入行正下方往下展开,状态栏仍固定在屏幕最底;缩回菜单时彻底擦除,不再残留"被缩回去的一行"
|
|
6
|
+
- **菜单更短**:`MENU_MAX` 由 5 降至 4,布局更紧凑
|
|
7
|
+
- **回车直接执行选中项**:↑↓ 选中命令/技能后按回车立即执行,无需补全;删掉斜杠即取消菜单回到普通输入
|
|
8
|
+
- **技能介绍**:新增 `/skill <名字>` 查看技能介绍(描述/调用/来源/SKILL.md 正文),`/skill` 列出全部技能
|
|
9
|
+
- 55 个技能已用智谱 API 全量连通性测试(46 个明文可解析 + 9 个加密存储,链路全部 200 通)
|
|
10
|
+
- 版本升级至 0.1.15
|
|
11
|
+
|
|
3
12
|
## 0.1.14 (2026-08-29)
|
|
4
13
|
|
|
5
14
|
- **手机端渲染进一步修复**:LOGO 启动画面、底部状态栏、灰色下拉菜单在窄屏/手机 Termux 下的显示优化
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "glmcode",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
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
|
@@ -7,7 +7,7 @@ import {fileURLToPath} from "node:url";
|
|
|
7
7
|
import termkit from "terminal-kit";
|
|
8
8
|
const term = termkit.terminal;
|
|
9
9
|
|
|
10
|
-
const VERSION="0.1.
|
|
10
|
+
const VERSION="0.1.15";
|
|
11
11
|
const BASE_URL="https://open.bigmodel.cn/api/paas/v4";
|
|
12
12
|
const API_KEYS_URL="https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys";
|
|
13
13
|
const HOME=os.homedir();
|
|
@@ -248,7 +248,33 @@ async function handleCommand(line){
|
|
|
248
248
|
else if(cmd==="/save"){if(!arg)console.log("用法: /save <会话名>");else{const f=path.join(DATA_DIR,`session_${arg}.json`);saveJson(f,{savedAt:new Date().toISOString(),messages,cwd});console.log(color(`会话已保存: ${f}`,C.green));}}
|
|
249
249
|
else if(cmd==="/load"){if(!arg)console.log("用法: /load <会话名>");else{const f=path.join(DATA_DIR,`session_${arg}.json`);const d=readJson(f,null);if(!d||!d.messages)console.log(color(`会话不存在: ${arg}`,C.red));else{messages=d.messages;if(d.cwd)try{process.chdir(d.cwd);cwd=d.cwd;}catch{}console.log(color(`已载入会话 ${arg}(${d.messages.length} 条)`,C.green));}}}
|
|
250
250
|
else if(cmd==="/skills"){console.log(color("技能目录:",C.cyanB));for(const d of skillDirs())console.log(" "+d);console.log(color("已加载技能:",C.cyanB));if(!skills.size)console.log(color(" (无) 创建: /skill new <名字> 后把 SKILL.md 放进目录即可",C.gray));else for(const s of skills.values())console.log(` /${s.name} ${s.description} ${color(s.dir,C.gray)}`);}
|
|
251
|
-
else if(cmd==="/skill"){
|
|
251
|
+
else if(cmd==="/skill"){
|
|
252
|
+
const parts=arg.trim().split(/\s+/).filter(Boolean);
|
|
253
|
+
if(!parts.length){
|
|
254
|
+
console.log(color("技能列表(/技能名 调用 · /skill <名字> 查看介绍):",C.cyanB));
|
|
255
|
+
if(!skills.size)console.log(color(" (无) 创建: /skill new <名字> 后把 SKILL.md 放进去",C.gray));
|
|
256
|
+
else for(const s of skills.values())console.log(` /${s.name} ${s.description} ${color(s.dir,C.gray)}`);
|
|
257
|
+
}
|
|
258
|
+
else if(parts[0]==="new"&&parts[1]){
|
|
259
|
+
const d=path.join(SKILLS_DIR,parts[1]);fs.mkdirSync(d,{recursive:true});
|
|
260
|
+
fs.writeFileSync(path.join(d,"SKILL.md"),`---\nname: ${parts[1]}\ndescription: ${parts[1]} 技能\n---\n# ${parts[1]}\n在这里写技能说明。\n`);
|
|
261
|
+
console.log(color(`技能目录已创建: ${d}`,C.green));
|
|
262
|
+
console.log(color("把文件放进该目录,/reload 生效。",C.gray));
|
|
263
|
+
}
|
|
264
|
+
else if(parts[0]==="new"){console.log("用法: /skill new <名字>");}
|
|
265
|
+
else{
|
|
266
|
+
const s=skills.get(parts[0]);
|
|
267
|
+
if(!s)console.log(color(`技能不存在: ${parts[0]}(/skill 或 /skills 查看全部)`,C.red));
|
|
268
|
+
else{
|
|
269
|
+
console.log(color(`—— 技能: ${s.name} ——`,C.cyanB));
|
|
270
|
+
console.log(`${color("描述:",C.yellow)} ${s.description}`);
|
|
271
|
+
console.log(`${color("调用:",C.yellow)} /${s.name}(输入该命令即加载技能)`);
|
|
272
|
+
console.log(`${color("来源:",C.gray)} ${s.dir}`);
|
|
273
|
+
console.log(color("—— SKILL.md 内容 ——",C.cyanB));
|
|
274
|
+
console.log(s.content.slice(0,2000));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
252
278
|
else if(cmd==="/reload"){await loadSkills();console.log(color(`已重载 ${skills.size} 个技能`,C.green));}
|
|
253
279
|
else if(cmd==="/auth"){const a=loadAuth();console.log(a?`API Key: ${mask(a.key)}\n来源: ${a.source}`:"未检测到 API Key");}
|
|
254
280
|
else if(cmd==="/run"){if(!arg)console.log("用法: /run <命令>");else console.log(execTool("run_shell",{command:arg}));}
|
|
@@ -277,7 +303,8 @@ const PROMPT=()=>`${shortDir()} > `;
|
|
|
277
303
|
const TUI={input:"",cursor:0,items:[],sel:0,scroll:0,history:[],histIdx:-1};
|
|
278
304
|
function tw(){return (term.width>0&&isFinite(term.width))?term.width:40;}
|
|
279
305
|
function th(){return (term.height>0&&isFinite(term.height))?term.height:24;}
|
|
280
|
-
const MENU_MAX=
|
|
306
|
+
const MENU_MAX=4;
|
|
307
|
+
/* 菜单最多 4 行(更短,输入框下方紧凑展示)*/
|
|
281
308
|
/* ---- 显示列宽工具:CJK 等宽字符占 2 列,按列截断,杜绝超宽触发终端换行滚动 ---- */
|
|
282
309
|
function strW(s){let w=0;for(const ch of String(s)){w+=ch.codePointAt(0)>0x2e7f?2:1;}return w;}
|
|
283
310
|
function truncW(s,maxW){let w=0,o="";for(const ch of String(s)){const c=ch.codePointAt(0),cw=c>0x2e7f?2:1;if(w+cw>maxW)break;w+=cw;o+=ch;}return o;}
|
|
@@ -312,19 +339,21 @@ function keepSelVisible(){
|
|
|
312
339
|
}
|
|
313
340
|
function render(){
|
|
314
341
|
const h=th(), w=tw();
|
|
315
|
-
const
|
|
342
|
+
const MAX=MENU_MAX;
|
|
343
|
+
const count=TUI.items.length?Math.min(TUI.items.length,MAX):0;
|
|
344
|
+
// 输入行:菜单展开时上移,把下方空间让给菜单(菜单紧贴输入框正下方);缩回时回到 h-1
|
|
345
|
+
const inputRow=h-1-count;
|
|
316
346
|
// 底部状态栏(固定在最后一行;反色灰条铺满整行,按列宽截断防超宽换行)
|
|
317
347
|
const st=truncW(statusText(),w);const stPad=" ".repeat(Math.max(0,w-strW(st)));
|
|
318
348
|
term.moveTo(1,h);term.eraseLine();
|
|
319
349
|
term.inverse(st+stPad);term.styleReset;
|
|
320
|
-
//
|
|
321
|
-
for(let j=0;j
|
|
322
|
-
//
|
|
323
|
-
if(
|
|
324
|
-
const count=Math.min(TUI.items.length,MAX);
|
|
350
|
+
// 先彻底擦除底部区域(状态栏上方 MAX+1 行),保证每次重绘不残留任何旧菜单行
|
|
351
|
+
for(let j=0;j<=MAX;j++){term.moveTo(1,h-1-j);term.eraseLine();}
|
|
352
|
+
// 下拉菜单:输入行正下方从上到下展开,最多 MAX 行,灰色反色条(与状态栏一致)
|
|
353
|
+
if(count){
|
|
325
354
|
for(let j=0;j<count;j++){
|
|
326
355
|
const idx=TUI.scroll+j;
|
|
327
|
-
term.moveTo(1,inputRow
|
|
356
|
+
term.moveTo(1,inputRow+1+j);
|
|
328
357
|
const mark=(j===0&&TUI.scroll>0)?"⋯":(j===count-1&&TUI.scroll+count<TUI.items.length)?"⋮":" ";
|
|
329
358
|
const text=truncW(" "+mark+" "+TUI.items[idx],w);
|
|
330
359
|
const pad=" ".repeat(Math.max(0,w-strW(text)));
|
|
@@ -333,7 +362,7 @@ function render(){
|
|
|
333
362
|
term.styleReset;
|
|
334
363
|
}
|
|
335
364
|
}
|
|
336
|
-
//
|
|
365
|
+
// 输入行(紧贴菜单上方 / 无菜单时固定在倒数第二行,与状态栏互不重叠)
|
|
337
366
|
term.moveTo(1,inputRow);term.eraseLine();
|
|
338
367
|
const before=TUI.input.slice(0,TUI.cursor), after=TUI.input.slice(TUI.cursor);
|
|
339
368
|
let prompt=PROMPT();let pw=strW(prompt);
|
|
@@ -382,7 +411,12 @@ function tuiInput(){
|
|
|
382
411
|
render();
|
|
383
412
|
}
|
|
384
413
|
else if(name==="ENTER"){
|
|
385
|
-
|
|
414
|
+
// 菜单可见时:回车直接执行上下键选中的那个命令/技能;无菜单时才发送当前输入
|
|
415
|
+
let v=TUI.input;
|
|
416
|
+
if(TUI.items.length){
|
|
417
|
+
const chosen=TUI.items[TUI.sel];
|
|
418
|
+
v=chosen.split(" ")[0];
|
|
419
|
+
}
|
|
386
420
|
term.removeListener("key",onKey);
|
|
387
421
|
term.grabInput(false);
|
|
388
422
|
term.hideCursor();
|
|
@@ -514,10 +548,10 @@ if(argv[0]==="--selftest"||argv[0]==="selftest"){await selftest();}
|
|
|
514
548
|
if(argv[0]==="--version"||argv[0]==="-v"){console.log(VERSION);process.exit(0);}
|
|
515
549
|
if(argv[0]==="--help"||argv[0]==="-h"){console.log(`GLMCode ${VERSION}
|
|
516
550
|
启动: glmcode
|
|
517
|
-
TUI: 输入 /
|
|
551
|
+
TUI: 输入 / 或字母实时弹出悬浮命令菜单(位于输入框正下方),↑↓ 键选择,回车直接执行选中的命令/技能,Tab 补全,删掉斜杠即可取消
|
|
518
552
|
底部常驻状态栏:思考开关 / 模型 / 技能数 / 当前目录
|
|
519
553
|
斜杠命令: /help /clear /thinking /model /temp /pwd /cd /ls /cat /find /grep /touch /mkdir /rm /cp /mv /run /install /shell /whoami /date /history /config /compact /save /load /skills /skill /reload /auth /exit
|
|
520
|
-
技能: 将 SKILL.md 目录或 .zip 包放入 ~/.glmcode/skills/(或包内 skills/)自动加载;/skill new <名字>
|
|
554
|
+
技能: 将 SKILL.md 目录或 .zip 包放入 ~/.glmcode/skills/(或包内 skills/)自动加载;/skill new <名字> 创建,/skill <名字> 查看介绍,/技能名 直接调用。
|
|
521
555
|
认证: glmcode auth set <API_KEY> | auth status | auth logout | auth login
|
|
522
556
|
Shell: glmcode shell | shell list`);process.exit(0);}
|
|
523
557
|
if(argv[0]==="auth"){const s=argv[1]||"status";if(s==="set"){if(!argv[2]){console.error("用法: glmcode auth set <API_KEY>");process.exit(2);}saveJson(AUTH_FILE,{provider:"zhipu",apiKey:argv[2],updatedAt:new Date().toISOString()});console.log(`API Key 已保存到 ${AUTH_FILE}`);}
|