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 CHANGED
@@ -1,42 +1,50 @@
1
1
  # GLMCode
2
2
 
3
- A Node.js coding-agent CLI powered by Zhipu GLM. It is inspired by the workflow patterns of modern coding agents, but is an independent implementation and does not copy their code or branding.
3
+ GLMCode 是一个 Node.js / npm GLM Coding Agent,界面和交互以移动端 Python 原版 `glmcode.py` 为基础,同时提供文件工具、Shell、Skills、Plugins、MCP、Session 和模型切换。
4
4
 
5
- ## Install
5
+ ## 安装
6
6
 
7
7
  ```bash
8
8
  npm install -g glmcode
9
9
  ```
10
10
 
11
- Then:
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
- Or:
19
+ 三种启动命令。
20
+
21
+ ## 登录
19
22
 
20
23
  ```bash
21
- ZHIPU_API_KEY=your_key glmcode run "分析当前项目并修复测试失败的问题"
24
+ glmcode auth login
22
25
  ```
23
26
 
24
- ## Current features
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
- - Interactive terminal chat
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
- ## Project config
44
+ - `.glmcode/skills/**/SKILL.md`
45
+ - `~/.glmcode/skills/**/SKILL.md`
46
+ - `skills/*.zip` 自动解压
39
47
 
40
- Create `glmcode.json` in a project directory. Start from `glmcode.example.json`.
48
+ ## 启动界面
41
49
 
42
- > Note: this first release is a working foundation, not a claim of feature-for-feature parity with OpenCode. Remote MCP OAuth, LSP integration, subagents, TUI panels, GitHub automation, and a richer extension API are deliberately left for later iterations.
50
+ 保持 Python 原版的 GLM Logo、状态栏、`┃ 输入消息... ┃`、斜杠命令和技能补全风格。
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "glmcode",
3
- "version": "0.1.2",
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": ">=20"
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.2';
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] || 'list';
58
+ const sub = args[0] || 'status';
58
59
  if (sub === 'login') {
59
- const key = await hiddenInput('API Key: ');
60
- if (!key.trim()) throw new Error('Empty API key');
61
- await saveAuth('zhipu', key.trim());
62
- console.log(color(`已保存到 ${AUTH_FILE}`, C.grn));
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 key = await getAuth(); console.log(key ? color('zhipu: authenticated', C.grn) : color('zhipu: not authenticated', C.yel));
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('未配置 API Key。请先执行: glmcode auth login',C.red));return;}
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 风格交互界面\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;}
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'){const key=await getAuth();return runAgent({cwd,config,apiKey:key,prompt:argv.join(' '),model:config.model});}
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: 'enabled',
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');