@brierb/brier-cli 0.0.6 → 0.0.7

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/dist/config.js CHANGED
@@ -1,9 +1,8 @@
1
1
  import { hostname as getHostname, type as osType, arch, platform, homedir } from 'node:os';
2
- import { execSync } from 'node:child_process';
3
- import { accessSync, constants, readFileSync } from 'node:fs';
2
+ import { readFileSync } from 'node:fs';
4
3
  import { join } from 'node:path';
5
4
  import { fileURLToPath } from 'node:url';
6
- import { RUNTIME_REGISTRY } from './runtimes.js';
5
+ import { resolveRuntimeExecutable, RUNTIME_REGISTRY } from './runtimes.js';
7
6
  export const BRIER_DIR = join(homedir(), '.brier');
8
7
  export const PID_FILE = join(BRIER_DIR, 'daemon.pid');
9
8
  export const LOG_FILE = join(BRIER_DIR, 'daemon.log');
@@ -42,40 +41,5 @@ export const toWsUrl = (serverUrl) => {
42
41
  .replace(/^http:\/\//, 'ws://')
43
42
  .replace(/\/$/, '') + '/tunnel');
44
43
  };
45
- // macOS 自带磁盘分区工具 gpt(8) 位于 /usr/sbin/gpt,会与 OpenAI 的 gpt CLI 同名,
46
- // macOS 自带磁盘分区工具 gpt(8) 位于 /usr/sbin/gpt,会与 OpenAI CLI 同名;
47
- // 命中 /sbin、/usr/sbin 视为「未安装」,避免误报。
48
- const SYSTEM_SBIN_RE = /^\/(usr\/)?sbin\//;
49
- // shell 内建命令(如 continue、cd、type)会被 `command -v` 返回为无路径的裸名,
50
- // 不是真实可执行文件,需排除。
51
- const isRealExecutablePath = (hit) => Boolean(hit) && hit.includes('/') && !SYSTEM_SBIN_RE.test(hit);
52
- const isExecutable = (file) => {
53
- try {
54
- accessSync(file, constants.X_OK);
55
- return true;
56
- }
57
- catch {
58
- return false;
59
- }
60
- };
61
- const expandHome = (p) => (p.startsWith('~/') ? join(homedir(), p.slice(2)) : p);
62
- const detectRuntimes = () => {
63
- const runtimes = [];
64
- for (const { name, command, fallbacks } of RUNTIME_REGISTRY) {
65
- let found = false;
66
- try {
67
- const hit = execSync(`command -v ${command}`, { stdio: 'pipe' }).toString().trim();
68
- found = isRealExecutablePath(hit);
69
- }
70
- catch {
71
- // 不在 PATH
72
- }
73
- if (!found) {
74
- const candidates = fallbacks ?? [`~/.local/bin/${command}`];
75
- found = candidates.some((p) => isExecutable(expandHome(p)));
76
- }
77
- if (found)
78
- runtimes.push(name);
79
- }
80
- return runtimes;
81
- };
44
+ /** 探测本机已安装的 AI runtime(与执行共用同一可执行文件解析)。 */
45
+ const detectRuntimes = () => RUNTIME_REGISTRY.filter((r) => resolveRuntimeExecutable(r.name) !== null).map((r) => r.name);
@@ -1,14 +1,19 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import { logger } from '../logger.js';
3
- import { RUNTIME_COMMANDS, RUNTIME_PROMPT_FLAGS } from '../runtimes.js';
3
+ import { resolveRuntimeExecutable, RUNTIME_PROMPT_FLAGS } from '../runtimes.js';
4
4
  const MAX_CONCURRENT = 3;
5
5
  export const createTaskExecutor = (callbacks) => {
6
6
  const processes = new Map();
7
+ /**
8
+ * 解析要执行的命令:
9
+ * - 显式 command:直接用(调用方负责其可执行性)
10
+ * - runtime 模式:解析为**绝对路径**(PATH 或官方安装目录),
11
+ * 避免 daemon 进程 PATH 不含 runtime 目录时 spawn ENOENT
12
+ */
7
13
  const resolveCommand = (task) => {
8
14
  if (task.command)
9
15
  return task.command;
10
- const mapped = RUNTIME_COMMANDS[task.runtime];
11
- return mapped ?? null;
16
+ return resolveRuntimeExecutable(task.runtime);
12
17
  };
13
18
  /**
14
19
  * 拼执行参数:
package/dist/runtimes.js CHANGED
@@ -1,3 +1,7 @@
1
+ import { execSync } from 'node:child_process';
2
+ import { accessSync, constants } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { join } from 'node:path';
1
5
  export const RUNTIME_REGISTRY = [
2
6
  { name: 'Claude Code', command: 'claude', fallbacks: ['~/.claude/bin/claude'] },
3
7
  { name: 'Codex CLI', command: 'codex' },
@@ -20,10 +24,45 @@ export const RUNTIME_COMMANDS = Object.fromEntries(RUNTIME_REGISTRY.map((r) => [
20
24
  * `spawn(<command>, [...flags, prompt])`;未列出的 runtime 按裸参数执行。
21
25
  */
22
26
  export const RUNTIME_PROMPT_FLAGS = {
23
- 'OpenCode': ['run'],
27
+ OpenCode: ['run'],
24
28
  'Claude Code': ['-p'],
25
29
  'Codex CLI': ['exec'],
26
30
  'Gemini CLI': ['-p'],
27
- 'Aider': ['--message'],
28
- 'Goose': ['run'],
31
+ Aider: ['--message'],
32
+ Goose: ['run'],
33
+ };
34
+ // macOS 自带磁盘分区工具 gpt(8) 位于 /usr/sbin/gpt,命中系统目录视为未安装。
35
+ const SYSTEM_SBIN_RE = /^\/(usr\/)?sbin\//;
36
+ const isExecutable = (file) => {
37
+ try {
38
+ accessSync(file, constants.X_OK);
39
+ return true;
40
+ }
41
+ catch {
42
+ return false;
43
+ }
44
+ };
45
+ const expandHome = (p) => (p.startsWith('~/') ? join(homedir(), p.slice(2)) : p);
46
+ /**
47
+ * 解析 runtime 的可执行文件**绝对路径**:
48
+ * 1. PATH 中查找(排除系统目录误报,如 /usr/sbin/gpt)
49
+ * 2. 未命中则检查官方安装目录(如 opencode → ~/.opencode/bin/opencode)
50
+ *
51
+ * 探测(runtimes 上报)与执行(spawn 任务)都走这里,保证"探测到"的
52
+ * runtime 一定可被执行,不依赖 daemon 进程的 PATH。
53
+ */
54
+ export const resolveRuntimeExecutable = (runtime) => {
55
+ const entry = RUNTIME_REGISTRY.find((r) => r.name === runtime);
56
+ if (!entry)
57
+ return null;
58
+ try {
59
+ const hit = execSync(`command -v ${entry.command}`, { stdio: 'pipe' }).toString().trim();
60
+ if (hit.includes('/') && !SYSTEM_SBIN_RE.test(hit))
61
+ return hit;
62
+ }
63
+ catch {
64
+ // 不在 PATH
65
+ }
66
+ const candidates = entry.fallbacks ?? [`~/.local/bin/${entry.command}`];
67
+ return candidates.map(expandHome).find(isExecutable) ?? null;
29
68
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brierb/brier-cli",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "Brier 平台接入命令行工具:通过加密隧道将本机接入 Brier 远程编码池,接收并执行远程任务",
5
5
  "keywords": [
6
6
  "brier",
@@ -12,3 +12,12 @@ export declare const RUNTIME_COMMANDS: Record<string, string>;
12
12
  * `spawn(<command>, [...flags, prompt])`;未列出的 runtime 按裸参数执行。
13
13
  */
14
14
  export declare const RUNTIME_PROMPT_FLAGS: Record<string, readonly string[]>;
15
+ /**
16
+ * 解析 runtime 的可执行文件**绝对路径**:
17
+ * 1. PATH 中查找(排除系统目录误报,如 /usr/sbin/gpt)
18
+ * 2. 未命中则检查官方安装目录(如 opencode → ~/.opencode/bin/opencode)
19
+ *
20
+ * 探测(runtimes 上报)与执行(spawn 任务)都走这里,保证"探测到"的
21
+ * runtime 一定可被执行,不依赖 daemon 进程的 PATH。
22
+ */
23
+ export declare const resolveRuntimeExecutable: (runtime: string) => string | null;