opencode-agents 1.0.7 → 1.0.8
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/build.mjs +1 -0
- package/dist/index.js +131 -20
- package/dist/index.js.map +3 -3
- package/package.json +3 -2
- package/src/commands/add.ts +126 -8
- package/src/core/installer.ts +25 -14
- package/src/types/index.ts +2 -0
- package/dist/index.cjs +0 -26268
- package/dist/index.cjs.map +0 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-agents",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "CLI for managing AI coding agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"gray-matter": "^4.0.3",
|
|
28
28
|
"ora": "^5.4.1",
|
|
29
29
|
"chalk": "^5.3.0",
|
|
30
|
-
"yaml": "^2.3.4"
|
|
30
|
+
"yaml": "^2.3.4",
|
|
31
|
+
"inquirer": "^9.2.12"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
33
34
|
"@types/node": "^20.10.0",
|
package/src/commands/add.ts
CHANGED
|
@@ -1,23 +1,135 @@
|
|
|
1
1
|
import ora from 'ora';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import { tmpdir } from 'os';
|
|
4
|
+
import { existsSync, rmSync } from 'fs';
|
|
2
5
|
import { installAgent } from '../core/installer.js';
|
|
6
|
+
import { discoverFromDirectory } from '../core/discover.js';
|
|
3
7
|
import { detectPlatforms } from '../utils/filesystem.js';
|
|
4
8
|
import { logger } from '../utils/logger.js';
|
|
5
|
-
import type { AgentPlatform, InstallOptions } from '../types/index.js';
|
|
9
|
+
import type { AgentPlatform, InstallOptions, AgentFile } from '../types/index.js';
|
|
10
|
+
import { basename } from 'path';
|
|
6
11
|
|
|
7
12
|
interface AddCommandOptions {
|
|
8
|
-
global: boolean;
|
|
13
|
+
global: boolean | undefined;
|
|
9
14
|
agent: string[] | undefined;
|
|
10
15
|
agentName: string | undefined;
|
|
11
16
|
yes: boolean;
|
|
12
17
|
copy: boolean;
|
|
13
18
|
}
|
|
14
19
|
|
|
20
|
+
async function promptInstallLocation(): Promise<boolean> {
|
|
21
|
+
const answers = await inquirer.prompt([
|
|
22
|
+
{
|
|
23
|
+
type: 'list',
|
|
24
|
+
name: 'scope',
|
|
25
|
+
message: 'Where would you like to install this agent?',
|
|
26
|
+
choices: [
|
|
27
|
+
{ name: 'Current project (./.opencode/agents/)', value: false },
|
|
28
|
+
{ name: 'Global (~/.config/opencode/agents/)', value: true },
|
|
29
|
+
],
|
|
30
|
+
default: 0,
|
|
31
|
+
},
|
|
32
|
+
]);
|
|
33
|
+
return answers.scope;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function promptSelectAgents(agents: AgentFile[]): Promise<AgentFile[]> {
|
|
37
|
+
const choices = agents.map(agent => ({
|
|
38
|
+
name: agent.agent.name || basename(agent.path, '.md'),
|
|
39
|
+
value: agent,
|
|
40
|
+
checked: false,
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
const answers = await inquirer.prompt([
|
|
44
|
+
{
|
|
45
|
+
type: 'checkbox',
|
|
46
|
+
name: 'selected',
|
|
47
|
+
message: 'Select agents to install (space to select, enter to confirm):',
|
|
48
|
+
choices,
|
|
49
|
+
},
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
return answers.selected;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function fetchSource(source: string): Promise<string> {
|
|
56
|
+
const { mkdtempSync, join } = await import('path');
|
|
57
|
+
const degit = await import('degit');
|
|
58
|
+
|
|
59
|
+
const tempDir = mkdtempSync(join(tmpdir(), 'agents-cli-'));
|
|
60
|
+
|
|
61
|
+
const parts = source.split('/');
|
|
62
|
+
let owner = parts[0];
|
|
63
|
+
let repo = parts[1]?.replace(/#.+$/, '') || '';
|
|
64
|
+
let ref = '';
|
|
65
|
+
|
|
66
|
+
if (source.includes('#')) {
|
|
67
|
+
const [repoPart, refPart] = source.split('#');
|
|
68
|
+
repo = repoPart.split('/')[1];
|
|
69
|
+
ref = refPart;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const target = ref ? `${owner}/${repo}#${ref}` : `${owner}/${repo}`;
|
|
74
|
+
await degit.default(target).clone(tempDir);
|
|
75
|
+
return tempDir;
|
|
76
|
+
} catch (err) {
|
|
77
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
78
|
+
throw new Error(`Failed to fetch from ${source}: ${err}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
15
82
|
export async function addCommand(source: string, options: AddCommandOptions): Promise<void> {
|
|
16
|
-
|
|
83
|
+
let isGlobal = options.global;
|
|
84
|
+
|
|
85
|
+
if (isGlobal === undefined) {
|
|
86
|
+
isGlobal = await promptInstallLocation();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const fetchSpinner = ora('Fetching source...').start();
|
|
90
|
+
|
|
91
|
+
let tempDir: string;
|
|
92
|
+
try {
|
|
93
|
+
tempDir = await fetchSource(source);
|
|
94
|
+
fetchSpinner.succeed('Source fetched');
|
|
95
|
+
} catch (err) {
|
|
96
|
+
fetchSpinner.fail(`Failed to fetch source: ${err instanceof Error ? err.message : String(err)}`);
|
|
97
|
+
logger.error(String(err));
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const discoverSpinner = ora('Discovering agents...').start();
|
|
102
|
+
let agents: AgentFile[];
|
|
103
|
+
try {
|
|
104
|
+
agents = await discoverFromDirectory(tempDir);
|
|
105
|
+
discoverSpinner.succeed(`Found ${agents.length} agent(s)`);
|
|
106
|
+
} catch (err) {
|
|
107
|
+
discoverSpinner.fail(`Failed to discover agents: ${err instanceof Error ? err.message : String(err)}`);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (agents.length === 0) {
|
|
112
|
+
logger.error('No agents found in the source');
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let selectedAgents: AgentFile[];
|
|
117
|
+
if (options.yes) {
|
|
118
|
+
selectedAgents = agents;
|
|
119
|
+
} else {
|
|
120
|
+
selectedAgents = await promptSelectAgents(agents);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (selectedAgents.length === 0) {
|
|
124
|
+
logger.warn('No agents selected, aborting');
|
|
125
|
+
process.exit(0);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const installSpinner = ora('Installing agent(s)...').start();
|
|
17
129
|
|
|
18
130
|
try {
|
|
19
131
|
let platforms: AgentPlatform[];
|
|
20
|
-
|
|
132
|
+
|
|
21
133
|
if (options.agent && options.agent.length > 0) {
|
|
22
134
|
platforms = options.agent as AgentPlatform[];
|
|
23
135
|
} else {
|
|
@@ -26,19 +138,25 @@ export async function addCommand(source: string, options: AddCommandOptions): Pr
|
|
|
26
138
|
|
|
27
139
|
const installOptions: InstallOptions = {
|
|
28
140
|
source,
|
|
29
|
-
|
|
141
|
+
sourcePath: tempDir,
|
|
142
|
+
global: isGlobal,
|
|
30
143
|
platforms,
|
|
31
144
|
agentName: options.agentName,
|
|
32
145
|
copy: options.copy,
|
|
33
146
|
yes: options.yes,
|
|
147
|
+
selectedAgents,
|
|
34
148
|
};
|
|
35
149
|
|
|
36
150
|
await installAgent(installOptions);
|
|
37
|
-
|
|
38
|
-
|
|
151
|
+
|
|
152
|
+
installSpinner.succeed(`Successfully installed ${selectedAgents.length} agent(s)`);
|
|
39
153
|
} catch (err) {
|
|
40
|
-
|
|
154
|
+
installSpinner.fail(`Failed to install agent: ${err instanceof Error ? err.message : String(err)}`);
|
|
41
155
|
logger.error(String(err));
|
|
42
156
|
process.exit(1);
|
|
157
|
+
} finally {
|
|
158
|
+
if (existsSync(tempDir) && tempDir.startsWith(tmpdir())) {
|
|
159
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
160
|
+
}
|
|
43
161
|
}
|
|
44
162
|
}
|
package/src/core/installer.ts
CHANGED
|
@@ -9,29 +9,39 @@ import { logger } from '../utils/logger.js';
|
|
|
9
9
|
import type { AgentPlatform, AgentFile, InstallOptions } from '../types/index.js';
|
|
10
10
|
|
|
11
11
|
export async function installAgent(options: InstallOptions): Promise<void> {
|
|
12
|
-
const { source, global, platforms, agentName, copy } = options;
|
|
13
|
-
|
|
12
|
+
const { source, sourcePath, global, platforms, agentName, copy, selectedAgents } = options;
|
|
13
|
+
|
|
14
14
|
logger.info(`Installing agent from: ${source}`);
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
let tempDir: string;
|
|
17
|
+
let agents: AgentFile[];
|
|
18
|
+
|
|
19
|
+
if (selectedAgents) {
|
|
20
|
+
tempDir = sourcePath || source;
|
|
21
|
+
agents = selectedAgents;
|
|
22
|
+
} else {
|
|
23
|
+
tempDir = await fetchSource(source);
|
|
24
|
+
agents = await discoverFromDirectory(tempDir);
|
|
25
|
+
|
|
21
26
|
if (agents.length === 0) {
|
|
27
|
+
if (existsSync(tempDir) && tempDir.startsWith(tmpdir())) {
|
|
28
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
29
|
+
}
|
|
22
30
|
throw new Error('No agents found in the source');
|
|
23
31
|
}
|
|
32
|
+
}
|
|
24
33
|
|
|
34
|
+
try {
|
|
25
35
|
for (const platform of platforms) {
|
|
26
36
|
const targetPaths = getPlatformPaths(platform, global);
|
|
27
|
-
|
|
37
|
+
|
|
28
38
|
for (const targetPath of targetPaths) {
|
|
29
39
|
ensureDir(targetPath);
|
|
30
|
-
|
|
40
|
+
|
|
31
41
|
for (const agentFile of agents) {
|
|
32
42
|
const name = agentName || agentFile.agent.name || basename(agentFile.path, '.md');
|
|
33
43
|
const finalPath = join(targetPath, `${name}.md`);
|
|
34
|
-
|
|
44
|
+
|
|
35
45
|
if (existsSync(finalPath) && !options.yes) {
|
|
36
46
|
logger.warn(`Agent "${name}" already exists at ${finalPath}`);
|
|
37
47
|
continue;
|
|
@@ -46,13 +56,14 @@ export async function installAgent(options: InstallOptions): Promise<void> {
|
|
|
46
56
|
}
|
|
47
57
|
logger.success(`Copied agent "${name}" to ${finalPath}`);
|
|
48
58
|
} else {
|
|
49
|
-
const
|
|
59
|
+
const sourcePath = agentFile.path;
|
|
60
|
+
const linkType = isDirectory(sourcePath) ? 'dir' : 'file';
|
|
50
61
|
try {
|
|
51
|
-
symlinkSync(
|
|
62
|
+
symlinkSync(sourcePath, finalPath, linkType);
|
|
52
63
|
logger.success(`Symlinked agent "${name}" to ${finalPath}`);
|
|
53
64
|
} catch (err) {
|
|
54
65
|
logger.warn(`Failed to create symlink, copying instead: ${err}`);
|
|
55
|
-
copyDirectory(
|
|
66
|
+
copyDirectory(sourcePath, join(targetPath, name));
|
|
56
67
|
logger.success(`Copied agent "${name}" to ${targetPath}`);
|
|
57
68
|
}
|
|
58
69
|
}
|
|
@@ -60,7 +71,7 @@ export async function installAgent(options: InstallOptions): Promise<void> {
|
|
|
60
71
|
}
|
|
61
72
|
}
|
|
62
73
|
} finally {
|
|
63
|
-
if (existsSync(tempDir) && tempDir.startsWith(tmpdir())) {
|
|
74
|
+
if (!selectedAgents && existsSync(tempDir) && tempDir.startsWith(tmpdir())) {
|
|
64
75
|
rmSync(tempDir, { recursive: true, force: true });
|
|
65
76
|
}
|
|
66
77
|
}
|
package/src/types/index.ts
CHANGED
|
@@ -70,9 +70,11 @@ export type AgentPlatform =
|
|
|
70
70
|
|
|
71
71
|
export interface InstallOptions {
|
|
72
72
|
source: string;
|
|
73
|
+
sourcePath?: string;
|
|
73
74
|
global: boolean;
|
|
74
75
|
platforms: AgentPlatform[];
|
|
75
76
|
agentName?: string;
|
|
76
77
|
copy: boolean;
|
|
77
78
|
yes: boolean;
|
|
79
|
+
selectedAgents?: AgentFile[];
|
|
78
80
|
}
|