Skip to content

Commit 6a845f3

Browse files
committed
feat: add repository management and VS Code extension
Implement plugin repository management system with git submodule support for agents, skills, hooks, and MCP servers. Adds /repo CLI commands for installing, updating, enabling, disabling, and removing repository items. Includes new VS Code extension providing integrated chat interface with file context mentions and WebSocket connection to the CLI.
1 parent 5fdc14a commit 6a845f3

19 files changed

Lines changed: 2408 additions & 9 deletions

src/commands/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { createServeCommand, createWebCommand } from "./web";
2+
import { createRepositoryCommand } from "./repository";
23
import { createProviderCommand } from "./provider";
34
import { createIndexCommand } from "./index-cmd";
45
import { createPluginsCommand } from "./plugins";
@@ -21,4 +22,5 @@ export function registerCommands(program: Command) {
2122
program.addCommand(createImportCommand());
2223
program.addCommand(createProviderCommand());
2324
program.addCommand(createIndexCommand());
25+
program.addCommand(createRepositoryCommand());
2426
}

src/commands/repository.ts

Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
import {
2+
getRepositoryManager,
3+
BUILTIN_REPOSITORIES,
4+
RepositoryItem,
5+
} from "../plugins/repository-manager";
6+
import { Command } from "commander";
7+
import chalk from "chalk";
8+
9+
/**
10+
* Create the Commander.js command for repository management
11+
*/
12+
export function createRepositoryCommand(): Command {
13+
const cmd = new Command("repo");
14+
15+
cmd.description("Manage plugin repositories").action(async () => {
16+
await repositoryCommands.listInstalled();
17+
});
18+
19+
cmd
20+
.command("list")
21+
.description("List installed repositories and available items")
22+
.action(async () => {
23+
await repositoryCommands.listInstalled();
24+
});
25+
26+
cmd
27+
.command("available")
28+
.description("List all available builtin repositories")
29+
.action(async () => {
30+
await repositoryCommands.listAvailable();
31+
});
32+
33+
cmd
34+
.command("install <type>")
35+
.description("Install a plugin repository (agents, skills, hooks, mcp)")
36+
.action(async (type: string) => {
37+
await repositoryCommands.install(type);
38+
});
39+
40+
cmd
41+
.command("update")
42+
.description("Update all installed repositories")
43+
.action(async () => {
44+
await repositoryCommands.update();
45+
});
46+
47+
cmd
48+
.command("enable <name> [type]")
49+
.description("Enable an item from a repository")
50+
.action(async (name: string, type?: string) => {
51+
await repositoryCommands.enable(name, type);
52+
});
53+
54+
cmd
55+
.command("disable <name> [type]")
56+
.description("Disable an item from a repository")
57+
.action(async (name: string, type?: string) => {
58+
await repositoryCommands.disable(name, type);
59+
});
60+
61+
cmd
62+
.command("remove <type>")
63+
.description("Remove an installed repository")
64+
.action(async (type: string) => {
65+
await repositoryCommands.remove(type);
66+
});
67+
68+
return cmd;
69+
}
70+
71+
export const repositoryCommands = {
72+
/**
73+
* List all available builtin repositories
74+
*/
75+
async listAvailable(): Promise<void> {
76+
console.log(chalk.bold("\n📦 Available Plugin Repositories:\n"));
77+
for (const [key, config] of Object.entries(BUILTIN_REPOSITORIES)) {
78+
console.log(` ${chalk.cyan(key)}: ${config.type}`);
79+
console.log(` URL: ${chalk.gray(config.url)}`);
80+
console.log(` Branch: ${chalk.gray(config.branch || "main")}\n`);
81+
}
82+
},
83+
84+
/**
85+
* List installed repositories and their items
86+
*/
87+
async listInstalled(): Promise<void> {
88+
const manager = getRepositoryManager();
89+
const repoInfo = manager.getRepositoryInfo();
90+
const items = await manager.listAvailableItems();
91+
92+
console.log(chalk.bold("\n📦 Installed Repositories:\n"));
93+
94+
if (Object.keys(repoInfo).length === 0) {
95+
console.log(chalk.gray(" No repositories installed."));
96+
console.log(
97+
chalk.gray(" Use '/repo install <type>' to install a repository.\n"),
98+
);
99+
return;
100+
}
101+
102+
for (const [key, config] of Object.entries(repoInfo)) {
103+
console.log(` ${chalk.cyan(key)}: ${config.type}`);
104+
console.log(` URL: ${chalk.gray(config.url)}\n`);
105+
}
106+
107+
if (items.length > 0) {
108+
console.log(chalk.bold("\n📋 Available Items:\n"));
109+
for (const item of items) {
110+
const statusIcon = item.enabled ? chalk.green("✓") : chalk.gray("○");
111+
console.log(
112+
` ${statusIcon} ${chalk.yellow(item.name)} (${chalk.gray(item.type)})`,
113+
);
114+
if (item.description) {
115+
console.log(` ${chalk.gray(item.description)}`);
116+
}
117+
}
118+
}
119+
},
120+
121+
/**
122+
* Install a repository
123+
*/
124+
async install(repoType: string): Promise<void> {
125+
const manager = getRepositoryManager();
126+
127+
// Check if it's a builtin repo
128+
if (!BUILTIN_REPOSITORIES[repoType]) {
129+
console.error(chalk.red(`❌ Unknown repository type: ${repoType}`));
130+
console.log(chalk.gray("\nAvailable types:"));
131+
for (const key of Object.keys(BUILTIN_REPOSITORIES)) {
132+
console.log(chalk.gray(` - ${key}`));
133+
}
134+
return;
135+
}
136+
137+
console.log(chalk.bold(`\n📥 Installing ${repoType} repository...`));
138+
const result = await manager.installRepository(repoType);
139+
140+
if (result.success) {
141+
console.log(
142+
chalk.green(`✅ Installed ${repoType} repository at ${result.path}`),
143+
);
144+
} else {
145+
console.error(chalk.red(`❌ Failed to install: ${result.error}`));
146+
}
147+
},
148+
149+
/**
150+
* Update all installed repositories
151+
*/
152+
async update(): Promise<void> {
153+
const manager = getRepositoryManager();
154+
console.log(chalk.bold("\n🔄 Updating all repositories..."));
155+
const result = await manager.updateRepositories();
156+
157+
if (result.updated.length > 0) {
158+
console.log(chalk.green(`✅ Updated: ${result.updated.join(", ")}`));
159+
}
160+
161+
if (result.errors.length > 0) {
162+
console.error(chalk.red(`❌ Errors:`));
163+
for (const error of result.errors) {
164+
console.error(chalk.red(` - ${error}`));
165+
}
166+
}
167+
168+
if (result.updated.length === 0 && result.errors.length === 0) {
169+
console.log(chalk.gray(" No repositories installed."));
170+
}
171+
},
172+
173+
/**
174+
* Enable an item from a repository
175+
*/
176+
async enable(itemName: string, itemType?: string): Promise<void> {
177+
const manager = getRepositoryManager();
178+
const items = await manager.listAvailableItems();
179+
180+
// Find the item
181+
let item: RepositoryItem | undefined;
182+
if (itemType) {
183+
item = items.find(i => i.name === itemName && i.type === itemType);
184+
} else {
185+
item = items.find(i => i.name === itemName);
186+
}
187+
188+
if (!item) {
189+
console.error(chalk.red(`❌ Item not found: ${itemName}`));
190+
if (itemType) {
191+
console.log(chalk.gray(`\nTry '/repo list' to see available items.`));
192+
} else {
193+
console.log(
194+
chalk.gray(
195+
`\nMultiple items may have this name. Specify type: '/repo enable ${itemName} <type>'`,
196+
),
197+
);
198+
}
199+
return;
200+
}
201+
202+
if (item.enabled) {
203+
console.log(chalk.yellow(`⚠️ Item '${itemName}' is already enabled.`));
204+
return;
205+
}
206+
207+
const success = await manager.enableItem(item);
208+
if (success) {
209+
console.log(chalk.green(`✅ Enabled ${itemName}`));
210+
} else {
211+
console.error(chalk.red(`❌ Failed to enable ${itemName}`));
212+
}
213+
},
214+
215+
/**
216+
* Disable an item from a repository
217+
*/
218+
async disable(itemName: string, itemType?: string): Promise<void> {
219+
const manager = getRepositoryManager();
220+
const items = await manager.listAvailableItems();
221+
222+
// Find the item
223+
let item: RepositoryItem | undefined;
224+
if (itemType) {
225+
item = items.find(i => i.name === itemName && i.type === itemType);
226+
} else {
227+
item = items.find(i => i.name === itemName);
228+
}
229+
230+
if (!item) {
231+
console.error(chalk.red(`❌ Item not found: ${itemName}`));
232+
return;
233+
}
234+
235+
if (!item.enabled) {
236+
console.log(chalk.yellow(`⚠️ Item '${itemName}' is already disabled.`));
237+
return;
238+
}
239+
240+
const success = await manager.disableItem(item);
241+
if (success) {
242+
console.log(chalk.green(`✅ Disabled ${itemName}`));
243+
} else {
244+
console.error(chalk.red(`❌ Failed to disable ${itemName}`));
245+
}
246+
},
247+
248+
/**
249+
* Remove a repository
250+
*/
251+
async remove(repoType: string): Promise<void> {
252+
const manager = getRepositoryManager();
253+
console.log(chalk.bold(`\n🗑️ Removing ${repoType} repository...`));
254+
const result = await manager.removeRepository(repoType);
255+
256+
if (result.success) {
257+
console.log(chalk.green(`✅ Removed ${repoType} repository`));
258+
} else {
259+
console.error(chalk.red(`❌ Failed to remove: ${result.error}`));
260+
}
261+
},
262+
};
263+
264+
/**
265+
* Handle repository commands from the CLI
266+
*/
267+
export async function handleRepositoryCommand(input: string): Promise<boolean> {
268+
const trimmedInput = input.trim();
269+
270+
if (trimmedInput === "/repo" || trimmedInput === "/repo list") {
271+
await repositoryCommands.listInstalled();
272+
return true;
273+
}
274+
275+
if (trimmedInput === "/repo available") {
276+
await repositoryCommands.listAvailable();
277+
return true;
278+
}
279+
280+
if (trimmedInput.startsWith("/repo install ")) {
281+
const repoType = trimmedInput.slice("/repo install ".length).trim();
282+
if (repoType) {
283+
await repositoryCommands.install(repoType);
284+
}
285+
return true;
286+
}
287+
288+
if (trimmedInput === "/repo update") {
289+
await repositoryCommands.update();
290+
return true;
291+
}
292+
293+
if (trimmedInput.startsWith("/repo enable ")) {
294+
const args = trimmedInput.slice("/repo enable ".length).trim().split(" ");
295+
const itemName = args[0];
296+
const itemType = args[1];
297+
if (itemName) {
298+
await repositoryCommands.enable(itemName, itemType);
299+
}
300+
return true;
301+
}
302+
303+
if (trimmedInput.startsWith("/repo disable ")) {
304+
const args = trimmedInput.slice("/repo disable ".length).trim().split(" ");
305+
const itemName = args[0];
306+
const itemType = args[1];
307+
if (itemName) {
308+
await repositoryCommands.disable(itemName, itemType);
309+
}
310+
return true;
311+
}
312+
313+
if (trimmedInput.startsWith("/repo remove ")) {
314+
const repoType = trimmedInput.slice("/repo remove ".length).trim();
315+
if (repoType) {
316+
await repositoryCommands.remove(repoType);
317+
}
318+
return true;
319+
}
320+
321+
return false;
322+
}

0 commit comments

Comments
 (0)