opencode-agents 1.0.16 → 1.1.0

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.0.16",
3
+ "version": "1.1.0",
4
4
  "description": "CLI for managing AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -22,18 +22,20 @@
22
22
  "author": "",
23
23
  "license": "MIT",
24
24
  "dependencies": {
25
+ "@clack/prompts": "^1.0.1",
26
+ "chalk": "^5.3.0",
25
27
  "commander": "^12.1.0",
26
28
  "degit": "^2.8.4",
27
29
  "gray-matter": "^4.0.3",
30
+ "inquirer": "^9.2.12",
28
31
  "ora": "^5.4.1",
29
- "chalk": "^5.3.0",
30
- "yaml": "^2.3.4",
31
- "inquirer": "^9.2.12"
32
+ "picocolors": "^1.1.1",
33
+ "yaml": "^2.3.4"
32
34
  },
33
35
  "devDependencies": {
34
36
  "@types/node": "^20.10.0",
35
- "typescript": "^5.3.0",
36
37
  "esbuild": "^0.24.0",
38
+ "typescript": "^5.3.0",
37
39
  "vitest": "^1.0.0"
38
40
  }
39
41
  }
@@ -1,15 +1,14 @@
1
- import ora from 'ora';
2
- import inquirer from 'inquirer';
1
+ import * as p from '@clack/prompts';
2
+ import pc from 'picocolors';
3
3
  import { tmpdir } from 'os';
4
4
  import { existsSync, rmSync } from 'fs';
5
5
  import { installAgent } from '../core/installer.js';
6
6
  import { discoverFromDirectory } from '../core/discover.js';
7
- import { detectPlatforms } from '../utils/filesystem.js';
8
- import { logger } from '../utils/logger.js';
9
7
  import type { AgentPlatform, InstallOptions, AgentFile } from '../types/index.js';
10
8
  import { basename, join } from 'path';
11
9
  import { mkdtempSync } from 'fs';
12
10
  import degit from 'degit';
11
+ import { showLogo, renderTree, renderSkillCard, success, error } from '../utils/ui.js';
13
12
 
14
13
  interface AddCommandOptions {
15
14
  global: boolean | undefined;
@@ -20,38 +19,41 @@ interface AddCommandOptions {
20
19
  }
21
20
 
22
21
  async function promptInstallLocation(): Promise<boolean> {
23
- const answers = await inquirer.prompt([
24
- {
25
- type: 'list',
26
- name: 'scope',
27
- message: 'Where would you like to install this agent?',
28
- choices: [
29
- { name: 'Current project (./.opencode/agents/)', value: false },
30
- { name: 'Global (~/.config/opencode/agents/)', value: true },
31
- ],
32
- default: 0,
33
- },
34
- ]);
35
- return answers.scope;
22
+ const location = await p.select({
23
+ message: 'Where would you like to install this agent?',
24
+ options: [
25
+ { value: false, label: 'Current project', hint: './.opencode/agents/' },
26
+ { value: true, label: 'Global', hint: '~/.config/opencode/agents/' },
27
+ ],
28
+ });
29
+
30
+ if (p.isCancel(location)) {
31
+ p.cancel('Operation cancelled');
32
+ process.exit(0);
33
+ }
34
+
35
+ return location;
36
36
  }
37
37
 
38
38
  async function promptSelectAgents(agents: AgentFile[]): Promise<AgentFile[]> {
39
- const choices = agents.map(agent => ({
40
- name: agent.agent.name || basename(agent.path, '.md'),
39
+ const options = agents.map(agent => ({
41
40
  value: agent,
42
- checked: false,
41
+ label: agent.agent.name || basename(agent.path, '.md'),
42
+ hint: agent.agent.description?.slice(0, 50) + '...',
43
43
  }));
44
44
 
45
- const answers = await inquirer.prompt([
46
- {
47
- type: 'checkbox',
48
- name: 'selected',
49
- message: 'Select agents to install (space to select, enter to confirm):',
50
- choices,
51
- },
52
- ]);
45
+ const selected = await p.multiselect({
46
+ message: 'Select agents to install (space to select, enter to confirm):',
47
+ options,
48
+ required: true,
49
+ });
50
+
51
+ if (p.isCancel(selected)) {
52
+ p.cancel('Operation cancelled');
53
+ process.exit(0);
54
+ }
53
55
 
54
- return answers.selected;
56
+ return selected as AgentFile[];
55
57
  }
56
58
 
57
59
  async function fetchSource(source: string): Promise<string> {
@@ -79,39 +81,49 @@ async function fetchSource(source: string): Promise<string> {
79
81
  }
80
82
 
81
83
  export async function addCommand(source: string, options: AddCommandOptions): Promise<void> {
84
+ console.clear();
85
+ showLogo();
86
+
82
87
  let isGlobal = options.global;
83
88
 
84
89
  if (isGlobal === undefined) {
85
90
  isGlobal = await promptInstallLocation();
86
91
  }
87
92
 
88
- const fetchSpinner = ora('Fetching source...').start();
89
-
93
+ // 步骤 1: 获取源码
94
+ p.log.step(pc.cyan(`Source: ${pc.dim(`https://github.com/${source}.git`)}`));
95
+
96
+ const s = p.spinner();
97
+ s.start('Cloning repository...');
98
+
90
99
  let tempDir: string;
91
100
  try {
92
101
  tempDir = await fetchSource(source);
93
- fetchSpinner.succeed('Source fetched');
102
+ s.stop('Repository cloned');
94
103
  } catch (err) {
95
- fetchSpinner.fail(`Failed to fetch source: ${err instanceof Error ? err.message : String(err)}`);
96
- logger.error(String(err));
104
+ s.stop('Failed to clone repository');
105
+ error(`Failed to fetch source: ${err instanceof Error ? err.message : String(err)}`);
97
106
  process.exit(1);
98
107
  }
99
108
 
100
- const discoverSpinner = ora('Discovering agents...').start();
109
+ // 步骤 2: 发现 agents
110
+ s.start('Discovering agents...');
101
111
  let agents: AgentFile[];
102
112
  try {
103
113
  agents = await discoverFromDirectory(tempDir);
104
- discoverSpinner.succeed(`Found ${agents.length} agent(s)`);
114
+ s.stop(`Found ${pc.green(String(agents.length))} agent(s)`);
105
115
  } catch (err) {
106
- discoverSpinner.fail(`Failed to discover agents: ${err instanceof Error ? err.message : String(err)}`);
116
+ s.stop('Failed to discover agents');
117
+ error(`Failed to discover agents: ${err instanceof Error ? err.message : String(err)}`);
107
118
  process.exit(1);
108
119
  }
109
120
 
110
121
  if (agents.length === 0) {
111
- logger.error('No agents found in the source');
122
+ error('No agents found in the source');
112
123
  process.exit(1);
113
124
  }
114
125
 
126
+ // 步骤 3: 选择 agents
115
127
  let selectedAgents: AgentFile[];
116
128
  if (options.yes) {
117
129
  selectedAgents = agents;
@@ -120,11 +132,22 @@ export async function addCommand(source: string, options: AddCommandOptions): Pr
120
132
  }
121
133
 
122
134
  if (selectedAgents.length === 0) {
123
- logger.warn('No agents selected, aborting');
135
+ p.cancel('No agents selected, aborting');
124
136
  process.exit(0);
125
137
  }
126
138
 
127
- const installSpinner = ora('Installing agent(s)...').start();
139
+ // 显示选中的 agents
140
+ console.log();
141
+ for (const agent of selectedAgents) {
142
+ renderSkillCard(
143
+ agent.agent.name || basename(agent.path, '.md'),
144
+ agent.agent.description || 'No description available',
145
+ 0
146
+ );
147
+ }
148
+
149
+ // 步骤 4: 安装
150
+ s.start(`Installing ${selectedAgents.length} agent(s)...`);
128
151
 
129
152
  try {
130
153
  let platforms: AgentPlatform[];
@@ -148,10 +171,17 @@ export async function addCommand(source: string, options: AddCommandOptions): Pr
148
171
 
149
172
  await installAgent(installOptions);
150
173
 
151
- installSpinner.succeed(`Successfully installed ${selectedAgents.length} agent(s)`);
174
+ s.stop(pc.green(`Successfully installed ${selectedAgents.length} agent(s)`));
175
+
176
+ console.log();
177
+ success('Installation complete!');
178
+ console.log();
179
+ console.log(pc.dim(' Try:'));
180
+ console.log(pc.dim(` npx opencode-agents list`));
181
+ console.log();
152
182
  } catch (err) {
153
- installSpinner.fail(`Failed to install agent: ${err instanceof Error ? err.message : String(err)}`);
154
- logger.error(String(err));
183
+ s.stop('Installation failed');
184
+ error(`Failed to install agent: ${err instanceof Error ? err.message : String(err)}`);
155
185
  process.exit(1);
156
186
  } finally {
157
187
  if (existsSync(tempDir) && tempDir.startsWith(tmpdir())) {
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { Command } from 'commander';
4
- import chalk from 'chalk';
4
+ import pc from 'picocolors';
5
5
  import { addCommand } from './commands/add.js';
6
6
  import { listCommand } from './commands/list.js';
7
7
  import { removeCommand } from './commands/remove.js';
@@ -9,13 +9,14 @@ import { initCommand } from './commands/init.js';
9
9
  import { findCommand } from './commands/find.js';
10
10
  import { checkCommand } from './commands/check.js';
11
11
  import { updateCommand } from './commands/update.js';
12
+ import { showBanner } from './utils/ui.js';
12
13
 
13
14
  const program = new Command();
14
15
 
15
16
  program
16
17
  .name('agents')
17
18
  .description('CLI for managing AI coding agents')
18
- .version('1.0.0');
19
+ .version('1.1.0');
19
20
 
20
21
  program
21
22
  .command('add <source>')
@@ -62,4 +63,10 @@ program
62
63
  .description('Update all installed agents to the latest version')
63
64
  .action(updateCommand);
64
65
 
66
+ // 显示 banner 如果没有参数
67
+ if (process.argv.length === 2) {
68
+ showBanner();
69
+ program.help();
70
+ }
71
+
65
72
  program.parse();
@@ -0,0 +1,135 @@
1
+ import pc from 'picocolors';
2
+
3
+ // ANSI 重置
4
+ export const RESET = '\x1b[0m';
5
+ export const BOLD = '\x1b[1m';
6
+
7
+ // 图标
8
+ export const S_STEP_ACTIVE = pc.green('◆');
9
+ export const S_STEP_SUBMIT = pc.green('◇');
10
+ export const S_STEP_CANCEL = pc.red('■');
11
+ export const S_RADIO_ACTIVE = pc.green('●');
12
+ export const S_RADIO_INACTIVE = pc.dim('○');
13
+ export const S_CHECKBOX_ACTIVE = pc.green('☑');
14
+ export const S_CHECKBOX_INACTIVE = pc.dim('☐');
15
+ export const S_CHECKBOX_LOCKED = pc.green('✓');
16
+ export const S_BULLET = pc.blue('●');
17
+ export const S_BAR = pc.dim('│');
18
+ export const S_BAR_H = pc.dim('─');
19
+ export const S_CORNER_TOP = pc.dim('┌');
20
+ export const S_CORNER_BOTTOM = pc.dim('└');
21
+ export const S_BRANCH = pc.dim('├');
22
+ export const S_BRANCH_END = pc.dim('└');
23
+
24
+ // Logo
25
+ export const LOGO_LINES = [
26
+ ' █████╗ ██████╗ ███████╗███╗ ██╗████████╗███████╗',
27
+ '██╔══██╗██╔════╝ ██╔════╝████╗ ██║╚══██╔══╝██╔════╝',
28
+ '███████║██║ ███╗█████╗ ██╔██╗ ██║ ██║ █████╗ ',
29
+ '██╔══██║██║ ██║██╔══╝ ██║╚██╗██║ ██║ ██╔══╝ ',
30
+ '██║ ██║╚██████╔╝███████╗██║ ╚████║ ██║ ███████╗',
31
+ '╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝',
32
+ ];
33
+
34
+ export const LOGO_COLORS = [
35
+ pc.cyan,
36
+ pc.cyan,
37
+ pc.blue,
38
+ pc.blue,
39
+ pc.magenta,
40
+ pc.magenta,
41
+ ];
42
+
43
+ export function showLogo(): void {
44
+ console.log();
45
+ LOGO_LINES.forEach((line, i) => {
46
+ console.log(LOGO_COLORS[i](line));
47
+ });
48
+ console.log();
49
+ }
50
+
51
+ export function showBanner(): void {
52
+ showLogo();
53
+ console.log(pc.dim(' AI Agent Management CLI'));
54
+ console.log();
55
+ console.log(` ${pc.dim('$')} ${pc.cyan('npx opencode-agents add')} ${pc.dim('<repo>')} ${pc.dim('Install agents from a repository')}`);
56
+ console.log(` ${pc.dim('$')} ${pc.cyan('npx opencode-agents list')} ${pc.dim('List installed agents')}`);
57
+ console.log(` ${pc.dim('$')} ${pc.cyan('npx opencode-agents remove')} ${pc.dim('Remove installed agents')}`);
58
+ console.log(` ${pc.dim('$')} ${pc.cyan('npx opencode-agents find')} ${pc.dim('Search for agents')}`);
59
+ console.log();
60
+ }
61
+
62
+ // 进度树状结构
63
+ export interface TreeStep {
64
+ label: string;
65
+ status: 'pending' | 'active' | 'done' | 'error';
66
+ value?: string;
67
+ }
68
+
69
+ export function renderTree(steps: TreeStep[]): void {
70
+ console.log();
71
+ steps.forEach((step, index) => {
72
+ const isLast = index === steps.length - 1;
73
+ const prefix = isLast ? S_BRANCH_END : S_BRANCH;
74
+
75
+ let icon: string;
76
+ let color: (s: string) => string;
77
+
78
+ switch (step.status) {
79
+ case 'done':
80
+ icon = pc.green('◆');
81
+ color = pc.green;
82
+ break;
83
+ case 'active':
84
+ icon = pc.cyan('◆');
85
+ color = pc.cyan;
86
+ break;
87
+ case 'error':
88
+ icon = pc.red('■');
89
+ color = pc.red;
90
+ break;
91
+ default:
92
+ icon = pc.dim('○');
93
+ color = pc.dim;
94
+ }
95
+
96
+ console.log(` ${prefix} ${icon} ${color(step.label)}`);
97
+
98
+ if (step.value && step.status === 'done') {
99
+ console.log(` ${isLast ? ' ' : `${S_BAR} `} ${pc.dim(step.value)}`);
100
+ }
101
+ });
102
+ console.log();
103
+ }
104
+
105
+ // 技能卡片
106
+ export function renderSkillCard(name: string, description: string, index: number): void {
107
+ console.log();
108
+ console.log(` ${S_BAR}`);
109
+ console.log(` ${S_BRANCH} ${pc.cyan('Agent:')} ${pc.bold(name)}`);
110
+ console.log(` ${S_BAR}`);
111
+ console.log(` ${S_BRANCH_END} ${pc.dim(description)}`);
112
+ console.log();
113
+ }
114
+
115
+ // 分隔线
116
+ export function divider(): void {
117
+ console.log(pc.dim(' ' + '─'.repeat(50)));
118
+ }
119
+
120
+ // 成功/错误信息
121
+ export function success(message: string): void {
122
+ console.log(` ${pc.green('✓')} ${message}`);
123
+ }
124
+
125
+ export function error(message: string): void {
126
+ console.log(` ${pc.red('✗')} ${message}`);
127
+ }
128
+
129
+ export function warning(message: string): void {
130
+ console.log(` ${pc.yellow('⚠')} ${message}`);
131
+ }
132
+
133
+ export function info(message: string): void {
134
+ console.log(` ${pc.blue('ℹ')} ${message}`);
135
+ }