mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-02-01 14:44:29 +00:00
feat(core): support shipping built-in skills with the CLI (#16300)
This commit is contained in:
@@ -11,6 +11,15 @@ import * as path from 'node:path';
|
||||
import { SkillManager } from './skillManager.js';
|
||||
import { Storage } from '../config/storage.js';
|
||||
import { type GeminiCLIExtension } from '../config/config.js';
|
||||
import { loadSkillsFromDir, type SkillDefinition } from './skillLoader.js';
|
||||
|
||||
vi.mock('./skillLoader.js', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('./skillLoader.js')>();
|
||||
return {
|
||||
...actual,
|
||||
loadSkillsFromDir: vi.fn(actual.loadSkillsFromDir),
|
||||
};
|
||||
});
|
||||
|
||||
describe('SkillManager', () => {
|
||||
let testRootDir: string;
|
||||
@@ -71,6 +80,8 @@ description: project-desc
|
||||
vi.spyOn(storage, 'getProjectSkillsDir').mockReturnValue(projectDir);
|
||||
|
||||
const service = new SkillManager();
|
||||
// @ts-expect-error accessing private method for testing
|
||||
vi.spyOn(service, 'discoverBuiltinSkills').mockResolvedValue(undefined);
|
||||
await service.discoverSkills(storage, [mockExtension]);
|
||||
|
||||
const skills = service.getSkills();
|
||||
@@ -126,6 +137,8 @@ description: project-desc
|
||||
vi.spyOn(storage, 'getProjectSkillsDir').mockReturnValue(projectDir);
|
||||
|
||||
const service = new SkillManager();
|
||||
// @ts-expect-error accessing private method for testing
|
||||
vi.spyOn(service, 'discoverBuiltinSkills').mockResolvedValue(undefined);
|
||||
await service.discoverSkills(storage, [mockExtension]);
|
||||
|
||||
const skills = service.getSkills();
|
||||
@@ -138,6 +151,34 @@ description: project-desc
|
||||
expect(service.getSkills()[0].description).toBe('user-desc');
|
||||
});
|
||||
|
||||
it('should discover built-in skills', async () => {
|
||||
const service = new SkillManager();
|
||||
const mockBuiltinSkill: SkillDefinition = {
|
||||
name: 'builtin-skill',
|
||||
description: 'builtin-desc',
|
||||
location: 'builtin-loc',
|
||||
body: 'builtin-body',
|
||||
};
|
||||
|
||||
vi.mocked(loadSkillsFromDir).mockImplementation(async (dir) => {
|
||||
if (dir.endsWith('builtin')) {
|
||||
return [{ ...mockBuiltinSkill }];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
const storage = new Storage('/dummy');
|
||||
vi.spyOn(storage, 'getProjectSkillsDir').mockReturnValue('/non-existent');
|
||||
vi.spyOn(Storage, 'getUserSkillsDir').mockReturnValue('/non-existent');
|
||||
|
||||
await service.discoverSkills(storage);
|
||||
|
||||
const skills = service.getSkills();
|
||||
expect(skills).toHaveLength(1);
|
||||
expect(skills[0].name).toBe('builtin-skill');
|
||||
expect(skills[0].isBuiltin).toBe(true);
|
||||
});
|
||||
|
||||
it('should filter disabled skills in getSkills but not in getAllSkills', async () => {
|
||||
const skillDir = path.join(testRootDir, 'skill1');
|
||||
await fs.mkdir(skillDir, { recursive: true });
|
||||
@@ -156,6 +197,8 @@ description: desc1
|
||||
vi.spyOn(Storage, 'getUserSkillsDir').mockReturnValue('/non-existent');
|
||||
|
||||
const service = new SkillManager();
|
||||
// @ts-expect-error accessing private method for testing
|
||||
vi.spyOn(service, 'discoverBuiltinSkills').mockResolvedValue(undefined);
|
||||
await service.discoverSkills(storage);
|
||||
service.setDisabledSkills(['skill1']);
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import * as path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { Storage } from '../config/storage.js';
|
||||
import { type SkillDefinition, loadSkillsFromDir } from './skillLoader.js';
|
||||
import type { GeminiCLIExtension } from '../config/config.js';
|
||||
@@ -56,9 +58,16 @@ export class SkillManager {
|
||||
* Discovers built-in skills.
|
||||
*/
|
||||
private async discoverBuiltinSkills(): Promise<void> {
|
||||
// Built-in skills can be added here.
|
||||
// For now, this is a placeholder for where built-in skills will be loaded from.
|
||||
// They could be loaded from a specific directory within the package.
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const builtinDir = path.join(__dirname, 'builtin');
|
||||
|
||||
const builtinSkills = await loadSkillsFromDir(builtinDir);
|
||||
|
||||
for (const skill of builtinSkills) {
|
||||
skill.isBuiltin = true;
|
||||
}
|
||||
|
||||
this.addSkillsWithPrecedence(builtinSkills);
|
||||
}
|
||||
|
||||
private addSkillsWithPrecedence(newSkills: SkillDefinition[]): void {
|
||||
|
||||
@@ -62,4 +62,15 @@ if (existsSync(docsSrc)) {
|
||||
console.log('Copied docs to bundle/docs/');
|
||||
}
|
||||
|
||||
// 4. Copy Built-in Skills (packages/core/src/skills/builtin)
|
||||
const builtinSkillsSrc = join(root, 'packages/core/src/skills/builtin');
|
||||
const builtinSkillsDest = join(bundleDir, 'builtin');
|
||||
if (existsSync(builtinSkillsSrc)) {
|
||||
cpSync(builtinSkillsSrc, builtinSkillsDest, {
|
||||
recursive: true,
|
||||
dereference: true,
|
||||
});
|
||||
console.log('Copied built-in skills to bundle/builtin/');
|
||||
}
|
||||
|
||||
console.log('Assets copied to bundle/');
|
||||
|
||||
@@ -74,4 +74,13 @@ if (packageName === 'cli') {
|
||||
}
|
||||
}
|
||||
|
||||
// Copy built-in skills for the core package.
|
||||
if (packageName === 'core') {
|
||||
const builtinSkillsSource = path.join(sourceDir, 'skills', 'builtin');
|
||||
const builtinSkillsTarget = path.join(targetDir, 'skills', 'builtin');
|
||||
if (fs.existsSync(builtinSkillsSource)) {
|
||||
fs.cpSync(builtinSkillsSource, builtinSkillsTarget, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Successfully copied files.');
|
||||
|
||||
Reference in New Issue
Block a user