glmcode 0.1.14 → 0.1.16
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/package.json +1 -1
- package/src/cli.js +105 -23
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 0.1.16 (2026-08-29)
|
|
4
|
+
|
|
5
|
+
- **自适应布局**:监听终端尺寸变化(手机横竖屏切换 / 窗口拉伸),输入态下立即重绘,不再残留错乱;各元素仍按实时列宽/行高计算
|
|
6
|
+
- **启动画面不再浪费空白**:欢迎块(LOGO+提示+启动信息面板)整体**垂直居中**,中间区域填充常用命令、技能目录提示、兼容性说明,屏幕矮时自动紧凑回顶部
|
|
7
|
+
- **Ctrl+C / /exit 退出即"打开新页面"**:真正清屏,清掉本会话所有历史输出,只显示一张**会话总结页**:运行时长、执行过的指令列表、生成的文件列表、Token 消耗
|
|
8
|
+
- **会话统计**:自动记录本次会话运行过哪些指令、通过 write_file 生成了哪些文件、API 累计消耗的 token 数(从流式响应 usage 读取)
|
|
9
|
+
- 35 项 `--selftest` 全过;36x40 高屏 PTY+pyte 仿真验证:居中布局、菜单缩回无残留、退出清屏总结页全部正常
|
|
10
|
+
- 版本升级至 0.1.16
|
|
11
|
+
|
|
12
|
+
## 0.1.15 (2026-08-29)
|
|
13
|
+
|
|
14
|
+
- **命令菜单移至输入框下方**:菜单紧贴输入行正下方往下展开,状态栏仍固定在屏幕最底;缩回菜单时彻底擦除,不再残留"被缩回去的一行"
|
|
15
|
+
- **菜单更短**:`MENU_MAX` 由 5 降至 4,布局更紧凑
|
|
16
|
+
- **回车直接执行选中项**:↑↓ 选中命令/技能后按回车立即执行,无需补全;删掉斜杠即取消菜单回到普通输入
|
|
17
|
+
- **技能介绍**:新增 `/skill <名字>` 查看技能介绍(描述/调用/来源/SKILL.md 正文),`/skill` 列出全部技能
|
|
18
|
+
- 55 个技能已用智谱 API 全量连通性测试(46 个明文可解析 + 9 个加密存储,链路全部 200 通)
|
|
19
|
+
- 版本升级至 0.1.15
|
|
20
|
+
|
|
3
21
|
## 0.1.14 (2026-08-29)
|
|
4
22
|
|
|
5
23
|
- **手机端渲染进一步修复**:LOGO 启动画面、底部状态栏、灰色下拉菜单在窄屏/手机 Termux 下的显示优化
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "glmcode",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
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.16";
|
|
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();
|
|
@@ -31,6 +31,8 @@ const DEFAULT_CONFIG={
|
|
|
31
31
|
|
|
32
32
|
let config={...DEFAULT_CONFIG};
|
|
33
33
|
let apiKey=null, messages=[], cwd=process.cwd(), thinkingEnabled=false, skills=new Map();
|
|
34
|
+
/* 会话统计:退出时生成耗费总结(运行了哪些指令/生成了哪些文件/耗了多少token) */
|
|
35
|
+
const SESSION={start:Date.now(),commands:[],files:[],tokens:0,calls:0};
|
|
34
36
|
|
|
35
37
|
function readJson(file,fallback={}){try{return JSON.parse(fs.readFileSync(file,"utf8"));}catch{return fallback;}}
|
|
36
38
|
function saveJson(file,v){fs.mkdirSync(DATA_DIR,{recursive:true,mode:0o700});fs.writeFileSync(file,JSON.stringify(v,null,2)+"\n",{mode:0o600});try{fs.chmodSync(file,0o600);}catch{}}
|
|
@@ -158,7 +160,7 @@ function execTool(name,args){
|
|
|
158
160
|
try{const r=spawnSync(file,shArgs,{cwd,encoding:"utf8",timeout:60000});return ((r.stdout||"")+(r.stderr?"\n[stderr]\n"+r.stderr:"")).slice(0,8000)||"(无输出)";}
|
|
159
161
|
catch(e){return `执行失败: ${e.message}`;}
|
|
160
162
|
}
|
|
161
|
-
if(name==="write_file"){try{const p=path.resolve(cwd,args.path);fs.mkdirSync(path.dirname(p),{recursive:true});fs.writeFileSync(p,args.content);return `已写入 ${p}`;}catch(e){return `写入失败: ${e.message}`;}}
|
|
163
|
+
if(name==="write_file"){try{const p=path.resolve(cwd,args.path);fs.mkdirSync(path.dirname(p),{recursive:true});fs.writeFileSync(p,args.content);SESSION.files.push(p);return `已写入 ${p}`;}catch(e){return `写入失败: ${e.message}`;}}
|
|
162
164
|
if(name==="read_file"){try{return fs.readFileSync(path.resolve(cwd,args.path),"utf8").slice(0,8000);}catch(e){return `读取失败: ${e.message}`;}}
|
|
163
165
|
if(name==="use_skill"){const s=skills.get(args.skill_name);return s?`技能已加载:${s.name}\n\n${s.content}`:`技能不存在:${args.skill_name}`;}
|
|
164
166
|
return "未知工具";
|
|
@@ -183,7 +185,8 @@ async function streamChat(){
|
|
|
183
185
|
line=line.trim();if(!line.startsWith("data:"))continue;
|
|
184
186
|
const data=line.slice(5).trim();if(data==="[DONE]")continue;
|
|
185
187
|
try{
|
|
186
|
-
const
|
|
188
|
+
const dj=JSON.parse(data);const d=dj.choices?.[0]?.delta||{};
|
|
189
|
+
if(dj.usage&&dj.usage.total_tokens){SESSION.tokens+=dj.usage.total_tokens;SESSION.calls++;}
|
|
187
190
|
if(d.reasoning_content&&thinkingEnabled)process.stdout.write(color(d.reasoning_content,C.gray));
|
|
188
191
|
if(d.content){content+=d.content;process.stdout.write(d.content);}
|
|
189
192
|
for(const tc of d.tool_calls||[]){
|
|
@@ -248,17 +251,43 @@ async function handleCommand(line){
|
|
|
248
251
|
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
252
|
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
253
|
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"){
|
|
254
|
+
else if(cmd==="/skill"){
|
|
255
|
+
const parts=arg.trim().split(/\s+/).filter(Boolean);
|
|
256
|
+
if(!parts.length){
|
|
257
|
+
console.log(color("技能列表(/技能名 调用 · /skill <名字> 查看介绍):",C.cyanB));
|
|
258
|
+
if(!skills.size)console.log(color(" (无) 创建: /skill new <名字> 后把 SKILL.md 放进去",C.gray));
|
|
259
|
+
else for(const s of skills.values())console.log(` /${s.name} ${s.description} ${color(s.dir,C.gray)}`);
|
|
260
|
+
}
|
|
261
|
+
else if(parts[0]==="new"&&parts[1]){
|
|
262
|
+
const d=path.join(SKILLS_DIR,parts[1]);fs.mkdirSync(d,{recursive:true});
|
|
263
|
+
fs.writeFileSync(path.join(d,"SKILL.md"),`---\nname: ${parts[1]}\ndescription: ${parts[1]} 技能\n---\n# ${parts[1]}\n在这里写技能说明。\n`);
|
|
264
|
+
console.log(color(`技能目录已创建: ${d}`,C.green));
|
|
265
|
+
console.log(color("把文件放进该目录,/reload 生效。",C.gray));
|
|
266
|
+
}
|
|
267
|
+
else if(parts[0]==="new"){console.log("用法: /skill new <名字>");}
|
|
268
|
+
else{
|
|
269
|
+
const s=skills.get(parts[0]);
|
|
270
|
+
if(!s)console.log(color(`技能不存在: ${parts[0]}(/skill 或 /skills 查看全部)`,C.red));
|
|
271
|
+
else{
|
|
272
|
+
console.log(color(`—— 技能: ${s.name} ——`,C.cyanB));
|
|
273
|
+
console.log(`${color("描述:",C.yellow)} ${s.description}`);
|
|
274
|
+
console.log(`${color("调用:",C.yellow)} /${s.name}(输入该命令即加载技能)`);
|
|
275
|
+
console.log(`${color("来源:",C.gray)} ${s.dir}`);
|
|
276
|
+
console.log(color("—— SKILL.md 内容 ——",C.cyanB));
|
|
277
|
+
console.log(s.content.slice(0,2000));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
252
281
|
else if(cmd==="/reload"){await loadSkills();console.log(color(`已重载 ${skills.size} 个技能`,C.green));}
|
|
253
282
|
else if(cmd==="/auth"){const a=loadAuth();console.log(a?`API Key: ${mask(a.key)}\n来源: ${a.source}`:"未检测到 API Key");}
|
|
254
283
|
else if(cmd==="/run"){if(!arg)console.log("用法: /run <命令>");else console.log(execTool("run_shell",{command:arg}));}
|
|
255
284
|
else if(cmd==="/install"){installProgram(arg)}
|
|
256
|
-
else if(cmd==="/exit"){
|
|
285
|
+
else if(cmd==="/exit"){showSummaryAndExit();process.exit(0);}
|
|
257
286
|
else console.log(color(`未知命令: ${cmd}(输入 / 查看全部)`,C.red));
|
|
258
287
|
}
|
|
259
288
|
|
|
260
289
|
function logo(){
|
|
261
|
-
const w=tw();
|
|
290
|
+
const w=tw(),h=th();
|
|
262
291
|
const art=[
|
|
263
292
|
" ██████╗ ██╗ ███╗ ███╗",
|
|
264
293
|
"██╔════╝ ██║ ████╗ ████║",
|
|
@@ -267,17 +296,32 @@ function logo(){
|
|
|
267
296
|
"╚██████╔╝███████╗██║ ╚═╝ ██║",
|
|
268
297
|
" ╚═════╝ ╚══════╝╚═╝ ╚═╝"
|
|
269
298
|
];
|
|
299
|
+
// 启动信息面板:填充中间空白区域,矮屏自动紧凑
|
|
300
|
+
const tip=truncW(" Coding Agent · v"+VERSION+" · 输入 / 弹菜单 · ↑↓ 选择 · 回车执行 · Ctrl+C 退出看总结",w);
|
|
301
|
+
const cmdKeys=[["/help","全部命令"],["/clear","清空对话"],["/skills","技能列表与目录"],["/skill","管理技能(可新建)"],["/model","切换模型"],["/exit","退出"]];
|
|
302
|
+
const panel=[""];
|
|
303
|
+
for(const [k,d] of cmdKeys)panel.push(" "+color(k,C.cyanB)+" "+color(truncW(d,Math.max(4,w-6)),C.gray));
|
|
304
|
+
panel.push("");
|
|
305
|
+
panel.push(truncW(" · 技能自动加载: ~/.glmcode/skills/ 或包内 skills/ · /skill new <名字> 新建",w));
|
|
306
|
+
panel.push(truncW(" · 全平台兼容 Linux/Android(termux)/Windows/macOS · 底部状态栏常驻",w));
|
|
307
|
+
const blockH=art.length+1+panel.length;
|
|
308
|
+
// 欢迎块整体垂直居中(底部恒留2行给输入行+状态栏);过矮/超屏则顶部紧凑
|
|
309
|
+
let start=Math.max(1,Math.floor((h-blockH-2)/2));
|
|
310
|
+
if(start+blockH>h-2)start=1;
|
|
311
|
+
term.moveTo(1,start);
|
|
270
312
|
for(const a of art)console.log(color(truncW(a,w),C.cyanB));
|
|
271
|
-
console.log(color(
|
|
272
|
-
console.log(
|
|
313
|
+
console.log(color(tip,C.gray));
|
|
314
|
+
for(const p of panel)console.log(truncW(p,w));
|
|
273
315
|
}
|
|
274
316
|
|
|
275
317
|
/* =============== 真 TUI:悬浮菜单 + 上下键 + 实时过滤 + 底部状态栏 =============== */
|
|
276
318
|
const PROMPT=()=>`${shortDir()} > `;
|
|
277
319
|
const TUI={input:"",cursor:0,items:[],sel:0,scroll:0,history:[],histIdx:-1};
|
|
320
|
+
let tuiActive=false; // 是否正处于输入等待态(resize 时仅此时才安全重绘)
|
|
278
321
|
function tw(){return (term.width>0&&isFinite(term.width))?term.width:40;}
|
|
279
322
|
function th(){return (term.height>0&&isFinite(term.height))?term.height:24;}
|
|
280
|
-
const MENU_MAX=
|
|
323
|
+
const MENU_MAX=4;
|
|
324
|
+
/* 菜单最多 4 行(更短,输入框下方紧凑展示)*/
|
|
281
325
|
/* ---- 显示列宽工具:CJK 等宽字符占 2 列,按列截断,杜绝超宽触发终端换行滚动 ---- */
|
|
282
326
|
function strW(s){let w=0;for(const ch of String(s)){w+=ch.codePointAt(0)>0x2e7f?2:1;}return w;}
|
|
283
327
|
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 +356,21 @@ function keepSelVisible(){
|
|
|
312
356
|
}
|
|
313
357
|
function render(){
|
|
314
358
|
const h=th(), w=tw();
|
|
315
|
-
const
|
|
359
|
+
const MAX=MENU_MAX;
|
|
360
|
+
const count=TUI.items.length?Math.min(TUI.items.length,MAX):0;
|
|
361
|
+
// 输入行:菜单展开时上移,把下方空间让给菜单(菜单紧贴输入框正下方);缩回时回到 h-1
|
|
362
|
+
const inputRow=h-1-count;
|
|
316
363
|
// 底部状态栏(固定在最后一行;反色灰条铺满整行,按列宽截断防超宽换行)
|
|
317
364
|
const st=truncW(statusText(),w);const stPad=" ".repeat(Math.max(0,w-strW(st)));
|
|
318
365
|
term.moveTo(1,h);term.eraseLine();
|
|
319
366
|
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);
|
|
367
|
+
// 先彻底擦除底部区域(状态栏上方 MAX+1 行),保证每次重绘不残留任何旧菜单行
|
|
368
|
+
for(let j=0;j<=MAX;j++){term.moveTo(1,h-1-j);term.eraseLine();}
|
|
369
|
+
// 下拉菜单:输入行正下方从上到下展开,最多 MAX 行,灰色反色条(与状态栏一致)
|
|
370
|
+
if(count){
|
|
325
371
|
for(let j=0;j<count;j++){
|
|
326
372
|
const idx=TUI.scroll+j;
|
|
327
|
-
term.moveTo(1,inputRow
|
|
373
|
+
term.moveTo(1,inputRow+1+j);
|
|
328
374
|
const mark=(j===0&&TUI.scroll>0)?"⋯":(j===count-1&&TUI.scroll+count<TUI.items.length)?"⋮":" ";
|
|
329
375
|
const text=truncW(" "+mark+" "+TUI.items[idx],w);
|
|
330
376
|
const pad=" ".repeat(Math.max(0,w-strW(text)));
|
|
@@ -333,7 +379,7 @@ function render(){
|
|
|
333
379
|
term.styleReset;
|
|
334
380
|
}
|
|
335
381
|
}
|
|
336
|
-
//
|
|
382
|
+
// 输入行(紧贴菜单上方 / 无菜单时固定在倒数第二行,与状态栏互不重叠)
|
|
337
383
|
term.moveTo(1,inputRow);term.eraseLine();
|
|
338
384
|
const before=TUI.input.slice(0,TUI.cursor), after=TUI.input.slice(TUI.cursor);
|
|
339
385
|
let prompt=PROMPT();let pw=strW(prompt);
|
|
@@ -359,6 +405,7 @@ function refreshFilter(){
|
|
|
359
405
|
function tuiInput(){
|
|
360
406
|
return new Promise(resolve=>{
|
|
361
407
|
TUI.input="";TUI.cursor=0;TUI.items=[];TUI.sel=0;TUI.scroll=0;TUI.histIdx=-1;
|
|
408
|
+
tuiActive=true;
|
|
362
409
|
term.grabInput(true);
|
|
363
410
|
render();
|
|
364
411
|
const onKey=(name,matches,data)=>{
|
|
@@ -382,12 +429,18 @@ function tuiInput(){
|
|
|
382
429
|
render();
|
|
383
430
|
}
|
|
384
431
|
else if(name==="ENTER"){
|
|
385
|
-
|
|
432
|
+
// 菜单可见时:回车直接执行上下键选中的那个命令/技能;无菜单时才发送当前输入
|
|
433
|
+
let v=TUI.input;
|
|
434
|
+
if(TUI.items.length){
|
|
435
|
+
const chosen=TUI.items[TUI.sel];
|
|
436
|
+
v=chosen.split(" ")[0];
|
|
437
|
+
}
|
|
386
438
|
term.removeListener("key",onKey);
|
|
387
439
|
term.grabInput(false);
|
|
388
440
|
term.hideCursor();
|
|
441
|
+
tuiActive=false;
|
|
389
442
|
term("\n");
|
|
390
|
-
if(v.trim())TUI.history.push(v.trim());
|
|
443
|
+
if(v.trim()){TUI.history.push(v.trim());SESSION.commands.push(v.trim());}
|
|
391
444
|
resolve(v);
|
|
392
445
|
}
|
|
393
446
|
else if(name==="BACKSPACE"){
|
|
@@ -402,7 +455,7 @@ function tuiInput(){
|
|
|
402
455
|
else if(name==="RIGHT"){if(TUI.cursor<TUI.input.length){TUI.cursor++;}render();}
|
|
403
456
|
else if(name==="HOME"){TUI.cursor=0;render();}
|
|
404
457
|
else if(name==="END"){TUI.cursor=TUI.input.length;render();}
|
|
405
|
-
else if(name==="CTRL_C"){term.removeListener("key",onKey);term.grabInput(false);term.hideCursor(true);term("\n");resolve(null);}
|
|
458
|
+
else if(name==="CTRL_C"){term.removeListener("key",onKey);term.grabInput(false);term.hideCursor(true);tuiActive=false;term("\n");resolve(null);}
|
|
406
459
|
else if(name==="CTRL_L"){term.clear();term.moveTo(1,1);render();}
|
|
407
460
|
};
|
|
408
461
|
// 防监听器累积:注册前先移除上一轮的 key 监听,杜绝按键被重复处理
|
|
@@ -411,9 +464,37 @@ function tuiInput(){
|
|
|
411
464
|
tuiInput._key=onKey;
|
|
412
465
|
});
|
|
413
466
|
}
|
|
467
|
+
function showSummaryAndExit(){
|
|
468
|
+
term.grabInput(false);term.hideCursor(true);
|
|
469
|
+
// 真正清屏:像打开新页面一样,清掉本会话所有输出
|
|
470
|
+
term.clear();term.moveTo(1,1);
|
|
471
|
+
const dur=Math.max(0,Math.round((Date.now()-SESSION.start)/1000));
|
|
472
|
+
const mm=String(Math.floor(dur/60)).padStart(2,"0"),ss=String(dur%60).padStart(2,"0");
|
|
473
|
+
const bar="=".repeat(Math.max(8,Math.min(tw()-2,42)));
|
|
474
|
+
console.log(color(bar,C.cyan));
|
|
475
|
+
console.log(color(" GLMCode 会话总结",C.cyanB));
|
|
476
|
+
console.log(color(bar,C.cyan));
|
|
477
|
+
console.log("");
|
|
478
|
+
console.log(` 时长: ${mm}:${ss} 模型: ${config.model}`);
|
|
479
|
+
console.log(` 执行指令: ${SESSION.commands.length} 条`);
|
|
480
|
+
if(SESSION.commands.length){
|
|
481
|
+
const shown=SESSION.commands.slice(-10);
|
|
482
|
+
for(const c of shown)console.log(color(" · "+truncW(c,Math.max(8,tw()-8)),C.gray));
|
|
483
|
+
if(SESSION.commands.length>shown.length)console.log(color(` … 还有 ${SESSION.commands.length-shown.length} 条`,C.gray));
|
|
484
|
+
}else console.log(color(" (无)",C.gray));
|
|
485
|
+
console.log(` 生成文件: ${SESSION.files.length} 个`);
|
|
486
|
+
if(SESSION.files.length){for(const f of SESSION.files.slice(-8))console.log(color(" · "+truncW(f,Math.max(8,tw()-8)),C.gray));}
|
|
487
|
+
else console.log(color(" (无)",C.gray));
|
|
488
|
+
console.log(` Token 消耗: ${SESSION.tokens}`);
|
|
489
|
+
console.log("");
|
|
490
|
+
console.log(color(" ✔ 已清屏,像打开新页面一样干净",C.green));
|
|
491
|
+
}
|
|
414
492
|
async function runTui(){
|
|
415
493
|
term.hideCursor();
|
|
416
494
|
term.clear();logo();
|
|
495
|
+
// 自适应:终端尺寸变化(手机横竖屏/窗口拉伸)时立即重绘,不残留不错乱
|
|
496
|
+
const onResize=()=>{if(tuiActive)render();};
|
|
497
|
+
term.on("resize",onResize);
|
|
417
498
|
while(true){
|
|
418
499
|
const input=await tuiInput();
|
|
419
500
|
if(input===null)break;
|
|
@@ -422,8 +503,9 @@ async function runTui(){
|
|
|
422
503
|
if(text.startsWith("/"))await handleCommand(text);
|
|
423
504
|
else await chat(text);
|
|
424
505
|
}
|
|
506
|
+
term.removeListener("resize",onResize);
|
|
425
507
|
term.grabInput(false);
|
|
426
|
-
|
|
508
|
+
showSummaryAndExit();
|
|
427
509
|
process.exit(0);
|
|
428
510
|
}
|
|
429
511
|
|
|
@@ -514,10 +596,10 @@ if(argv[0]==="--selftest"||argv[0]==="selftest"){await selftest();}
|
|
|
514
596
|
if(argv[0]==="--version"||argv[0]==="-v"){console.log(VERSION);process.exit(0);}
|
|
515
597
|
if(argv[0]==="--help"||argv[0]==="-h"){console.log(`GLMCode ${VERSION}
|
|
516
598
|
启动: glmcode
|
|
517
|
-
TUI: 输入 /
|
|
599
|
+
TUI: 输入 / 或字母实时弹出悬浮命令菜单(位于输入框正下方),↑↓ 键选择,回车直接执行选中的命令/技能,Tab 补全,删掉斜杠即可取消
|
|
518
600
|
底部常驻状态栏:思考开关 / 模型 / 技能数 / 当前目录
|
|
519
601
|
斜杠命令: /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 <名字>
|
|
602
|
+
技能: 将 SKILL.md 目录或 .zip 包放入 ~/.glmcode/skills/(或包内 skills/)自动加载;/skill new <名字> 创建,/skill <名字> 查看介绍,/技能名 直接调用。
|
|
521
603
|
认证: glmcode auth set <API_KEY> | auth status | auth logout | auth login
|
|
522
604
|
Shell: glmcode shell | shell list`);process.exit(0);}
|
|
523
605
|
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}`);}
|