glmcode 0.1.5 → 0.1.6

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,8 +1,9 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.4
4
- - Cross-platform OS and shell detection.
5
- - Safe browser launching for Termux, Linux, macOS and Windows.
6
- - Headless Linux/Alpine environments no longer crash when no browser launcher exists.
7
- - Added `glmcode auth login --no-browser`.
8
- - Added shell inspection commands.
3
+ ## 0.1.6
4
+
5
+ - 修复 API Key 检测:环境变量 + `~/.glmcode/auth.json`
6
+ - 增加 `auth set/status/logout`
7
+ - 修复没有 `termux-open-url` 时的 ENOENT 崩溃
8
+ - 更新智谱 API Key 管理页面
9
+ - 保留跨平台 Shell 检测与配置入口
package/LICENSE ADDED
@@ -0,0 +1 @@
1
+ MIT License
package/README.md CHANGED
@@ -1,29 +1,60 @@
1
- # GLMCode 0.1.4
1
+ # GLMCode 0.1.6
2
2
 
3
- Cross-platform GLM CLI for Termux, Linux/Alpine, macOS and Windows.
4
-
5
- Install:
3
+ ## 安装
6
4
  ```bash
7
5
  npm install -g glmcode
6
+ glmcode --version
8
7
  ```
9
8
 
10
- Run:
9
+ ## API Key 自动检测
10
+ 检测顺序:
11
+ 1. `ZHIPU_API_KEY`
12
+ 2. `GLM_API_KEY`
13
+ 3. `~/.glmcode/auth.json`
14
+
15
+ 已有 Key:
11
16
  ```bash
12
- glmcode
17
+ glmcode auth set YOUR_API_KEY
13
18
  ```
14
19
 
15
- Aliases: `glm`, `gc`
20
+ 状态:
21
+ ```bash
22
+ glmcode auth status
23
+ ```
16
24
 
17
- Authentication:
25
+ 没有 Key:
18
26
  ```bash
19
27
  glmcode auth login
28
+ ```
29
+
30
+ 无 GUI 环境:
31
+ ```bash
20
32
  glmcode auth login --no-browser
21
33
  ```
22
34
 
23
- Shell inspection:
35
+ 官方 API Key 页面:
36
+ https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys
37
+
38
+ ## Shell
24
39
  ```bash
25
40
  glmcode shell
26
41
  glmcode shell list
42
+ glmcode run "echo hello"
27
43
  ```
28
44
 
29
- Headless environments print the login URL instead of crashing when no browser launcher exists.
45
+ 0.1.6 不再假定 `termux-open-url` 一定存在;Windows、macOS、Linux/Alpine/Android 环境分别检测可用启动器和 Shell。
46
+
47
+ ## 配置
48
+ 配置:`~/.glmcode/config.json`
49
+ 认证:`~/.glmcode/auth.json`
50
+
51
+ ```bash
52
+ glmcode config
53
+ glmcode config set model glm-4.5
54
+ ```
55
+
56
+ ## 开发
57
+ ```bash
58
+ npm run check
59
+ npm pack
60
+ ```
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "glmcode",
3
- "version": "0.1.5",
4
- "description": "Cross-platform GLM coding agent CLI",
3
+ "version": "0.1.6",
4
+ "description": "Cross-platform GLM CLI with local authentication and shell detection.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "glmcode": "src/cli.js",
@@ -9,9 +9,15 @@
9
9
  "gc": "src/cli.js"
10
10
  },
11
11
  "scripts": {
12
- "start": "node src/cli.js",
13
- "check": "node --check src/cli.js && node --check src/auth.js && node --check src/platform/shell.js && node --check src/platform/browser.js"
12
+ "check": "node --check src/cli.js && node --check src/auth.js && node --check src/config.js && node --check src/shell.js"
14
13
  },
15
- "engines": { "node": ">=18" },
16
- "license": "MIT"
14
+ "files": [
15
+ "src/",
16
+ "README.md",
17
+ "CHANGELOG.md",
18
+ "LICENSE"
19
+ ],
20
+ "engines": {
21
+ "node": ">=18"
22
+ }
17
23
  }
package/src/auth.js CHANGED
@@ -1,40 +1,62 @@
1
- import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
- import { homedir } from "node:os";
3
- import { join } from "node:path";
4
- import { openBrowser } from "./platform/browser.js";
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { execFileSync, spawn } from "node:child_process";
5
5
 
6
- const dir = join(homedir(), ".glmcode");
7
- const authFile = join(dir, "auth.json");
8
- const loginUrl = "https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys";
6
+ export const CONFIG_DIR = path.join(os.homedir(), ".glmcode");
7
+ export const AUTH_FILE = path.join(CONFIG_DIR, "auth.json");
8
+ export const API_KEYS_URL = "https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys";
9
9
 
10
- export function getApiKey() {
11
- for (const k of ["ZHIPU_API_KEY", "GLM_API_KEY", "BIGMODEL_API_KEY"]) {
12
- if (process.env[k]) return process.env[k];
13
- }
14
- try {
15
- const x = JSON.parse(readFileSync(authFile, "utf8"));
16
- return x.apiKey || x.api_key || "";
17
- } catch { return ""; }
10
+ function readJson(file, fallback = {}) {
11
+ try { return JSON.parse(fs.readFileSync(file, "utf8")); } catch { return fallback; }
12
+ }
13
+ function writeJson(file, value) {
14
+ fs.mkdirSync(CONFIG_DIR, {recursive:true, mode:0o700});
15
+ fs.writeFileSync(file, JSON.stringify(value,null,2)+"\n", {mode:0o600});
16
+ try { fs.chmodSync(file,0o600); } catch {}
18
17
  }
18
+ function key(v) { const s = v == null ? "" : String(v).trim(); return s || null; }
19
19
 
20
- export async function login({ noBrowser = false } = {}) {
21
- mkdirSync(dir, { recursive: true });
22
- console.log("正在打开智谱 GLM 登录/API Key 页面...");
23
- console.log("GLM API 使用 API Key 进行 API 认证。");
20
+ export function getApiKey() {
21
+ const env = key(process.env.ZHIPU_API_KEY) || key(process.env.GLM_API_KEY);
22
+ if (env) return {apiKey:env, source:"environment"};
23
+ const a = readJson(AUTH_FILE,{});
24
+ const saved = key(a.apiKey) || key(a.api_key) || key(a.key) || key(a.token);
25
+ return saved ? {apiKey:saved, source:AUTH_FILE} : null;
26
+ }
27
+ export function saveApiKey(apiKey) {
28
+ const k = key(apiKey);
29
+ if (!k) throw new Error("API Key 不能为空");
30
+ writeJson(AUTH_FILE,{provider:"zhipu",apiKey:k,updatedAt:new Date().toISOString()});
31
+ return AUTH_FILE;
32
+ }
33
+ export function clearAuth() { writeJson(AUTH_FILE,{}); }
24
34
 
25
- if (getApiKey()) {
26
- console.log("已检测到现有 API Key,无需再次输入。");
27
- return true;
35
+ function exists(cmd) {
36
+ try { execFileSync(process.platform==="win32" ? "where" : "which",[cmd],{stdio:"ignore"}); return true; }
37
+ catch { return false; }
38
+ }
39
+ export function openUrl(url) {
40
+ const candidates = process.platform==="win32"
41
+ ? [["cmd",["/c","start","",url]]]
42
+ : process.platform==="darwin"
43
+ ? [["open",[url]]]
44
+ : [["xdg-open",[url]],["termux-open-url",[url]],["gio",["open",url]]];
45
+ for (const [cmd,args] of candidates) {
46
+ if (!exists(cmd)) continue;
47
+ try { const p=spawn(cmd,args,{detached:true,stdio:"ignore"}); p.unref(); return true; } catch {}
28
48
  }
29
-
30
- if (noBrowser) console.log(`请在浏览器中打开:\n${loginUrl}\n`);
31
- else await openBrowser(loginUrl);
32
- return Boolean(getApiKey());
49
+ return false;
33
50
  }
34
-
35
- export function saveApiKey(apiKey) {
36
- if (!apiKey?.trim()) throw new Error("API Key 不能为空");
37
- mkdirSync(dir, { recursive: true });
38
- writeFileSync(authFile, JSON.stringify({ apiKey: apiKey.trim() }, null, 2), { mode: 0o600 });
39
- console.log(`Saved to ${authFile}`);
51
+ export function login({noBrowser=false}={}) {
52
+ const found=getApiKey();
53
+ if (found) { console.log(`已检测到 API Key(来源:${found.source}),无需重新配置。`); return found; }
54
+ console.log("未检测到 GLM API Key。");
55
+ console.log(`API Key 管理页面:${API_KEYS_URL}`);
56
+ if (!noBrowser && openUrl(API_KEYS_URL)) console.log("已尝试打开默认浏览器。");
57
+ else console.log("当前环境无法自动打开浏览器,请手动复制上面的地址。");
58
+ console.log("浏览器登录不会自动把网页中的 API Key 暴露给 CLI。");
59
+ console.log("获得 Key 后运行:glmcode auth set <API_KEY>");
60
+ return null;
40
61
  }
62
+ export function maskKey(k) { return k && k.length>8 ? `${k.slice(0,4)}...${k.slice(-4)}` : "********"; }
package/src/cli.js CHANGED
@@ -1,88 +1,31 @@
1
1
  #!/usr/bin/env node
2
- import readline from "node:readline";
3
- import { getApiKey, login } from "./auth.js";
4
- import { createAgent } from "./agent.js";
5
- import { getConfig, saveConfig } from "./config.js";
6
- import { discoverSkills } from "./skills.js";
7
- import { detectPlatform, detectShell, listAvailableShells } from "./platform/shell.js";
8
-
9
- const VERSION = "0.1.5";
10
- const models = [
11
- ["glm-5.2", "GLM-5.2"],
12
- ["glm-4.5-air", "GLM-4.5-Air"],
13
- ["glm-4.5-flash", "GLM-4.5-Flash (free)"],
14
- ["glm-z1-flash", "GLM-Z1-Flash (free)"]
15
- ];
16
-
17
- function printShell() {
18
- const p = detectPlatform(), s = detectShell();
19
- console.log(`OS: ${p.platform}${p.isTermux ? " (Termux)" : ""}`);
20
- console.log(`Architecture: ${p.arch}`);
21
- console.log(`Shell: ${s.name}`);
22
- console.log(`Executable: ${s.executable}`);
23
- }
24
-
25
- function help() {
26
- console.log(`
27
- /help 帮助
28
- /models 模型列表
29
- /model <id> 切换模型
30
- /thinking 切换思考模式
31
- /auth 登录
32
- /skills Skills
33
- /shell Shell 信息
34
- /shell list 可用 Shell
35
- /pwd 当前目录
36
- /clear 清屏
37
- /exit 退出
38
- `);
39
- }
40
-
41
- async function main() {
42
- const args = process.argv.slice(2);
43
- if (args.includes("--version") || args.includes("-v")) return console.log(VERSION);
44
-
45
- if (args[0] === "auth" && args[1] === "login") {
46
- await login({ noBrowser: args.includes("--no-browser") });
47
- return;
48
- }
49
- if (args[0] === "models") {
50
- models.forEach(([id, label]) => console.log(`${id}\t${label}`));
51
- return;
52
- }
53
- if (args[0] === "shell") {
54
- if (args[1] === "list") return listAvailableShells().forEach(x => console.log(x));
55
- printShell();
56
- return;
57
- }
58
-
59
- if (!getApiKey()) await login();
60
-
61
- console.log(`GLMCode v${VERSION}`);
62
- printShell();
63
- console.log(`模型: ${getConfig().model} Skills: ${discoverSkills().length}`);
64
-
65
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: "\n> " });
66
- const agent = createAgent({ getConfig });
67
- rl.prompt();
68
-
69
- rl.on("line", async line => {
70
- const text = line.trim();
71
- if (!text) return rl.prompt();
72
- if (text === "/exit" || text === "/quit") return rl.close();
73
- if (text === "/help") { help(); return rl.prompt(); }
74
- if (text === "/clear") { console.clear(); return rl.prompt(); }
75
- if (text === "/auth") { await login(); return rl.prompt(); }
76
- if (text === "/shell") { printShell(); return rl.prompt(); }
77
- if (text === "/shell list") { listAvailableShells().forEach(x => console.log(x)); return rl.prompt(); }
78
- if (text === "/skills") { discoverSkills().forEach(s => console.log(`${s.name}\t${s.path}`)); return rl.prompt(); }
79
- if (text === "/models") { models.forEach(([id, label]) => console.log(`${id}\t${label}`)); return rl.prompt(); }
80
- if (text.startsWith("/model ")) { saveConfig({ model: text.slice(7).trim() }); console.log(`模型已切换为 ${getConfig().model}`); return rl.prompt(); }
81
- if (text === "/thinking") { saveConfig({ thinking: !getConfig().thinking }); console.log(`思考模式: ${getConfig().thinking ? "开" : "关"}`); return rl.prompt(); }
82
- try { await agent.run(text); } catch (e) { console.error(`\n错误: ${e.message}`); }
83
- rl.prompt();
84
- });
85
- rl.on("close", () => process.exit(0));
86
- }
87
-
88
- main().catch(e => { console.error(e); process.exit(1); });
2
+ import {spawnSync} from "node:child_process";
3
+ import {getApiKey,saveApiKey,clearAuth,login,maskKey} from "./auth.js";
4
+ import {loadConfig,saveConfig} from "./config.js";
5
+ import {shellInfo,availableShells} from "./shell.js";
6
+ const VERSION="0.1.6";
7
+ function help(){console.log(`GLMCode ${VERSION}
8
+ Usage: glmcode [command]
9
+ auth login [--no-browser] 检测 Key;没有则打开官方页面
10
+ auth set <API_KEY> 保存 Key 到 ~/.glmcode/auth.json
11
+ auth status 查看认证状态(Key 脱敏)
12
+ auth logout 删除本地 Key
13
+ shell 查看 OS/CPU/Shell
14
+ shell list 列出可用 Shell
15
+ run <command> 使用当前 Shell 执行命令
16
+ config 查看配置
17
+ config set <key> <value> 修改配置
18
+ skills / plugins / mcp 查看对应配置
19
+ --version 显示版本`);}
20
+ function runShell(command){const s=shellInfo(),win=process.platform==="win32";const f=win?(s.shell==="powershell"||s.shell==="pwsh"?s.executable:process.env.ComSpec||"cmd.exe"):s.executable;const a=win?(s.shell==="powershell"||s.shell==="pwsh"?["-NoProfile","-Command",command]:["/d","/s","/c",command]):["-lc",command];const r=spawnSync(f,a,{stdio:"inherit"});process.exitCode=r.status??1;}
21
+ function value(v){if(v==="true")return true;if(v==="false")return false;if(/^-?\d+(?:\.\d+)?$/.test(v))return Number(v);try{return JSON.parse(v);}catch{return v;}}
22
+ const a=process.argv.slice(2);
23
+ if(!a.length){const f=getApiKey();if(!f){console.log(`GLMCode ${VERSION}\n未检测到 API Key。运行 glmcode auth login。`);process.exitCode=1;}else{const c=loadConfig();console.log(`GLMCode ${VERSION}\nProvider: ${c.provider}\nModel: ${c.model}\nAPI Key: ${maskKey(f.apiKey)} (${f.source})\n认证检测成功。`);}}
24
+ else if(a[0]==="--version"||a[0]==="-v")console.log(VERSION);
25
+ else if(a[0]==="--help"||a[0]==="-h"||a[0]==="help")help();
26
+ else if(a[0]==="auth"){const s=a[1]||"status";if(s==="login")login({noBrowser:a.includes("--no-browser")});else if(s==="set"){if(!a[2]){console.error("用法:glmcode auth set <API_KEY>");process.exitCode=2;}else console.log(`API Key 已保存到 ${saveApiKey(a[2])}`);}else if(s==="status"){const f=getApiKey();console.log(f?`API Key: ${maskKey(f.apiKey)}\n来源: ${f.source}`:"未检测到 API Key。");}else if(s==="logout"){clearAuth();console.log("本地 API Key 已删除。");}else help();}
27
+ else if(a[0]==="shell"){if(a[1]==="list")console.log(availableShells().join("\n")||"未检测到额外 Shell");else{const s=shellInfo();console.log(`OS: ${s.os}\nArchitecture: ${s.architecture}\nShell: ${s.shell}\nExecutable: ${s.executable}`);}}
28
+ else if(a[0]==="run"){if(!a[1]){console.error("用法:glmcode run <command>");process.exitCode=2;}else runShell(a.slice(1).join(" "));}
29
+ else if(a[0]==="config"){const c=loadConfig();if(a[1]==="set"){if(!a[2]||a[3]===undefined){console.error("用法:glmcode config set <key> <value>");process.exitCode=2;}else{c[a[2]]=value(a.slice(3).join(" "));saveConfig(c);console.log(`已设置 ${a[2]}`);}}else console.log(JSON.stringify(c,null,2));}
30
+ else if(["skills","plugins","mcp"].includes(a[0]))console.log(JSON.stringify(loadConfig()[a[0]],null,2));
31
+ else{console.error(`未知命令:${a[0]}`);help();process.exitCode=2;}
package/src/config.js CHANGED
@@ -1,17 +1,8 @@
1
- import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
- import { homedir } from "node:os";
3
- import { join } from "node:path";
4
-
5
- const dir = join(homedir(), ".glmcode");
6
- const file = join(dir, "config.json");
7
-
8
- export function getConfig() {
9
- try { return { model: "glm-4.5-air", thinking: false, ...JSON.parse(readFileSync(file, "utf8")) }; }
10
- catch { return { model: "glm-4.5-air", thinking: false }; }
11
- }
12
- export function saveConfig(patch) {
13
- mkdirSync(dir, { recursive: true });
14
- const next = { ...getConfig(), ...patch };
15
- writeFileSync(file, JSON.stringify(next, null, 2));
16
- return next;
17
- }
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ export const CONFIG_DIR=path.join(os.homedir(),".glmcode");
5
+ export const CONFIG_FILE=path.join(CONFIG_DIR,"config.json");
6
+ const defaults={provider:"zhipu",model:"glm-4.5",freeModel:"glm-4.5-air",baseUrl:"https://open.bigmodel.cn/api/paas/v4",maxTokens:4096,temperature:0.7,thinking:true,approval:"ask",autoCompact:true,skillsDirs:[],plugins:[],mcp:{},startupCommand:"",autoOpenLogin:true};
7
+ export function loadConfig(){try{return {...defaults,...JSON.parse(fs.readFileSync(CONFIG_FILE,"utf8"))};}catch{return {...defaults};}}
8
+ export function saveConfig(c){fs.mkdirSync(CONFIG_DIR,{recursive:true,mode:0o700});fs.writeFileSync(CONFIG_FILE,JSON.stringify(c,null,2)+"\n",{mode:0o600});}
package/src/shell.js ADDED
@@ -0,0 +1,5 @@
1
+ import os from "node:os";
2
+ import {execFileSync} from "node:child_process";
3
+ function shell(){if(process.platform==="win32"){const p=process.env.PWSH||process.env.POWERSHELL;if(p)return{name:"powershell",executable:p};return{name:"cmd",executable:process.env.ComSpec||"cmd.exe"};}const e=process.env.SHELL||"/bin/sh";return{name:e.split("/").pop()||"sh",executable:e};}
4
+ export function shellInfo(){const s=shell();return{os:process.platform,architecture:process.arch,shell:s.name,executable:s.executable};}
5
+ export function availableShells(){const c=process.platform==="win32"?["cmd","powershell","pwsh"]:["sh","bash","zsh","fish","ash"];return c.filter(n=>{try{execFileSync(process.platform==="win32"?"where":"which",[n],{stdio:"ignore"});return true;}catch{return false;}});}
package/glmcode-0.1.4.tgz DELETED
Binary file
package/glmcode-0.1.5.tgz DELETED
Binary file
package/src/agent.js DELETED
@@ -1,46 +0,0 @@
1
- import { getApiKey } from "./auth.js";
2
- import { discoverSkills } from "./skills.js";
3
-
4
- const endpoint = "https://open.bigmodel.cn/api/paas/v4/chat/completions";
5
-
6
- export function createAgent({ getConfig }) {
7
- return { async run(input) {
8
- const key = getApiKey();
9
- if (!key) throw new Error("没有可用的 GLM API Key,请运行 glmcode auth login。");
10
- const cfg = getConfig();
11
- const skillText = discoverSkills().map(s => `## Skill: ${s.name}\n${s.content}`).join("\n\n");
12
- const res = await fetch(endpoint, {
13
- method: "POST",
14
- headers: { "Content-Type": "application/json", "Authorization": `Bearer ${key}` },
15
- body: JSON.stringify({
16
- model: cfg.model,
17
- messages: [
18
- { role: "system", content: `You are GLMCode, a coding assistant.\n${skillText}` },
19
- { role: "user", content: input }
20
- ],
21
- stream: true
22
- })
23
- });
24
- if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
25
- const reader = res.body.getReader();
26
- const decoder = new TextDecoder();
27
- let buffer = "";
28
- while (true) {
29
- const { value, done } = await reader.read();
30
- if (done) break;
31
- buffer += decoder.decode(value, { stream: true });
32
- const lines = buffer.split("\n");
33
- buffer = lines.pop() || "";
34
- for (const line of lines) {
35
- if (!line.startsWith("data:")) continue;
36
- const data = line.slice(5).trim();
37
- if (data === "[DONE]") continue;
38
- try {
39
- const delta = JSON.parse(data).choices?.[0]?.delta?.content;
40
- if (delta) process.stdout.write(delta);
41
- } catch {}
42
- }
43
- }
44
- process.stdout.write("\n");
45
- }};
46
- }
package/src/mcp.js DELETED
@@ -1,13 +0,0 @@
1
- import { readFileSync, mkdirSync, writeFileSync } from "node:fs";
2
- import { homedir } from "node:os";
3
- import { join } from "node:path";
4
-
5
- const file = join(homedir(), ".glmcode", "mcp.json");
6
- export function loadMcp() {
7
- try { return JSON.parse(readFileSync(file, "utf8")); }
8
- catch { return { mcpServers: {} }; }
9
- }
10
- export function saveMcp(config) {
11
- mkdirSync(join(homedir(), ".glmcode"), { recursive: true });
12
- writeFileSync(file, JSON.stringify(config, null, 2));
13
- }
@@ -1,30 +0,0 @@
1
- import { execFile } from "node:child_process";
2
- import { detectPlatform } from "./shell.js";
3
-
4
- function tryRun(command, args) {
5
- return new Promise(resolve => {
6
- const child = execFile(command, args, { windowsHide: true }, error => resolve(!error));
7
- child.on("error", () => resolve(false));
8
- });
9
- }
10
-
11
- export async function openBrowser(url) {
12
- const p = detectPlatform();
13
-
14
- if (process.env.GLMCODE_NO_BROWSER === "1") {
15
- console.log(`\n浏览器自动打开已禁用。\n请手动打开:\n${url}\n`);
16
- return false;
17
- }
18
-
19
- if (p.platform === "win32") {
20
- if (await tryRun("cmd.exe", ["/c", "start", "", url])) return true;
21
- } else if (p.platform === "darwin") {
22
- if (await tryRun("open", [url])) return true;
23
- } else {
24
- if (p.isTermux && await tryRun("termux-open-url", [url])) return true;
25
- if (await tryRun("xdg-open", [url])) return true;
26
- }
27
-
28
- console.log(`\n当前环境没有可用的浏览器启动器。\n请在浏览器中打开:\n${url}\n`);
29
- return false;
30
- }
@@ -1,64 +0,0 @@
1
- import os from "node:os";
2
- import { execFileSync } from "node:child_process";
3
-
4
- function exists(command) {
5
- try {
6
- if (process.platform === "win32") {
7
- execFileSync("where", [command], { stdio: "ignore" });
8
- } else {
9
- execFileSync("sh", ["-lc", `command -v ${JSON.stringify(command)}`], { stdio: "ignore" });
10
- }
11
- return true;
12
- } catch { return false; }
13
- }
14
-
15
- export function detectPlatform() {
16
- return {
17
- platform: process.platform,
18
- arch: process.arch,
19
- isTermux: Boolean(process.env.TERMUX_VERSION || process.env.PREFIX?.includes("com.termux")),
20
- release: os.release()
21
- };
22
- }
23
-
24
- export function detectShell() {
25
- const p = detectPlatform();
26
- if (p.platform === "win32") {
27
- if (process.env.PSModulePath && exists("pwsh")) return { name: "PowerShell", executable: "pwsh" };
28
- if (process.env.PSModulePath) return { name: "PowerShell", executable: "powershell.exe" };
29
- return { name: "CMD", executable: process.env.ComSpec || "cmd.exe" };
30
- }
31
-
32
- const envShell = process.env.SHELL || "";
33
- const name = envShell.split("/").pop();
34
- if (["bash", "zsh", "fish", "sh", "dash", "ksh", "nu"].includes(name) && exists(name)) {
35
- return { name, executable: envShell };
36
- }
37
-
38
- for (const candidate of ["bash", "zsh", "fish", "sh"]) {
39
- if (exists(candidate)) return { name: candidate, executable: candidate };
40
- }
41
- return { name: "sh", executable: "/bin/sh" };
42
- }
43
-
44
- export function listAvailableShells() {
45
- if (process.platform === "win32") {
46
- return [
47
- ...(exists("pwsh") ? ["powershell"] : []),
48
- ...(exists("powershell.exe") ? ["powershell.exe"] : []),
49
- "cmd.exe",
50
- ...(exists("bash.exe") ? ["bash.exe"] : [])
51
- ];
52
- }
53
- return ["sh", ...(exists("bash") ? ["bash"] : []), ...(exists("zsh") ? ["zsh"] : []), ...(exists("fish") ? ["fish"] : [])];
54
- }
55
-
56
- export function shellCommand(command, shell = detectShell().executable) {
57
- if (process.platform === "win32" && /powershell|pwsh/i.test(shell)) {
58
- return { file: shell, args: ["-NoLogo", "-NoProfile", "-Command", command] };
59
- }
60
- if (process.platform === "win32" && /cmd/i.test(shell)) {
61
- return { file: shell, args: ["/d", "/s", "/c", command] };
62
- }
63
- return { file: shell, args: ["-lc", command] };
64
- }
package/src/skills.js DELETED
@@ -1,17 +0,0 @@
1
- import { existsSync, readdirSync, readFileSync } from "node:fs";
2
- import { join } from "node:path";
3
- import { homedir } from "node:os";
4
-
5
- export function discoverSkills() {
6
- const roots = [join(process.cwd(), ".glmcode", "skills"), join(homedir(), ".glmcode", "skills")];
7
- const out = [];
8
- for (const root of roots) {
9
- if (!existsSync(root)) continue;
10
- for (const e of readdirSync(root, { withFileTypes: true })) {
11
- if (!e.isDirectory()) continue;
12
- const p = join(root, e.name, "SKILL.md");
13
- if (existsSync(p)) out.push({ name: e.name, path: p, content: readFileSync(p, "utf8") });
14
- }
15
- }
16
- return out;
17
- }
package/src/tools.js DELETED
@@ -1,14 +0,0 @@
1
- import { promises as fs } from "node:fs";
2
- import { execFile } from "node:child_process";
3
- import { detectShell, shellCommand } from "./platform/shell.js";
4
-
5
- export async function readFile(path) { return fs.readFile(path, "utf8"); }
6
- export async function writeFile(path, content) { await fs.writeFile(path, content, "utf8"); return `wrote ${path}`; }
7
-
8
- export async function runShell(command, cwd = process.cwd()) {
9
- const s = detectShell(), spec = shellCommand(command, s.executable);
10
- return await new Promise((resolve, reject) => {
11
- execFile(spec.file, spec.args, { cwd, maxBuffer: 10 * 1024 * 1024 },
12
- (error, stdout, stderr) => error ? reject(new Error(stderr || error.message)) : resolve({ stdout, stderr, code: 0, shell: s.name }));
13
- });
14
- }