opencode-agents 1.1.3 → 1.1.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-agents",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "CLI for managing AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,7 +8,7 @@ import type { AgentPlatform, InstallOptions, AgentFile } from '../types/index.js
8
8
  import { basename, join } from 'path';
9
9
  import { mkdtempSync } from 'fs';
10
10
  import degit from 'degit';
11
- import { showLogo, S_BAR, S_BRANCH, S_BRANCH_END, S_BULLET, S_STEP_ACTIVE } from '../utils/ui.js';
11
+ import { showLogo } from '../utils/ui.js';
12
12
 
13
13
  interface AddCommandOptions {
14
14
  global: boolean | undefined;
@@ -39,11 +39,11 @@ async function promptSelectAgents(agents: AgentFile[]): Promise<AgentFile[]> {
39
39
  const options = agents.map(agent => ({
40
40
  value: agent,
41
41
  label: agent.agent.name || basename(agent.path, '.md'),
42
- hint: agent.agent.description?.slice(0, 50) + '...',
42
+ hint: agent.agent.description?.slice(0, 50),
43
43
  }));
44
44
 
45
45
  const selected = await p.multiselect({
46
- message: 'Select agents to install (space to select, enter to confirm):',
46
+ message: 'Select agents to install:',
47
47
  options,
48
48
  required: true,
49
49
  });
@@ -90,33 +90,28 @@ export async function addCommand(source: string, options: AddCommandOptions): Pr
90
90
  isGlobal = await promptInstallLocation();
91
91
  }
92
92
 
93
- // 使用统一的树状结构
94
- console.log(`${S_STEP_ACTIVE} ${pc.cyan('Source:')} ${pc.dim(`https://github.com/${source}.git`)}`);
95
- console.log(S_BAR);
93
+ p.log.step(`Source: ${pc.cyan(`https://github.com/${source}.git`)}`);
96
94
 
97
95
  const s = p.spinner();
98
- s.start('Cloning repository...');
96
+ s.start('Cloning repository');
99
97
 
100
98
  let tempDir: string;
101
99
  try {
102
100
  tempDir = await fetchSource(source);
103
- s.stop(`${pc.green('✓')} Repository cloned`);
101
+ s.stop('Repository cloned');
104
102
  } catch (err) {
105
- s.stop(`${pc.red('✗')} Failed to clone repository`);
103
+ s.stop('Failed to clone repository');
106
104
  p.log.error(`Failed to fetch source: ${err instanceof Error ? err.message : String(err)}`);
107
105
  process.exit(1);
108
106
  }
109
107
 
110
- console.log(S_BAR);
111
-
112
- // 步骤 2: 发现 agents
113
- s.start('Discovering agents...');
108
+ s.start('Discovering agents');
114
109
  let agents: AgentFile[];
115
110
  try {
116
111
  agents = await discoverFromDirectory(tempDir);
117
- s.stop(`${pc.green('✓')} Found ${agents.length} agent(s)`);
112
+ s.stop(`Found ${pc.green(String(agents.length))} agent(s)`);
118
113
  } catch (err) {
119
- s.stop(`${pc.red('✗')} Failed to discover agents`);
114
+ s.stop('Failed to discover agents');
120
115
  p.log.error(`Failed to discover agents: ${err instanceof Error ? err.message : String(err)}`);
121
116
  process.exit(1);
122
117
  }
@@ -126,9 +121,6 @@ export async function addCommand(source: string, options: AddCommandOptions): Pr
126
121
  process.exit(1);
127
122
  }
128
123
 
129
- console.log(S_BAR);
130
-
131
- // 步骤 3: 选择 agents
132
124
  let selectedAgents: AgentFile[];
133
125
  if (options.yes) {
134
126
  selectedAgents = agents;
@@ -141,18 +133,13 @@ export async function addCommand(source: string, options: AddCommandOptions): Pr
141
133
  process.exit(0);
142
134
  }
143
135
 
144
- // 显示选中的 agents - 作为树的分支
145
- console.log(`${S_BAR} ${S_BRANCH} ${pc.dim('Selected:')}`);
146
- selectedAgents.forEach((agent, index) => {
147
- const isLast = index === selectedAgents.length - 1;
148
- const prefix = isLast ? S_BRANCH_END : S_BRANCH;
149
- const name = agent.agent.name || basename(agent.path, '.md');
150
- console.log(`${S_BAR} ${isLast ? ' ' : S_BAR} ${prefix} ${S_BULLET} ${pc.bold(name)}`);
151
- });
136
+ // 显示选中的 agents
137
+ const selectedList = selectedAgents.map(agent =>
138
+ ` ${pc.bold(agent.agent.name || basename(agent.path, '.md'))}`
139
+ ).join('\n');
140
+ p.note(selectedList, 'Selected agents');
152
141
 
153
- // 步骤 4: 安装
154
- console.log(S_BAR);
155
- s.start('Installing agents...');
142
+ s.start(`Installing ${selectedAgents.length} agent(s)`);
156
143
 
157
144
  try {
158
145
  let platforms: AgentPlatform[];
@@ -176,14 +163,11 @@ export async function addCommand(source: string, options: AddCommandOptions): Pr
176
163
 
177
164
  await installAgent(installOptions);
178
165
 
179
- s.stop(`${pc.green('✓')} Successfully installed ${selectedAgents.length} agent(s)`);
166
+ s.stop(pc.green(`Successfully installed ${selectedAgents.length} agent(s)`));
180
167
 
181
- console.log();
182
- console.log(pc.dim(' Next steps:'));
183
- console.log(pc.dim(` npx opencode-agents list View installed agents`));
184
- console.log();
168
+ p.outro('Installation complete! Run `npx opencode-agents list` to see installed agents.');
185
169
  } catch (err) {
186
- s.stop(`${pc.red('✗')} Installation failed`);
170
+ s.stop('Installation failed');
187
171
  p.log.error(`Failed to install agent: ${err instanceof Error ? err.message : String(err)}`);
188
172
  process.exit(1);
189
173
  } finally {
@@ -2,7 +2,6 @@ import * as p from '@clack/prompts';
2
2
  import pc from 'picocolors';
3
3
  import { listInstalledAgents } from '../core/installer.js';
4
4
  import type { AgentPlatform } from '../types/index.js';
5
- import { S_BAR, S_BRANCH, S_BRANCH_END, S_BULLET } from '../utils/ui.js';
6
5
 
7
6
  interface ListCommandOptions {
8
7
  global: boolean;
@@ -18,8 +17,6 @@ export async function listCommand(options: ListCommandOptions): Promise<void> {
18
17
  platforms = ['opencode'];
19
18
  }
20
19
 
21
- console.log();
22
-
23
20
  let hasAnyAgents = false;
24
21
 
25
22
  for (const platform of platforms) {
@@ -32,57 +29,52 @@ export async function listCommand(options: ListCommandOptions): Promise<void> {
32
29
 
33
30
  hasAnyAgents = true;
34
31
 
35
- console.log(pc.bold(pc.cyan(`◆ ${platform} agents`)));
32
+ console.log();
33
+ console.log(pc.bold(pc.cyan(`${platform} agents`)));
36
34
  console.log();
37
35
 
38
36
  // 项目级别
39
37
  if (projectAgents.length > 0) {
40
- console.log(` ${globalAgents.length > 0 ? S_BRANCH : S_BRANCH_END} ${pc.dim('Project')} ${pc.dim('(./.opencode/agents/)')}`);
38
+ console.log(pc.dim('Project (./.opencode/agents/)'));
41
39
 
42
- projectAgents.forEach((agent, index) => {
43
- const isLast = index === projectAgents.length - 1;
44
- const prefix = isLast ? S_BRANCH_END : S_BRANCH;
45
-
40
+ for (const agent of projectAgents) {
46
41
  const name = agent.agent.name || agent.path.split(/[/\\]/).pop()?.replace('.md', '') || 'unknown';
47
42
  const mode = agent.agent.mode || 'subagent';
48
43
 
49
- console.log(` ${globalAgents.length > 0 ? S_BAR : ' '} ${prefix} ${S_BULLET} ${pc.bold(name)} ${pc.dim(`[${mode}]`)}`);
44
+ console.log(` ${pc.bold(name)} ${pc.dim(`[${mode}]`)}`);
50
45
 
51
46
  if (agent.agent.description) {
52
- console.log(` ${globalAgents.length > 0 ? S_BAR : ' '} ${isLast ? ' ' : `${S_BAR} `} ${pc.dim(agent.agent.description)}`);
47
+ console.log(` ${pc.dim(agent.agent.description)}`);
53
48
  }
54
- });
49
+ }
55
50
 
56
51
  console.log();
57
52
  }
58
53
 
59
54
  // 全局级别
60
55
  if (globalAgents.length > 0) {
61
- console.log(` ${S_BRANCH_END} ${pc.dim('Global')} ${pc.dim('(~/.config/opencode/agents/)')}`);
56
+ console.log(pc.dim('Global (~/.config/opencode/agents/)'));
62
57
 
63
- globalAgents.forEach((agent, index) => {
64
- const isLast = index === globalAgents.length - 1;
65
- const prefix = isLast ? S_BRANCH_END : S_BRANCH;
66
-
58
+ for (const agent of globalAgents) {
67
59
  const name = agent.agent.name || agent.path.split(/[/\\]/).pop()?.replace('.md', '') || 'unknown';
68
60
  const mode = agent.agent.mode || 'subagent';
69
61
 
70
- console.log(` ${prefix} ${S_BULLET} ${pc.bold(name)} ${pc.dim(`[${mode}]`)}`);
62
+ console.log(` ${pc.bold(name)} ${pc.dim(`[${mode}]`)}`);
71
63
 
72
64
  if (agent.agent.description) {
73
- console.log(` ${isLast ? ' ' : `${S_BAR} `} ${pc.dim(agent.agent.description)}`);
65
+ console.log(` ${pc.dim(agent.agent.description)}`);
74
66
  }
75
- });
67
+ }
76
68
 
77
69
  console.log();
78
70
  }
79
71
  }
80
72
 
81
73
  if (!hasAnyAgents) {
82
- p.log.info(pc.dim('No agents installed'));
74
+ p.log.info('No agents installed');
83
75
  console.log();
84
- console.log(pc.dim(' To install an agent:'));
85
- console.log(pc.dim(` npx opencode-agents add <repo>`));
76
+ console.log(pc.dim('To install an agent:'));
77
+ console.log(pc.dim(` npx opencode-agents add <repo>`));
86
78
  console.log();
87
79
  }
88
80
  }
@@ -1,6 +1,6 @@
1
1
  import { join, basename } from 'path';
2
2
  import { existsSync, readdirSync, readFileSync } from 'fs';
3
- import { parseAgentFile } from './parser.js';
3
+ import { parseAgentFile, isAgentContent } from './parser.js';
4
4
  import { findFiles, isDirectory } from '../utils/filesystem.js';
5
5
  import type { AgentFile, Agent } from '../types/index.js';
6
6
 
@@ -14,7 +14,6 @@ const SEARCH_DIRECTORIES = [
14
14
 
15
15
  const PRIORITY_FILES = [
16
16
  'AGENTS.md',
17
- 'SKILL.md',
18
17
  'AGENT.md',
19
18
  ];
20
19
 
@@ -34,8 +33,10 @@ export async function discoverFromDirectory(dir: string): Promise<AgentFile[]> {
34
33
 
35
34
  try {
36
35
  const content = readFileSync(file, 'utf-8');
37
- const agentFile = parseAgentFile(content, file);
38
- agents.push(agentFile);
36
+ const result = parseAgentFile(content, file);
37
+ // 如果解析结果为 null,说明不是 Agent 文件(是 Skill 文件)
38
+ if (result === null) continue;
39
+ agents.push(result);
39
40
  } catch (err) {
40
41
  continue;
41
42
  }
@@ -47,10 +48,12 @@ export async function discoverFromDirectory(dir: string): Promise<AgentFile[]> {
47
48
  if (existsSync(priorityFile)) {
48
49
  try {
49
50
  const content = readFileSync(priorityFile, 'utf-8');
50
- const agentFile = parseAgentFile(content, priorityFile);
51
- const exists = agents.some(a => a.path === agentFile.path);
51
+ const result = parseAgentFile(content, priorityFile);
52
+ // 如果解析结果为 null,说明不是 Agent 文件(是 Skill 文件)
53
+ if (result === null) continue;
54
+ const exists = agents.some(a => a.path === result.path);
52
55
  if (!exists) {
53
- agents.unshift(agentFile);
56
+ agents.unshift(result);
54
57
  }
55
58
  } catch (err) {
56
59
  continue;
@@ -96,8 +99,10 @@ export function discoverLocal(dir: string): AgentFile[] {
96
99
 
97
100
  try {
98
101
  const content = readFileSync(file, 'utf-8');
99
- const agentFile = parseAgentFile(content, file);
100
- agents.push(agentFile);
102
+ const result = parseAgentFile(content, file);
103
+ // 如果解析结果为 null,说明不是 Agent 文件(是 Skill 文件)
104
+ if (result === null) continue;
105
+ agents.push(result);
101
106
  } catch (err) {
102
107
  continue;
103
108
  }
@@ -110,12 +110,14 @@ export function listInstalledAgents(platform: AgentPlatform, global: boolean): A
110
110
 
111
111
  for (const path of paths) {
112
112
  if (!existsSync(path)) continue;
113
-
113
+
114
114
  const mdFiles = findFiles(path, /\.md$/);
115
115
  for (const file of mdFiles) {
116
116
  try {
117
117
  const content = readFileSync(file, 'utf-8');
118
118
  const agentFile = parseAgentFile(content, file);
119
+ // 如果解析结果为 null,说明不是 Agent 文件(是 Skill 文件)
120
+ if (agentFile === null) continue;
119
121
  agents.push(agentFile);
120
122
  } catch (err) {
121
123
  continue;
@@ -1,9 +1,40 @@
1
1
  import matter from 'gray-matter';
2
2
  import type { Agent, AgentFile } from '../types/index.js';
3
3
 
4
- export function parseAgentFile(content: string, path: string): AgentFile {
4
+ // Agent 特有字段,用于区分 Agent Skill
5
+ const AGENT_SPECIFIC_FIELDS = [
6
+ 'mode',
7
+ 'model',
8
+ 'temperature',
9
+ 'maxSteps',
10
+ 'color',
11
+ 'trigger',
12
+ 'tools',
13
+ 'permission',
14
+ 'mcp',
15
+ ];
16
+
17
+ /**
18
+ * 判断一个 markdown 文件内容是 Agent 还是 Skill
19
+ * 如果 frontmatter 中包含任何 Agent 特有字段,则认为是 Agent
20
+ */
21
+ export function isAgentContent(content: string): boolean {
22
+ try {
23
+ const { data } = matter(content);
24
+ return AGENT_SPECIFIC_FIELDS.some(field => field in data);
25
+ } catch {
26
+ return false;
27
+ }
28
+ }
29
+
30
+ export function parseAgentFile(content: string, path: string): AgentFile | null {
5
31
  const { data, content: body } = matter(content);
6
32
 
33
+ // 如果不是 Agent 文件(是 Skill 文件),返回 null
34
+ if (!isAgentContent(content)) {
35
+ return null;
36
+ }
37
+
7
38
  if (!data.description) {
8
39
  throw new Error(`Agent at ${path} is missing required "description" field in frontmatter`);
9
40
  }
@@ -31,7 +62,7 @@ export function parseAgentFile(content: string, path: string): AgentFile {
31
62
  };
32
63
  }
33
64
 
34
- export function parseAgentFromString(content: string, filename: string): AgentFile {
65
+ export function parseAgentFromString(content: string, filename: string): AgentFile | null {
35
66
  const baseName = filename.replace(/\.md$/, '');
36
67
  return parseAgentFile(content, baseName);
37
68
  }