glmcode 0.1.8 → 0.1.9

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 CHANGED
@@ -1,14 +1,23 @@
1
- # Changelog
1
+ # CHANGELOG
2
2
 
3
- ## 0.1.7
3
+ ## 0.1.9 (2026-08-28)
4
4
 
5
- ### Skills 路径修正
5
+ - 输入 `/` 或前缀自动弹出斜杠命令菜单(含技能命令)
6
+ - 新增底部状态栏,实时显示思考开关 / 模型 / 技能数 / 当前目录
7
+ - 技能包支持 `.zip` 自动解压加载(依赖 `adm-zip`)
8
+ - 启动时自动检测并安装缺失依赖(`ensureDeps`)
9
+ - `/install` 增加 termux `pkg` 支持
10
+ - Tab 补全菜单增强
6
11
 
7
- - Skills 目录改为相对于主 CLI 文件位置解析,并在启动时自动创建
8
- - 按原版 glmcode.py 重构交互式 coding agent
9
- - 增加流式 GLM Chat Completions
10
- - 增加 run_shell / read_file / write_file / use_skill
11
- - 增加 Skills 自动扫描和斜杠调用
12
- - 输入 `/` 后提供命令补全
13
- - 增加底部状态栏:思考、模型、技能数量、当前目录
14
- - 保留跨平台 Shell 和 API Key 自动检测
12
+ ## 0.1.8 (2026-08-27)
13
+
14
+ - 重构为单文件 `src/cli.js`,零第三方运行时依赖
15
+ - 内置工具:`run_shell` / `write_file` / `read_file` / `use_skill`
16
+ - 技能系统:`skills/` 目录 `SKILL.md` 自动加载
17
+ - 认证:`glmcode auth set / status / logout / login`
18
+ - `glmcode shell` 系统信息查看
19
+ - 流式对话 + 思考模式切换
20
+
21
+ ## 0.1.6
22
+
23
+ - 首个公开版本
package/LICENSE CHANGED
@@ -1 +1,21 @@
1
1
  MIT License
2
+
3
+ Copyright (c) 2026 jimioffice
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,58 +1,56 @@
1
- # GLMCode 0.1.7
1
+ # GLMCode
2
2
 
3
- 基于原版 `glmcode.py` Node.js CLI 实现,重点保留其交互界面、斜杠命令、技能系统、流式输出和 coding-agent 工具。
3
+ GLM 移动端 coding agent CLI。在终端里直接和 GLM 对话,自动执行写文件、读文件、Shell 命令、加载技能等任务。
4
4
 
5
- ## 启动
6
-
7
- ```bash
5
+ ```
8
6
  npm install -g glmcode
9
- glmcode
10
7
  ```
11
8
 
12
- ## API Key
9
+ ## 特性
13
10
 
14
- 检测顺序:
11
+ - 流式对话(支持思考模式)
12
+ - 四个内置工具:`run_shell` / `write_file` / `read_file` / `use_skill`
13
+ - 技能系统:把 `SKILL.md` 目录或 `.zip` 技能包放进 `skills/` 自动加载
14
+ - 斜杠命令菜单:输入 `/` 或前缀即弹出菜单,Tab 补全
15
+ - 底部状态栏:实时显示思考开关 / 模型 / 技能数 / 当前目录
16
+ - 自动安装依赖:缺失 `adm-zip` 等依赖时启动自动 `npm install`
17
+ - `/install` 命令:调用系统包管理器(apt/apk/pkg/brew/winget 等)自动安装程序
18
+ - 认证:`glmcode auth set <API_KEY>`
15
19
 
16
- 1. `ZHIPU_API_KEY`
17
- 2. `GLM_API_KEY`
18
- 3. `~/.glmcode/auth.json`
19
-
20
- 已有 Key:
20
+ ## 用法
21
21
 
22
22
  ```bash
23
- glmcode auth set YOUR_API_KEY
23
+ glmcode # 启动对话
24
+ glmcode --help # 帮助
25
+ glmcode auth set sk-xxx # 设置 API Key
26
+ glmcode shell # 查看系统信息
27
+ glmcode shell list # 列出可用 Shell
24
28
  ```
25
29
 
26
- ## 交互
30
+ ## 斜杠命令
27
31
 
28
- 输入 `/` 后按 Tab 可补全命令;命令包括:
32
+ | 命令 | 说明 |
33
+ |------|------|
34
+ | `/help` | 显示全部命令 |
35
+ | `/clear` | 清空对话 |
36
+ | `/thinking` | 切换思考模式 |
37
+ | `/model <ID>` | 切换模型 |
38
+ | `/pwd` `/cd` `/ls` `/cat` | 目录与文件操作 |
39
+ | `/run <shell>` | 执行命令 |
40
+ | `/install <程序>` | 自动安装程序/依赖 |
41
+ | `/exit` | 退出 |
29
42
 
30
- `/help` `/clear` `/thinking` `/model` `/pwd` `/cd` `/ls` `/cat` `/run` `/exit`
43
+ ## 环境变量
31
44
 
32
- Skills 目录由程序自动创建,位置固定为**主 CLI 文件所在目录的上一级目录中的 `skills` 子目录**。
45
+ - `ZHIPU_API_KEY` / `GLM_API_KEY`:直接注入 API Key,无需写盘
46
+ - `GLM_SKILLS_DIR`:自定义技能目录
33
47
 
34
- npm 安装后通常是:
48
+ ## 技能
35
49
 
36
- ```text
37
- .../glmcode/
38
- ├── src/
39
- │ └── cli.js
40
- └── skills/
41
- ├── coding/
42
- │ └── SKILL.md
43
- └── git/
44
- └── SKILL.md
50
+ ```bash
51
+ mkdir -p skills/my-skill
52
+ # 写入 skills/my-skill/SKILL.md(含 name / description frontmatter)
53
+ # 或将技能打成 zip 放入 skills/,启动时自动解压加载
45
54
  ```
46
55
 
47
- 程序启动时自动创建 `skills/`,然后只检测这个目录的**直接子目录**;每个子目录中的 `SKILL.md` 会注册成一个斜杠命令,例如 `/coding`。
48
-
49
- AI 可调用:
50
-
51
- - `run_shell`
52
- - `read_file`
53
- - `write_file`
54
- - `use_skill`
55
-
56
- ## 跨平台
57
-
58
- 根据实际系统选择 Windows CMD/PowerShell、macOS/Linux/Alpine/Android 可用 Shell;打开网页登录时不会强制依赖 `termux-open-url`。
56
+ 启动后输入 `/技能名` 即可注入技能指令。
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "glmcode",
3
- "version": "0.1.8",
4
- "description": "GLM coding agent CLI with shell tools, skills, streaming chat and slash-command completion.",
3
+ "version": "0.1.9",
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": {
7
7
  "glmcode": "src/cli.js",
@@ -9,7 +9,8 @@
9
9
  "gc": "src/cli.js"
10
10
  },
11
11
  "scripts": {
12
- "check": "node --check src/cli.js && npm pack --dry-run"
12
+ "check": "node --check src/cli.js && npm pack --dry-run",
13
+ "postinstall": "node -e \"try{require.resolve('adm-zip')}catch{console.log('[glmcode] installing deps...');require('child_process').execSync('npm install --no-save adm-zip')}\""
13
14
  },
14
15
  "files": [
15
16
  "src/",
@@ -18,7 +19,21 @@
18
19
  "CHANGELOG.md",
19
20
  "LICENSE"
20
21
  ],
22
+ "dependencies": {
23
+ "adm-zip": "^0.5.16"
24
+ },
21
25
  "engines": {
22
26
  "node": ">=18"
23
- }
27
+ },
28
+ "keywords": [
29
+ "glm",
30
+ "zhipu",
31
+ "coding-agent",
32
+ "cli",
33
+ "mcp",
34
+ "skills",
35
+ "plugins",
36
+ "termux"
37
+ ],
38
+ "license": "MIT"
24
39
  }
package/src/cli.js CHANGED
@@ -6,7 +6,7 @@ import readline from "node:readline";
6
6
  import {spawnSync, spawn} from "node:child_process";
7
7
  import {fileURLToPath} from "node:url";
8
8
 
9
- const VERSION="0.1.8";
9
+ const VERSION="0.1.9";
10
10
  const BASE_URL="https://open.bigmodel.cn/api/paas/v4";
11
11
  const API_KEYS_URL="https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys";
12
12
  const HOME=os.homedir();
@@ -58,9 +58,49 @@ function openUrl(url){
58
58
  return false;
59
59
  }
60
60
 
61
- function loadSkills(){
61
+ /* ---- 自动依赖安装(确保所有需要的程序/依赖就绪) ---- */
62
+ async function ensureDeps(){
63
+ const missing=[];
64
+ try{await import("adm-zip");}catch{missing.push("adm-zip");}
65
+ if(!missing.length)return true;
66
+ console.log(color(`[glmcode] 缺少依赖: ${missing.join(", ")},正在自动安装...`,C.yellow));
67
+ let ok=true;
68
+ for(const m of missing){
69
+ const r=spawnSync(process.platform==="win32"?"npm.cmd":"npm",["install","--no-save",m],{cwd:PACKAGE_ROOT,encoding:"utf8",stdio:"inherit"});
70
+ if(r.status!==0){ok=false;console.log(color(`[glmcode] ${m} 自动安装失败,请手动运行: npm install -g glmcode`,C.red));}
71
+ }
72
+ return ok;
73
+ }
74
+
75
+ /* ---- 技能系统:SKILL.md + zip 自动解压 ---- */
76
+ async function extractZipSkills(){
77
+ if(!fs.existsSync(SKILLS_DIR))return;
78
+ const zips=fs.readdirSync(SKILLS_DIR).filter(x=>x.toLowerCase().endsWith(".zip")).sort((a,b)=>a.localeCompare(b));
79
+ if(!zips.length)return;
80
+ let AdmZip=null;
81
+ try{AdmZip=(await import("adm-zip")).default;}catch{return;}
82
+ for(const f of zips){
83
+ const zp=path.join(SKILLS_DIR,f), target=path.join(SKILLS_DIR,f.replace(/\.zip$/i,""));
84
+ if(fs.existsSync(target))continue;
85
+ try{
86
+ new AdmZip(zp).extractAllTo(target,true);
87
+ // 扁平化:zip 内带同名根目录时(如 my-skill/SKILL.md)解压会出现 target/my-skill/... 嵌套,上移一层
88
+ const sub=fs.readdirSync(target,{withFileTypes:true}).find(d=>d.isDirectory());
89
+ if(sub && !fs.existsSync(path.join(target,"SKILL.md")) && fs.existsSync(path.join(target,sub.name,"SKILL.md"))){
90
+ for(const e of fs.readdirSync(path.join(target,sub.name))){
91
+ fs.renameSync(path.join(target,sub.name,e),path.join(target,e));
92
+ }
93
+ fs.rmdirSync(path.join(target,sub.name));
94
+ }
95
+ console.log(color(`[glmcode] 技能包已解压: ${f} -> ${path.basename(target)}`,C.green));
96
+ }catch(e){console.log(color(`[glmcode] 解压失败 ${f}: ${e.message}`,C.red));}
97
+ }
98
+ }
99
+ async function loadSkills(){
100
+ await extractZipSkills();
62
101
  skills=new Map(); fs.mkdirSync(SKILLS_DIR,{recursive:true});
63
- for(const entry of fs.readdirSync(SKILLS_DIR,{withFileTypes:true}).sort((a,b)=>a.name.localeCompare(b.name))){
102
+ const entries=fs.readdirSync(SKILLS_DIR,{withFileTypes:true}).sort((a,b)=>a.name.localeCompare(b.name));
103
+ for(const entry of entries){
64
104
  if(!entry.isDirectory())continue;
65
105
  const file=path.join(SKILLS_DIR,entry.name,"SKILL.md"); if(!fs.existsSync(file))continue;
66
106
  const content=fs.readFileSync(file,"utf8");
@@ -97,7 +137,7 @@ const TOOLS=[
97
137
  function execTool(name,args){
98
138
  if(name==="run_shell"){
99
139
  const s=shellInfo(), isWin=process.platform==="win32";
100
- const file=isWin?s.exe:s.exe, a=isWin?(s.name==="powershell"?["-NoProfile","-Command",args.command]:["/d","/s","/c",args.command]):["-lc",args.command];
140
+ const file=s.exe, a=isWin?(s.name==="powershell"?["-NoProfile","-Command",args.command]:["/d","/s","/c",args.command]):["-lc",args.command];
101
141
  try{const r=spawnSync(file,a,{cwd,encoding:"utf8",timeout:60000});return ((r.stdout||"")+(r.stderr?"\n[stderr]\n"+r.stderr:"")).slice(0,8000)||"(无输出)";}
102
142
  catch(e){return `执行失败: ${e.message}`;}
103
143
  }
@@ -154,10 +194,19 @@ async function chat(text){
154
194
  }
155
195
  }
156
196
  }
197
+ /* ---- 斜杠命令菜单:输入 / 即弹出 ---- */
198
+ function showMenu(prefix){
199
+ const p=(prefix||"/").trim();
200
+ const list=Object.entries(commands()).filter(([c])=>c.startsWith(p));
201
+ if(!list.length){console.log(color(`没有以 "${p}" 开头的命令`,C.red));return;}
202
+ console.log(color(`— 命令菜单 (${list.length}) —`,C.cyanB));
203
+ for(const [c,d] of list)console.log(` ${color(c,C.cyanB).padEnd(20)} ${color(d,C.gray)}`);
204
+ console.log("");
205
+ }
157
206
  function handleCommand(line){
158
207
  const [cmd,...rest]=line.trim().split(/\s+/), arg=rest.join(" ");
159
208
  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;}
160
- if(cmd==="/help"){console.log("");for(const [c,d] of Object.entries(commands()))console.log(` ${color(c,C.cyanB).padEnd(24)} ${color(d,C.gray)}`);console.log("");}
209
+ if(cmd==="/help"){showMenu("/");}
161
210
  else if(cmd==="/clear"){messages=[];console.log(color("已清空对话",C.green));}
162
211
  else if(cmd==="/thinking"){thinkingEnabled=!thinkingEnabled;config.thinking=thinkingEnabled;saveConfig();console.log(color(`思考模式: ${thinkingEnabled?"开启":"关闭"}`,C.yellow));}
163
212
  else if(cmd==="/model"){if(arg){config.model=arg;saveConfig();console.log(color(`模型已切换: ${arg}`,C.green));}else console.log(`当前模型: ${config.model}`);}
@@ -175,18 +224,26 @@ function logo(){
175
224
  const rows=[" ██████╗ ██╗ ███╗ ███╗","██╔════╝ ██║ ████╗ ████║","██║ ███╗ ██║ ██╔████╔██║","██║ ██║ ██║ ██║╚██╔╝██║","╚██████╔╝ ███████╗ ██║ ╚═╝ ██║"," ╚═════╝ ╚══════╝ ╚═╝ ╚═╝"];
176
225
  const W=44;console.log(color("".padStart(W),C.cyan));console.log(color("✧ GLM ✧".padStart((W+"✧ GLM ✧".length)/2),C.cyanB));for(const r of rows)console.log(color(r.padStart((W+r.length)/2),C.cyanB));console.log(color("GLM Code · Coding Agent",C.cyanB));console.log(color(`v${VERSION}`,C.gray));console.log(color("/help 查看命令 · /thinking 思考开关",C.gray));console.log("");
177
226
  }
178
- function toolbar(){return `思考:${thinkingEnabled?"开":"关"} 模型:${config.model} 技能:${skills.size} ${cwd}`;}
227
+ /* ---- 底部状态栏 ---- */
228
+ function toolbar(){return `┌ 思考:${thinkingEnabled?"开":"关"} 模型:${config.model} 技能:${skills.size} 目录:${cwd} ┐`;}
179
229
  function completer(line){
180
- if(!line.startsWith("/"))return [];
181
- return Object.entries(commands()).filter(([c])=>c.startsWith(line)).map(([c,d])=>`${c} — ${d}`);
230
+ if(!line.startsWith("/"))return [[],[]];
231
+ const list=Object.entries(commands()).filter(([c])=>c.startsWith(line)).map(([c,d])=>`${c} — ${d}`);
232
+ return [list.length?list:[line], list.map(v=>v.split(" — ")[0])];
182
233
  }
183
234
  function promptLoop(){
184
- const rl=readline.createInterface({input:process.stdin,output:process.stdout,completer:(line)=>{const x=completer(line);return [x,x.map(v=>v.split(" — ")[0])];}});
185
- rl.setPrompt(color("┃ 输入消息... ┃ ",C.cyanB));rl.prompt();
235
+ const rl=readline.createInterface({input:process.stdin,output:process.stdout,completer});
236
+ rl.setPrompt(color("┃ 输入消息( / 弹菜单, Tab 补全) ┃ ",C.cyanB));
237
+ function next(){console.log(color(toolbar(),C.gray));rl.prompt();}
238
+ next();
186
239
  rl.on("line",async line=>{
187
- const text=line.trim();if(!text){rl.prompt();return;}
188
- if(text.startsWith("/"))handleCommand(text);else{rl.pause();await chat(text);rl.resume();}
189
- console.log(color(toolbar(),C.gray));rl.prompt();
240
+ const text=line.trim();if(!text){next();return;}
241
+ if(text.startsWith("/")){
242
+ const cmds=commands();
243
+ if(text!=="/"&&cmds[text])handleCommand(text);
244
+ else showMenu(text==="/"?"/":text);
245
+ }else{rl.pause();await chat(text);rl.resume();}
246
+ next();
190
247
  });
191
248
  rl.on("SIGINT",()=>{if(stopping){rl.close();process.exit(0);}stopping=true;console.log(color("\n[再次 Ctrl+C 退出]",C.yellow));setTimeout(()=>stopping=false,1200);rl.prompt();});
192
249
  rl.on("close",()=>process.exit(0));
@@ -196,7 +253,7 @@ function promptLoop(){
196
253
  function detectInstaller(){
197
254
  if(process.platform==="win32")return "winget";
198
255
  if(process.platform==="darwin")return "brew";
199
- for(const pm of ["apk","apt-get","apt","dnf","pacman","pkg"]){
256
+ for(const pm of ["apk","apt-get","apt","dnf","pacman","pkg","termux-pkg"]){
200
257
  if(spawnSync("sh",["-lc",`command -v ${pm}`],{encoding:"utf8"}).status===0)return pm;
201
258
  }
202
259
  return null;
@@ -212,15 +269,17 @@ function installProgram(name){
212
269
  }
213
270
  async function main(){
214
271
  loadConfig();const a=loadAuth();if(!a){console.log(color("未检测到 GLM API Key。",C.red));console.log(`API Key 页面: ${API_KEYS_URL}`);console.log("获得 Key 后运行: glmcode auth set <API_KEY>");process.exit(1);}
215
- apiKey=a.key;loadSkills();logo();console.log(color(`API: ${mask(apiKey)} (${a.source})`,C.green));console.log(color(`模型: ${config.model} 技能: ${skills.size}`,C.gray));console.log("");
272
+ apiKey=a.key;await ensureDeps();await loadSkills();logo();console.log(color(`API: ${mask(apiKey)} (${a.source})`,C.green));console.log(color(`模型: ${config.model} 技能: ${skills.size}`,C.gray));console.log("");
216
273
  promptLoop();
217
274
  }
218
275
  const argv=process.argv.slice(2);
219
276
  if(argv[0]==="--version"||argv[0]==="-v"){console.log(VERSION);process.exit(0);}
220
277
  if(argv[0]==="--help"||argv[0]==="-h"){console.log(`GLMCode ${VERSION}
221
278
  启动: glmcode
222
- 斜杠命令: /help /clear /thinking /model /pwd /cd /ls /cat /run /exit
223
- 认证: glmcode auth set <API_KEY> | auth status | auth logout
279
+ 斜杠命令: /help /clear /thinking /model /pwd /cd /ls /cat /run /install /exit
280
+ 输入 / 或前缀自动弹出命令菜单,Tab 补全,底部常驻状态栏。
281
+ 技能: 将 SKILL.md 目录或 .zip 包放入 skills/ 自动加载。
282
+ 认证: glmcode auth set <API_KEY> | auth status | auth logout | auth login
224
283
  Shell: glmcode shell | shell list`);process.exit(0);}
225
284
  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}`);}
226
285
  else if(s==="status"){const a=loadAuth();console.log(a?`API Key: ${mask(a.key)}\n来源: ${a.source}`:"未检测到 API Key。");}