glmcode 0.1.2 → 0.1.3
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 +29 -21
- package/package.json +9 -4
- package/src/auth.js +58 -0
- package/src/cli.js +18 -10
- package/src/config.js +4 -2
- package/src/skills.js +14 -0
package/README.md
CHANGED
|
@@ -1,42 +1,50 @@
|
|
|
1
1
|
# GLMCode
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
GLMCode 是一个 Node.js / npm 的 GLM Coding Agent,界面和交互以移动端 Python 原版 `glmcode.py` 为基础,同时提供文件工具、Shell、Skills、Plugins、MCP、Session 和模型切换。
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 安装
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npm install -g glmcode
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
npm 会自动安装 package.json 中声明的运行依赖;不需要手动 `pip install`,也不需要手动创建 PATH 脚本。npm 的全局 bin 会提供:
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
glmcode auth login
|
|
15
14
|
glmcode
|
|
15
|
+
glm
|
|
16
|
+
gc
|
|
16
17
|
```
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
三种启动命令。
|
|
20
|
+
|
|
21
|
+
## 登录
|
|
19
22
|
|
|
20
23
|
```bash
|
|
21
|
-
|
|
24
|
+
glmcode auth login
|
|
22
25
|
```
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+
GLMCode 会尝试在手机、Linux、macOS、Windows 上打开智谱官方平台页面,并自动检测已有的 `ZHIPU_API_KEY` / `GLM_API_KEY` 或本地凭据。
|
|
28
|
+
|
|
29
|
+
注意:智谱公开开发文档当前仍以“登录开放平台 → 创建 API Key → 使用 API Key 调用”为标准流程;普通网页登录并不会自动把 API Key 暴露给第三方 CLI。因此 GLMCode 不会伪造网页登录,也不会从浏览器 Cookie 中窃取凭据。
|
|
30
|
+
|
|
31
|
+
## 依赖
|
|
32
|
+
|
|
33
|
+
Python 原版需要 `requests` 与 `prompt_toolkit`,并使用 Python 标准库的 `zipfile`。Node 版对应为:
|
|
34
|
+
|
|
35
|
+
- Node.js >= 18:内置 `fetch`、readline、child_process、fs 等能力
|
|
36
|
+
- `adm-zip`:自动解压 `skills/*.zip` 技能包
|
|
37
|
+
|
|
38
|
+
用户通过 npm 安装时这些 npm 依赖会自动安装。
|
|
39
|
+
|
|
40
|
+
## 技能
|
|
25
41
|
|
|
26
|
-
|
|
27
|
-
- GLM provider with streaming responses
|
|
28
|
-
- Model switching and a configurable `freeModel` preset
|
|
29
|
-
- API-key login/logout stored under `~/.glmcode`
|
|
30
|
-
- Project instructions from `AGENTS.md`, `CLAUDE.md`, `.glmcode/INSTRUCTIONS.md`
|
|
31
|
-
- Skills discovery from `.glmcode/skills/**/SKILL.md` and `~/.glmcode/skills/**/SKILL.md`
|
|
32
|
-
- File read/write/list/search tools
|
|
33
|
-
- Shell execution with `ask`, `auto`, or `deny` approval modes
|
|
34
|
-
- Session persistence
|
|
35
|
-
- Plugin discovery foundation
|
|
36
|
-
- MCP server configuration and stdio launcher foundation
|
|
42
|
+
支持:
|
|
37
43
|
|
|
38
|
-
|
|
44
|
+
- `.glmcode/skills/**/SKILL.md`
|
|
45
|
+
- `~/.glmcode/skills/**/SKILL.md`
|
|
46
|
+
- `skills/*.zip` 自动解压
|
|
39
47
|
|
|
40
|
-
|
|
48
|
+
## 启动界面
|
|
41
49
|
|
|
42
|
-
|
|
50
|
+
保持 Python 原版的 GLM Logo、状态栏、`┃ 输入消息... ┃`、斜杠命令和技能补全风格。
|
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "glmcode",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "GLM Code · mobile coding agent with the GLM terminal UI, tools, skills, plugins and MCP.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"glmcode": "src/cli.js"
|
|
7
|
+
"glmcode": "src/cli.js",
|
|
8
|
+
"glm": "src/cli.js",
|
|
9
|
+
"gc": "src/cli.js"
|
|
8
10
|
},
|
|
9
11
|
"engines": {
|
|
10
|
-
"node": ">=
|
|
12
|
+
"node": ">=18"
|
|
11
13
|
},
|
|
12
14
|
"files": [
|
|
13
15
|
"src",
|
|
@@ -29,5 +31,8 @@
|
|
|
29
31
|
"skills",
|
|
30
32
|
"plugins"
|
|
31
33
|
],
|
|
32
|
-
"license": "MIT"
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"adm-zip": "^0.5.16"
|
|
37
|
+
}
|
|
33
38
|
}
|
package/src/auth.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { spawn } from 'node:child_process';
|
|
5
|
+
import { AUTH_FILE, readJson, writeJson, getAuth } from './config.js';
|
|
6
|
+
|
|
7
|
+
export const LOGIN_URL = 'https://open.bigmodel.cn/usercenter/apikeys';
|
|
8
|
+
export const PLATFORM_URL = 'https://open.bigmodel.cn/';
|
|
9
|
+
|
|
10
|
+
export async function openUrl(url) {
|
|
11
|
+
const attempts = process.platform === 'win32'
|
|
12
|
+
? [['cmd', ['/c', 'start', '', url]]]
|
|
13
|
+
: process.platform === 'darwin'
|
|
14
|
+
? [['open', [url]]]
|
|
15
|
+
: [['termux-open-url', [url]], ['xdg-open', [url]]];
|
|
16
|
+
for (const [cmd, args] of attempts) {
|
|
17
|
+
try {
|
|
18
|
+
const child = spawn(cmd, args, { detached: true, stdio: 'ignore' });
|
|
19
|
+
child.unref();
|
|
20
|
+
return true;
|
|
21
|
+
} catch {}
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function detectApiKey() {
|
|
27
|
+
const candidates = [
|
|
28
|
+
process.env.ZHIPU_API_KEY,
|
|
29
|
+
process.env.GLM_API_KEY,
|
|
30
|
+
process.env.BIGMODEL_API_KEY,
|
|
31
|
+
await getAuth('zhipu')
|
|
32
|
+
].filter(Boolean).map(x => String(x).trim()).filter(Boolean);
|
|
33
|
+
return candidates[0] || '';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function browserLogin() {
|
|
37
|
+
console.log('正在打开智谱 GLM 登录/API Key 页面...');
|
|
38
|
+
const opened = await openUrl(LOGIN_URL);
|
|
39
|
+
if (!opened) {
|
|
40
|
+
console.log(`请手动打开: ${LOGIN_URL}`);
|
|
41
|
+
}
|
|
42
|
+
console.log('');
|
|
43
|
+
console.log('登录说明:GLM 官方公开文档目前要求通过智谱开放平台创建 API Key。');
|
|
44
|
+
console.log('如果本机已有 ZHIPU_API_KEY / GLM_API_KEY,GLMCode 会自动检测并直接使用。');
|
|
45
|
+
console.log('浏览器登录本身不会把网页里的 API Key 自动暴露给第三方 CLI。');
|
|
46
|
+
console.log('');
|
|
47
|
+
const key = await detectApiKey();
|
|
48
|
+
if (key) {
|
|
49
|
+
console.log('检测到已有 API Key,正在使用已保存凭据。');
|
|
50
|
+
return key;
|
|
51
|
+
}
|
|
52
|
+
return '';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function authStatus() {
|
|
56
|
+
const key = await detectApiKey();
|
|
57
|
+
return { authenticated: Boolean(key), source: process.env.ZHIPU_API_KEY ? 'ZHIPU_API_KEY' : process.env.GLM_API_KEY ? 'GLM_API_KEY' : key ? 'local' : null };
|
|
58
|
+
}
|
package/src/cli.js
CHANGED
|
@@ -7,9 +7,10 @@ import { ensureState, AUTH_FILE, GLOBAL_DIR, getProjectConfig, getAuth, saveAuth
|
|
|
7
7
|
import { runAgent } from './agent.js';
|
|
8
8
|
import { listMcp, saveMcpConfig } from './mcp.js';
|
|
9
9
|
import { loadSkills, loadPlugins } from './skills.js';
|
|
10
|
+
import { browserLogin, authStatus, LOGIN_URL } from './auth.js';
|
|
10
11
|
|
|
11
12
|
await ensureState();
|
|
12
|
-
const VERSION = '0.1.
|
|
13
|
+
const VERSION = '0.1.3';
|
|
13
14
|
const cwd0 = process.cwd();
|
|
14
15
|
|
|
15
16
|
const C = { rst:'\x1b[0m', dim:'\x1b[2m', bold:'\x1b[1m', cya:'\x1b[36m', cyb:'\x1b[1;36m', grn:'\x1b[32m', yel:'\x1b[33m', red:'\x1b[31m', mag:'\x1b[35m', gry:'\x1b[90m' };
|
|
@@ -54,16 +55,22 @@ function printHelp(commands) {
|
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
async function authCmd(args) {
|
|
57
|
-
const sub = args[0] || '
|
|
58
|
+
const sub = args[0] || 'status';
|
|
58
59
|
if (sub === 'login') {
|
|
59
|
-
const
|
|
60
|
-
if (
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
const detected = await browserLogin();
|
|
61
|
+
if (detected) {
|
|
62
|
+
await saveAuth('zhipu', detected);
|
|
63
|
+
console.log(color(`已检测并保存现有 API 凭据到 ${AUTH_FILE}`, C.grn));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
console.log(color('未检测到本机已有 API Key。', C.yel));
|
|
67
|
+
console.log(color('请先在浏览器登录智谱开放平台并创建 API Key,然后设置 ZHIPU_API_KEY。', C.gry));
|
|
68
|
+
console.log(color(`登录入口: ${LOGIN_URL}`, C.gry));
|
|
63
69
|
return;
|
|
64
70
|
}
|
|
65
71
|
if (sub === 'logout') { await deleteAuth('zhipu'); console.log(color('已退出智谱登录。', C.grn)); return; }
|
|
66
|
-
const
|
|
72
|
+
const status = await authStatus();
|
|
73
|
+
console.log(status.authenticated ? color(`zhipu: authenticated (${status.source})`, C.grn) : color('zhipu: not authenticated', C.yel));
|
|
67
74
|
}
|
|
68
75
|
|
|
69
76
|
function hiddenInput(prompt) {
|
|
@@ -119,7 +126,7 @@ async function readLineInput(prompt, completer) {
|
|
|
119
126
|
|
|
120
127
|
async function interactive() {
|
|
121
128
|
const config=await getProjectConfig(cwd0); let model=config.model, thinking=Boolean(config.thinking), cwd=cwd0;
|
|
122
|
-
let apiKey=await getAuth(); if(!apiKey){console.log(color('
|
|
129
|
+
let apiKey=await getAuth(); if(!apiKey){ apiKey=await browserLogin(); if(apiKey) await saveAuth('zhipu', apiKey); } if(!apiKey){console.log(color('未检测到 API Key。请在浏览器登录智谱开放平台后配置 API Key,或设置 ZHIPU_API_KEY。',C.red));return;}
|
|
123
130
|
let skills=await loadSkills(cwd), commands=commandMap(skills), sessionId;
|
|
124
131
|
startPage();
|
|
125
132
|
console.log(color(`提示: 思考模式默认${thinking?'开启':'关闭'},输入 /thinking ${thinking?'关闭':'开启'}`,C.gry));
|
|
@@ -156,10 +163,11 @@ async function interactive() {
|
|
|
156
163
|
async function main(){
|
|
157
164
|
const [command,...argv]=process.argv.slice(2); const cwd=process.cwd();
|
|
158
165
|
if(!command||command==='chat')return interactive();
|
|
159
|
-
if(command==='-h'||command==='--help'||command==='help'){console.log(`GLMCode ${VERSION}\n\n用法:\n glmcode 启动 GLM
|
|
166
|
+
if(command==='-h'||command==='--help'||command==='help'){console.log(`GLMCode ${VERSION}\n\n用法:\n glmcode 启动 GLM 风格交互界面
|
|
167
|
+
glm / gc 快捷启动 GLMCode\n glmcode run <prompt> 执行一次 coding task\n glmcode auth login|logout 管理智谱 API Key\n glmcode models 查看模型\n glmcode model <id>|free 切换模型\n glmcode session list|clear <id> 管理会话\n glmcode skill list 查看技能\n glmcode plugin list 查看插件\n glmcode mcp list|add|remove 管理 MCP\n glmcode config 查看配置`);return;}
|
|
160
168
|
if(command==='-v'||command==='--version')return console.log(VERSION);
|
|
161
169
|
const config=await getProjectConfig(cwd);
|
|
162
|
-
if(command==='run'){
|
|
170
|
+
if(command==='run'){let key=await getAuth();if(!key) key=await browserLogin();if(!key) throw new Error('No Zhipu API key detected. Run: glmcode auth login');return runAgent({cwd,config,apiKey:key,prompt:argv.join(' '),model:config.model});}
|
|
163
171
|
if(command==='auth')return authCmd(argv);if(command==='models')return modelsCmd();if(command==='model')return modelCmd(argv,config,cwd);if(command==='session')return sessionCmd(argv);if(command==='skill')return skillCmd(argv,cwd);if(command==='plugin')return pluginCmd(argv,config,cwd);if(command==='mcp')return mcpCmd(argv,config,cwd);if(command==='config')return console.log(JSON.stringify(config,null,2));
|
|
164
172
|
console.error(`Unknown command: ${command}`);process.exitCode=1;
|
|
165
173
|
}
|
package/src/config.js
CHANGED
|
@@ -14,12 +14,14 @@ const defaultConfig = {
|
|
|
14
14
|
baseUrl: 'https://open.bigmodel.cn/api/paas/v4',
|
|
15
15
|
maxTokens: 8192,
|
|
16
16
|
temperature: 0.2,
|
|
17
|
-
thinking: '
|
|
17
|
+
thinking: 'disabled',
|
|
18
18
|
approval: 'ask',
|
|
19
19
|
autoCompact: true,
|
|
20
20
|
skillsDirs: ['.glmcode/skills', '~/.glmcode/skills'],
|
|
21
21
|
plugins: [],
|
|
22
|
-
mcp: {}
|
|
22
|
+
mcp: {},
|
|
23
|
+
startupCommand: 'glmcode',
|
|
24
|
+
autoOpenLogin: true
|
|
23
25
|
};
|
|
24
26
|
|
|
25
27
|
export async function ensureState() {
|
package/src/skills.js
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { expandHome, exists } from './utils.js';
|
|
4
|
+
import AdmZip from 'adm-zip';
|
|
5
|
+
|
|
6
|
+
async function extractZipSkills(root) {
|
|
7
|
+
if (!await exists(root)) return;
|
|
8
|
+
for (const name of await fs.readdir(root)) {
|
|
9
|
+
if (!name.toLowerCase().endsWith('.zip')) continue;
|
|
10
|
+
const zipPath = path.join(root, name);
|
|
11
|
+
const target = path.join(root, name.slice(0, -4));
|
|
12
|
+
try {
|
|
13
|
+
if (!await exists(target)) new AdmZip(zipPath).extractAllTo(target, true);
|
|
14
|
+
} catch {}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
4
17
|
|
|
5
18
|
export async function loadSkills(cwd) {
|
|
6
19
|
const dirs = [path.join(cwd, '.glmcode', 'skills'), path.join(expandHome('~/.glmcode'), 'skills')];
|
|
7
20
|
const skills = [];
|
|
8
21
|
for (const root of dirs) {
|
|
22
|
+
await extractZipSkills(root);
|
|
9
23
|
if (!await exists(root)) continue;
|
|
10
24
|
for (const name of await fs.readdir(root)) {
|
|
11
25
|
const file = path.join(root, name, 'SKILL.md');
|