nx-ce 0.1.8 → 0.2.1

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
@@ -3,13 +3,13 @@
3
3
  [![npm version](https://img.shields.io/npm/v/nx-ce)](https://www.npmjs.com/package/nx-ce)
4
4
  [![CI](https://github.com/joke-lx/nx-ce/actions/workflows/npm-publish.yml/badge.svg)](https://github.com/joke-lx/nx-ce/actions/workflows/npm-publish.yml)
5
5
 
6
- **nx-ce** is a lightweight Node.js adapter for `@anthropic-ai/claude-agent-sdk`. It provides two modes:
6
+ **nx-ce** is a lightweight Node.js adapter for `@anthropic-ai/claude-agent-sdk`. As of **v0.2**, it provides a single mode:
7
7
 
8
- - **`nx-ce query`** — one-shot cold-start queries (stateless, CLI-friendly)
9
- - **`nx-ce serve`** — WebSocket multi-session server (persistent, concurrent clients)
8
+ - **`nx-ce serve`** — WebSocket multi-session server (persistent, concurrent clients). All consumers (CLI scripts, Chrome extensions, native_host) connect via this single WS endpoint.
10
9
 
11
- **nx-ce** 是一个轻量级 Node.js 适配器,封装了 `@anthropic-ai/claude-agent-sdk`。支持两种运行模式:
12
- 一次性冷启动查询与多会话 WebSocket 持久化服务器。
10
+ **nx-ce** 是一个轻量级 Node.js 适配器,封装了 `@anthropic-ai/claude-agent-sdk`。
11
+ **v0.2 起只提供一种模式**:`nx-ce serve` 启动 WebSocket 多会话服务器。
12
+ 所有调用方(CLI 脚本 / Chrome 扩展 / native_host)都通过这个唯一的 WS 端点与 SDK 通信。
13
13
 
14
14
  ---
15
15
 
@@ -35,47 +35,16 @@ npm install -g nx-ce
35
35
  ## Quick Start / 快速开始
36
36
 
37
37
  ```bash
38
- # One-shot query (stateless)
39
- nx-ce query "用中文回答:1+1=?" --model claude-haiku-4-5
40
-
41
- # Start WebSocket server (persistent, multi-session)
38
+ # Start WebSocket server (the only mode)
42
39
  nx-ce serve --port 3100
43
40
 
44
41
  # In another terminal, run tests
45
42
  node test/serve-test.mjs
46
43
  ```
47
44
 
48
- ---
49
-
50
- ## `nx-ce query` — One-Shot Cold-Start Query / 一次性冷启动查询
51
-
52
- ```bash
53
- nx-ce query "解释这段代码" --model claude-sonnet-4-6
54
- nx-ce query "继续之前的对话" --resume sess_abc123
55
- nx-ce query "Analyze" --skill git-workflow,code-review
56
- nx-ce query "Analyze" --skill all
57
- ```
58
-
59
- | Flag | Description / 说明 |
60
- |------|-------------------|
61
- | `--model <id>` | Model override (default `claude-sonnet-4-6`) / 模型 ID |
62
- | `--claude-path <path>` | Path to Claude CLI binary / Claude CLI 路径 |
63
- | `--system-prompt <text>` | System prompt override / 系统提示词覆盖 |
64
- | `--resume <sessionId>` | Resume a prior session (long conversation) / 续接会话 |
65
- | `--skill <name>[,<name>...]` | Load specific skills (comma-separated, or `all`) / 加载 Skill |
66
- | `--include-metadata` | Include skills/tools/slashCommands in output / 附带元数据 |
67
- | `--no-persist` | Don't persist session / 不持久化 |
68
- | `--env "KEY=val,KEY2=val"` | Extra environment variables / 额外环境变量 |
69
-
70
- ### JSON output
45
+ > **v0.2 breaking change**: `nx-ce query` and `nx-ce skills` CLI subcommands removed. All consumers must use the WebSocket protocol. See the [Protocol](#websocket-protocol--websocket-协议) section below.
71
46
 
72
- ```json
73
- // Default
74
- { "text": "2", "sessionId": "sess_abc" }
75
-
76
- // With --include-metadata
77
- { "text": "2", "sessionId": "sess_abc", "metadata": { "skills": [...], "tools": [...], ... } }
78
- ```
47
+ > **v0.2 破坏性变更**:移除 `nx-ce query` 和 `nx-ce skills` 子命令。所有调用方必须使用 WebSocket 协议。
79
48
 
80
49
  ---
81
50
 
@@ -298,21 +267,6 @@ Each session has its own `agentQuery()`, `MessageChannel`, `MonotonicClock`, and
298
267
 
299
268
  ---
300
269
 
301
- ## `nx-ce skills` — List Available Skills / 列出可用 Skill
302
-
303
- ```bash
304
- nx-ce skills --cwd "D:/project"
305
- ```
306
-
307
- ```json
308
- { "skills": ["code-review", "browse", ...],
309
- "tools": ["Read", "Edit", "Bash", ...],
310
- "slashCommands": ["code-review", ...],
311
- "agents": ["Explore", ...] }
312
- ```
313
-
314
- ---
315
-
316
270
  ## State Persistence / 状态持久化
317
271
 
318
272
  State files at `~/.nx-ce/instances/{key}.json`. Key format: `{name}~{cwd}`.
@@ -343,9 +297,6 @@ State files at `~/.nx-ce/instances/{key}.json`. Key format: `{name}~{cwd}`.
343
297
  ## Development / 开发
344
298
 
345
299
  ```bash
346
- # One-shot query
347
- node ./bin/nx-ce.js query "你好"
348
-
349
300
  # Start server
350
301
  node ./bin/nx-ce.js serve --port 3100
351
302
 
package/bin/nx-ce.js CHANGED
@@ -1,12 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * nx-ce — Claude Engine
4
+ * nx-ce — Claude Engine (v0.2 serve-only)
5
5
  *
6
- * CLI entry point. Routes to subcommands:
7
- * nx-ce query "prompt" — one-shot cold-start query
8
- * nx-ce serve — persistent manager process (stdin/stdout protocol)
9
- * nx-ce status show instance state
6
+ * Single entry point all consumers (CLI / Chrome extension / native_host)
7
+ * talk to the WebSocket server via the unified protocol.
8
+ *
9
+ * nx-ce serve [--port 3100] start WebSocket server
10
+ * nx-ce status — list instance states
11
+ * nx-ce help — show help
10
12
  */
11
13
 
12
14
  import { runCli } from '../src/cli.js';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nx-ce",
3
- "version": "0.1.8",
4
- "description": "Claude Engine — SDK adapter layer for native messaging host. Bridges @anthropic-ai/claude-agent-sdk calls over a length-prefixed JSON protocol.",
3
+ "version": "0.2.1",
4
+ "description": "Claude Engine — WebSocket adapter for @anthropic-ai/claude-agent-sdk. Single serve mode serves all consumers (CLI / Chrome extension / native_host) over a unified JSON-over-WebSocket protocol.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
7
7
  "bin": {
@@ -17,8 +17,8 @@
17
17
  },
18
18
  "scripts": {
19
19
  "nx-ce": "node ./bin/nx-ce.js",
20
- "test": "node test/basic.js",
21
- "start": "node ./bin/nx-ce.js"
20
+ "test": "node test/serve-test.mjs",
21
+ "start": "node ./bin/nx-ce.js serve"
22
22
  },
23
23
  "keywords": [
24
24
  "claude",
@@ -26,7 +26,8 @@
26
26
  "anthropic",
27
27
  "sdk",
28
28
  "native-messaging",
29
- "chrome-extension"
29
+ "chrome-extension",
30
+ "websocket"
30
31
  ],
31
32
  "license": "MIT",
32
33
  "dependencies": {
package/src/cli.js CHANGED
@@ -1,14 +1,15 @@
1
1
  /**
2
- * CLI — 子命令路由器
2
+ * CLI — 子命令路由器(v0.2 起仅 serve 模式)
3
3
  *
4
- * 模式:nx-sx/src/cli.js
5
- * 路由到 query | serve | status | help
4
+ * 历史:
5
+ * - 旧版有 query(冷启动)/ skills(独立拉元数据)子命令
6
+ * - v0.2 起所有调用统一收敛到 serve:所有元数据通过 getSkills 消息获取
7
+ *
8
+ * 路由: serve | status | help
6
9
  */
7
10
 
8
11
  import { existsSync } from 'node:fs';
9
- import { runQuery } from './query.js';
10
12
  import { startServe } from './serve.js';
11
- import { listSkills } from './skills.js';
12
13
  import { readState, listStates } from './session-store.js';
13
14
 
14
15
  /**
@@ -19,11 +20,9 @@ import { readState, listStates } from './session-store.js';
19
20
  * @returns {{ cmd: string, flags: object, args: string[] }}
20
21
  */
21
22
  export function parseArgs(argv = process.argv.slice(2)) {
22
- const cmd = argv[0]; // 第一个参数为子命令
23
+ const cmd = argv[0];
23
24
  const rest = argv.slice(1);
24
25
 
25
- // 解析 --key=value 或 --key value 选项
26
- // positional 只收集非 -- 开头的参数
27
26
  const flags = {};
28
27
  const positional = [];
29
28
  for (let i = 0; i < rest.length; i++) {
@@ -31,13 +30,10 @@ export function parseArgs(argv = process.argv.slice(2)) {
31
30
  if (arg.startsWith('--')) {
32
31
  const eqIdx = arg.indexOf('=');
33
32
  if (eqIdx !== -1) {
34
- // --key=value 格式
35
33
  flags[arg.slice(2, eqIdx)] = arg.slice(eqIdx + 1);
36
34
  } else if (rest[i + 1] === undefined || rest[i + 1].startsWith('--')) {
37
- // --key 后面没有值,或下一个参数也是 flag → boolean flag
38
35
  flags[arg.slice(2)] = true;
39
36
  } else {
40
- // --key value 格式(下一个参数作为值)
41
37
  flags[arg.slice(2)] = rest[++i] ?? true;
42
38
  }
43
39
  } else {
@@ -50,42 +46,14 @@ export function parseArgs(argv = process.argv.slice(2)) {
50
46
 
51
47
  /**
52
48
  * 运行 CLI 入口。
53
- * 根据子命令分发到对应的处理函数。
54
49
  */
55
50
  export async function runCli() {
56
- const { cmd, flags, args } = parseArgs();
51
+ const { cmd, flags } = parseArgs();
57
52
 
58
53
  switch (cmd) {
59
- case 'query': {
60
- // 冷启动查询
61
- const prompt = args[0] || flags.prompt;
62
- if (!prompt) {
63
- throw new Error('用法: nx-ce query <prompt> [--model ...] [--claude-path ...]');
64
- }
65
-
66
- const result = await runQuery({
67
- prompt,
68
- model: flags.model,
69
- cwd: flags.cwd || process.cwd(),
70
- claudePath: resolveClaudePath(flags['claude-path']),
71
- systemPrompt: flags['system-prompt'],
72
- persistSession: flags['no-persist'] ? false : undefined,
73
- resumeSessionId: flags.resume,
74
- skills: parseSkills(flags.skill),
75
- env: flags.env ? parseEnvString(flags.env) : undefined,
76
- });
77
-
78
- // 默认只返回 text + sessionId,加 --include-metadata 才返回 metadata
79
- if (flags['include-metadata']) {
80
- return result;
81
- }
82
- return { text: result.text, sessionId: result.sessionId };
83
- }
84
-
85
54
  case 'serve': {
86
- // WebSocket 持久化服务模式
55
+ // WebSocket 持久化服务模式(唯一数据通路)
87
56
  const name = flags.name || 'default';
88
-
89
57
  const result = await startServe({
90
58
  name,
91
59
  claudePath: resolveClaudePath(flags['claude-path']),
@@ -94,57 +62,41 @@ export async function runCli() {
94
62
  env: flags.env ? parseEnvString(flags.env) : undefined,
95
63
  port: flags.port ? parseInt(flags.port, 10) : undefined,
96
64
  });
97
-
98
65
  return result;
99
66
  }
100
67
 
101
68
  case 'status': {
102
- // 查询实例状态
103
69
  const name = flags.name;
104
70
  if (name) {
105
- return readState(name); // 查询指定实例
71
+ return readState(name);
106
72
  }
107
- return listStates(); // 列出所有实例
108
- }
109
-
110
- case 'skills': {
111
- const result = await listSkills({
112
- cwd: flags.cwd || process.cwd(),
113
- claudePath: resolveClaudePath(flags['claude-path']),
114
- env: flags.env ? parseEnvString(flags.env) : undefined,
115
- });
116
- return result;
73
+ return listStates();
117
74
  }
118
75
 
119
76
  case 'help':
77
+ case '--help':
78
+ case '-h':
120
79
  default:
121
- // 显示帮助信息
122
80
  console.log(`
123
- nx-ce — Claude Engine
81
+ nx-ce — Claude Engine (v0.2: serve-only)
124
82
 
125
83
  用法:
126
- nx-ce query <prompt> 一次性冷启动查询
127
- --model <id> 模型覆盖
128
- --claude-path <path> Claude CLI 路径
129
- --system-prompt <text> 系统提示词覆盖
130
- --resume <sessionId> 续接之前的会话(长对话)
131
- --skill <name>[,<name>...] 加载指定 Skill(逗号分隔,传 "all" 加载全部)
132
- --include-metadata 输出中附带 skills/tools/slash_commands 列表
133
- --no-persist 不持久化会话
134
- --env "KEY=value,KEY2=val" 额外环境变量
135
-
136
- nx-ce serve WebSocket 持久化服务器(单一进程 / 多客户端)
84
+ nx-ce serve WebSocket 持久化服务器(唯一入口)
137
85
  --name <name> 实例名称(默认: "default")
138
86
  --port <port> WebSocket 端口(默认: 3100)
139
87
  --model <id> 模型覆盖
140
88
  --claude-path <path> Claude CLI 路径
89
+ --cwd <path> 默认工作目录
141
90
  --env "KEY=value,..." 额外环境变量
142
91
 
143
92
  nx-ce status [--name <name>] 查看实例状态
144
93
 
145
- nx-ce skills [--cwd <path>] 列出 SDK 可用 skill/tool/agent
146
-
147
94
  nx-ce help 显示此帮助
95
+
96
+ 协议(ws://127.0.0.1:3100):
97
+ C→S: query / getSkills / getStatus / listSessions / closeSession / ping
98
+ S→C: connected / init / turn_start / text / thinking / tool_use / done /
99
+ error / pong / skills / status / session_list / session_closed
148
100
  `);
149
101
  return null;
150
102
  }
@@ -152,32 +104,18 @@ export async function runCli() {
152
104
 
153
105
  /**
154
106
  * 解析 Claude CLI 路径。
155
- * 优先使用命令行参数,然后检查环境变量。
156
- *
157
- * @param {string|undefined} flag - 命令行传入的路径
158
- * @returns {string|undefined} 解析后的路径,未找到则返回 undefined(由 SDK 自动检测)
159
107
  */
160
108
  function resolveClaudePath(flag) {
161
109
  if (flag) return flag;
162
- // 常见位置
163
- const candidates = [
164
- process.env.CLAUDE_PATH,
165
- process.env.CLAUDE_CLI_PATH,
166
- // Windows: npx、npm 全局或用户本地安装
167
- ];
110
+ const candidates = [process.env.CLAUDE_PATH, process.env.CLAUDE_CLI_PATH];
168
111
  for (const c of candidates) {
169
112
  if (c && existsSync(c)) return c;
170
113
  }
171
- // 未找到,让 SDK 自动检测
172
114
  return undefined;
173
115
  }
174
116
 
175
117
  /**
176
- * 解析环境变量字符串为对象。
177
- * 格式: "KEY=value,KEY2=val2"
178
- *
179
- * @param {string} str - 逗号分隔的 KEY=value 对
180
- * @returns {object}
118
+ * 解析环境变量字符串 "KEY=val,KEY2=val2"
181
119
  */
182
120
  function parseEnvString(str) {
183
121
  const result = {};
@@ -189,13 +127,3 @@ function parseEnvString(str) {
189
127
  }
190
128
  return result;
191
129
  }
192
-
193
- /**
194
- * 解析 --skill 参数。
195
- * 逗号分隔的列表 → 数组;"all" → "all"(由 SDK 处理)。
196
- */
197
- function parseSkills(value) {
198
- if (value === undefined || value === null || value === '') return undefined;
199
- if (value === 'all' || value === 'ALL') return 'all';
200
- return value.split(',').map((s) => s.trim()).filter(Boolean);
201
- }
package/src/index.js CHANGED
@@ -2,12 +2,14 @@
2
2
  * nx-ce — 公开 API 入口
3
3
  *
4
4
  * 用法:
5
- * import { runQuery } from 'nx-ce';
6
- * const { text, sessionId } = await runQuery({ prompt: 'hello', ... });
5
+ * import { startServe, readState } from 'nx-ce';
6
+ * await startServe({ name: 'main', port: 3100 });
7
+ *
8
+ * v0.2 起:nx-ce 仅提供 WebSocket serve 模式,不再有冷启动 query。
9
+ * 所有调用方(CLI / Chrome 扩展 / native_host)都通过 WS 协议与 serve 通信。
7
10
  */
8
11
 
9
- export { runQuery } from './query.js';
10
- export { listSkills } from './skills.js';
12
+ export { startServe } from './serve.js';
11
13
  export {
12
14
  readState,
13
15
  writeState,
package/src/serve.js CHANGED
@@ -515,19 +515,43 @@ export async function startServe(options) {
515
515
  break;
516
516
 
517
517
  case 'getSkills': {
518
- // (name, cwd) 查 session
519
- const key = sessionKey(sessionName, req.cwd);
520
- const session = sessionManager.sessions.get(key);
521
- if (session?.metadata) {
522
- ws.send(JSON.stringify(session.metadata));
518
+ // 解析目标 session
519
+ // 带 session/cwd 取该 session 的元数据
520
+ // 不带 → 取任意已 init 的 session 的元数据(server 级)
521
+ let meta = null;
522
+ if (req.session || req.cwd) {
523
+ const key = sessionKey(sessionName, req.cwd);
524
+ const session = sessionManager.sessions.get(key);
525
+ meta = session?.metadata;
526
+ } else {
527
+ // server 级:找任意一个已 init 的 session
528
+ for (const s of sessionManager.sessions.values()) {
529
+ if (s.metadata) { meta = s.metadata; break; }
530
+ }
531
+ }
532
+
533
+ if (meta) {
534
+ // 统一返回 type='skills',便于客户端按 type 路由
535
+ ws.send(JSON.stringify({
536
+ type: 'skills',
537
+ sessionId: meta.sessionId,
538
+ model: meta.model,
539
+ skills: meta.skills || [],
540
+ tools: meta.tools || [],
541
+ slashCommands: meta.slashCommands || [],
542
+ agents: meta.agents || [],
543
+ cwd: meta.cwd,
544
+ }));
523
545
  } else {
546
+ // 无 init 元数据时:让客户端发一个 query 触发 init,或
547
+ // 等下一个 session 启动后再次 getSkills
524
548
  ws.send(JSON.stringify({
525
549
  type: 'skills',
526
550
  skills: [],
527
551
  tools: [],
528
552
  slashCommands: [],
529
553
  agents: [],
530
- note: 'session not yet initialized',
554
+ note: 'no session has been initialized yet — send a query first',
531
555
  }));
532
556
  }
533
557
  break;
package/src/query.js DELETED
@@ -1,128 +0,0 @@
1
- /**
2
- * 冷启动查询 — 对 @anthropic-ai/claude-agent-sdk 的一次性调用
3
- *
4
- * 参考 claudian 的 claudeColdStartQuery.ts(简单的非持久化路径)。
5
- * 不含 MessageChannel、流式消费循环或 Electron 兼容代码。
6
- */
7
-
8
- import { query as agentQuery } from '@anthropic-ai/claude-agent-sdk';
9
-
10
- /**
11
- * 执行一次冷启动查询并返回完整的文本结果。
12
- *
13
- * @param {object} options
14
- * @param {string} options.prompt - 用户提示词
15
- * @param {string} [options.systemPrompt] - 系统提示词覆盖
16
- * @param {string} [options.model] - 模型 ID 覆盖
17
- * @param {string} options.cwd - 工作目录
18
- * @param {string} options.claudePath - Claude CLI 可执行文件路径
19
- * @param {object} [options.env] - 额外的环境变量
20
- * @param {string[]} [options.tools] - 工具白名单(省略则使用 SDK 默认值)
21
- * @param {boolean} [options.persistSession] - 是否持久化会话(默认 true)
22
- * @param {string} [options.resumeSessionId] - 恢复之前的会话
23
- * @param {string[]|'all'} [options.skills] - 加载哪些 Skill(数组或 'all')
24
- * @param {AbortController} [options.signal] - 中止信号
25
- * @returns {Promise<{ text: string, sessionId: string | null, metadata: object | null }>}
26
- */
27
- export async function runQuery(options) {
28
- const {
29
- prompt,
30
- systemPrompt,
31
- model,
32
- cwd,
33
- claudePath,
34
- env = {},
35
- tools,
36
- persistSession,
37
- resumeSessionId,
38
- skills,
39
- signal,
40
- } = options;
41
-
42
- // 组装 SDK 选项
43
- const sdkOptions = {
44
- cwd: cwd || process.cwd(),
45
- model: model || 'claude-sonnet-4-6',
46
- pathToClaudeCodeExecutable: claudePath,
47
- permissionMode: 'bypassPermissions', // 跳过权限确认
48
- allowDangerouslySkipPermissions: true,
49
- env: {
50
- ...process.env,
51
- ...env, // 合并额外环境变量
52
- },
53
- };
54
-
55
- // 以下为可选参数的条件注入
56
- if (systemPrompt) {
57
- sdkOptions.systemPrompt = systemPrompt;
58
- }
59
-
60
- if (tools !== undefined) {
61
- sdkOptions.tools = tools;
62
- }
63
-
64
- if (persistSession === false) {
65
- sdkOptions.persistSession = false;
66
- }
67
-
68
- if (resumeSessionId) {
69
- sdkOptions.resume = resumeSessionId;
70
- }
71
-
72
- if (skills !== undefined) {
73
- sdkOptions.skills = skills;
74
- }
75
-
76
- if (signal) {
77
- sdkOptions.abortController = signal;
78
- }
79
-
80
- // 确保总有一个 abort controller
81
- const abortController = signal || new AbortController();
82
- sdkOptions.abortController = abortController;
83
-
84
- // 发起 SDK 查询(返回异步迭代器)
85
- const response = agentQuery({ prompt, options: sdkOptions });
86
-
87
- let text = '';
88
- let sessionId = null;
89
- let metadata = null;
90
-
91
- // 遍历 SDK 返回的流式消息
92
- for await (const message of response) {
93
- // 如果已中止,中断查询
94
- if (abortController.signal.aborted) {
95
- await response.interrupt();
96
- break;
97
- }
98
-
99
- // 从 init 消息中捕获 sessionId + 技能/工具/命令 元数据
100
- if (message.type === 'system' && message.subtype === 'init') {
101
- sessionId = message.session_id;
102
- metadata = {
103
- model: message.model,
104
- skills: message.skills || [],
105
- tools: message.tools || [],
106
- slashCommands: message.slash_commands || [],
107
- agents: message.agents || [],
108
- };
109
- }
110
-
111
- // 提取助手的文本回复内容
112
- if (message.type === 'assistant' && message.message?.content) {
113
- const content = message.message.content;
114
- if (typeof content === 'string') {
115
- text += content;
116
- } else if (Array.isArray(content)) {
117
- // 内容块数组,筛选出 text 块
118
- for (const block of content) {
119
- if (block.type === 'text') {
120
- text += block.text;
121
- }
122
- }
123
- }
124
- }
125
- }
126
-
127
- return { text, sessionId, metadata };
128
- }
package/src/skills.js DELETED
@@ -1,56 +0,0 @@
1
- /**
2
- * skills — 查询 SDK 可用的 skill / tool / agent 列表
3
- *
4
- * 使用一次超轻量 agentQuery() 获取 init 元数据。
5
- * 不发起真正对话,不持久化 session。
6
- */
7
-
8
- import { query as agentQuery } from '@anthropic-ai/claude-agent-sdk';
9
-
10
- /**
11
- * 获取 SDK 可用的 skills/tools/slashCommands/agents 列表。
12
- *
13
- * @param {object} [options]
14
- * @param {string} [options.cwd] - 工作目录
15
- * @param {string} [options.claudePath] - Claude CLI 路径
16
- * @param {object} [options.env] - 额外环境变量
17
- * @returns {Promise<{ skills: string[], tools: string[], slashCommands: string[], agents: string[] }>}
18
- */
19
- export async function listSkills(options = {}) {
20
- const sdkOptions = {
21
- cwd: options.cwd || process.cwd(),
22
- model: 'claude-haiku-4-5',
23
- permissionMode: 'bypassPermissions',
24
- allowDangerouslySkipPermissions: true,
25
- persistSession: false,
26
- env: { ...process.env, ...options.env },
27
- };
28
-
29
- if (options.claudePath) {
30
- sdkOptions.pathToClaudeCodeExecutable = options.claudePath;
31
- }
32
-
33
- // 用空 prompt 做一次超轻量 init
34
- const response = agentQuery({ prompt: ' ', options: sdkOptions });
35
-
36
- const result = {
37
- skills: [],
38
- tools: [],
39
- slashCommands: [],
40
- agents: [],
41
- };
42
-
43
- for await (const message of response) {
44
- if (message.type === 'system' && message.subtype === 'init') {
45
- if (Array.isArray(message.skills)) result.skills = message.skills;
46
- if (Array.isArray(message.tools)) result.tools = message.tools;
47
- if (Array.isArray(message.slash_commands)) result.slashCommands = message.slash_commands;
48
- if (Array.isArray(message.agents)) result.agents = message.agents;
49
- // init 消息后立即中断,不继续消耗资源
50
- await response.interrupt().catch(() => {});
51
- break;
52
- }
53
- }
54
-
55
- return result;
56
- }