mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-02-01 22:48:03 +00:00
feat(skills): promote Agent Skills to stable (#17693)
This commit is contained in:
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"experimental": {
|
||||
"skills": true
|
||||
}
|
||||
}
|
||||
@@ -205,8 +205,8 @@ Slash commands provide meta-level control over the CLI itself.
|
||||
while others require a restart.
|
||||
|
||||
- [**`/skills`**](./skills.md)
|
||||
- **Description:** (Experimental) Manage Agent Skills, which provide on-demand
|
||||
expertise and specialized workflows.
|
||||
- **Description:** Manage Agent Skills, which provide on-demand expertise and
|
||||
specialized workflows.
|
||||
- **Sub-commands:**
|
||||
- **`list`**:
|
||||
- **Description:** List all discovered skills and their current status
|
||||
|
||||
@@ -29,8 +29,8 @@ overview of Gemini CLI, see the [main documentation page](../index.md).
|
||||
in an enterprise environment.
|
||||
- **[Sandboxing](./sandbox.md):** Isolate tool execution in a secure,
|
||||
containerized environment.
|
||||
- **[Agent Skills](./skills.md):** (Experimental) Extend the CLI with
|
||||
specialized expertise and procedural workflows.
|
||||
- **[Agent Skills](./skills.md):** Extend the CLI with specialized expertise and
|
||||
procedural workflows.
|
||||
- **[Telemetry](./telemetry.md):** Configure observability to monitor usage and
|
||||
performance.
|
||||
- **[Token caching](./token-caching.md):** Optimize API costs by caching tokens.
|
||||
|
||||
@@ -115,13 +115,18 @@ they appear in the UI.
|
||||
|
||||
| UI Label | Setting | Description | Default |
|
||||
| ----------------------------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------- | ------- |
|
||||
| Agent Skills | `experimental.skills` | Enable Agent Skills (experimental). | `false` |
|
||||
| Enable Codebase Investigator | `experimental.codebaseInvestigatorSettings.enabled` | Enable the Codebase Investigator agent. | `true` |
|
||||
| Codebase Investigator Max Num Turns | `experimental.codebaseInvestigatorSettings.maxNumTurns` | Maximum number of turns for the Codebase Investigator agent. | `10` |
|
||||
| Use OSC 52 Paste | `experimental.useOSC52Paste` | Use OSC 52 sequence for pasting instead of clipboardy (useful for remote sessions). | `false` |
|
||||
| Enable CLI Help Agent | `experimental.cliHelpAgentSettings.enabled` | Enable the CLI Help Agent. | `true` |
|
||||
| Plan | `experimental.plan` | Enable planning features (Plan Mode and tools). | `false` |
|
||||
|
||||
### Skills
|
||||
|
||||
| UI Label | Setting | Description | Default |
|
||||
| ------------------- | ---------------- | -------------------- | ------- |
|
||||
| Enable Agent Skills | `skills.enabled` | Enable Agent Skills. | `true` |
|
||||
|
||||
### HooksConfig
|
||||
|
||||
| UI Label | Setting | Description | Default |
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
# Agent Skills
|
||||
|
||||
_Note: This is an experimental feature enabled via `experimental.skills`. You
|
||||
can also search for "Skills" within the `/settings` interactive UI to toggle
|
||||
this and manage other skill-related settings._
|
||||
|
||||
Agent Skills allow you to extend Gemini CLI with specialized expertise,
|
||||
procedural workflows, and task-specific resources. Based on the
|
||||
[Agent Skills](https://agentskills.io) open standard, a "skill" is a
|
||||
|
||||
@@ -1,37 +1,10 @@
|
||||
# Getting Started with Agent Skills
|
||||
|
||||
Agent Skills allow you to extend Gemini CLI with specialized expertise. This
|
||||
tutorial will guide you through creating your first skill, enabling it, and
|
||||
using it in a session.
|
||||
tutorial will guide you through creating your first skill and using it in a
|
||||
session.
|
||||
|
||||
## 1. Enable Agent Skills
|
||||
|
||||
Agent Skills are currently an experimental feature and must be enabled in your
|
||||
settings.
|
||||
|
||||
### Via the interactive UI
|
||||
|
||||
1. Start a Gemini CLI session by running `gemini`.
|
||||
2. Type `/settings` to open the interactive settings dialog.
|
||||
3. Search for "Skills".
|
||||
4. Toggle **Agent Skills** to `true`.
|
||||
5. Press `Esc` to save and exit. You may need to restart the CLI for the
|
||||
changes to take effect.
|
||||
|
||||
### Via `settings.json`
|
||||
|
||||
Alternatively, you can manually edit your global settings file at
|
||||
`~/.gemini/settings.json` (create it if it doesn't exist):
|
||||
|
||||
```json
|
||||
{
|
||||
"experimental": {
|
||||
"skills": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Create Your First Skill
|
||||
## 1. Create your first skill
|
||||
|
||||
A skill is a directory containing a `SKILL.md` file. Let's create an **API
|
||||
Auditor** skill that helps you verify if local or remote endpoints are
|
||||
@@ -86,7 +59,7 @@ responding correctly.
|
||||
.catch((e) => console.error(`Result: Failed (${e.message})`));
|
||||
```
|
||||
|
||||
## 3. Verify the Skill is Discovered
|
||||
## 2. Verify the skill is discovered
|
||||
|
||||
Use the `/skills` slash command (or `gemini skills list` from your terminal) to
|
||||
see if Gemini CLI has found your new skill.
|
||||
@@ -99,7 +72,7 @@ In a Gemini CLI session:
|
||||
|
||||
You should see `api-auditor` in the list of available skills.
|
||||
|
||||
## 4. Use the Skill in a Chat
|
||||
## 3. Use the skill in a chat
|
||||
|
||||
Now, let's see the skill in action. Start a new session and ask a question about
|
||||
an endpoint.
|
||||
|
||||
@@ -224,8 +224,6 @@ file in every session where the extension is active.
|
||||
|
||||
## (Optional) Step 6: Add an Agent Skill
|
||||
|
||||
_Note: This is an experimental feature enabled via `experimental.skills`._
|
||||
|
||||
[Agent Skills](../cli/skills.md) let you bundle specialized expertise and
|
||||
procedural workflows. Unlike `GEMINI.md`, which provides persistent context,
|
||||
skills are activated only when needed, saving context tokens.
|
||||
|
||||
@@ -850,11 +850,6 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`experimental.skills`** (boolean):
|
||||
- **Description:** Enable Agent Skills (experimental).
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`experimental.codebaseInvestigatorSettings.enabled`** (boolean):
|
||||
- **Description:** Enable the Codebase Investigator agent.
|
||||
- **Default:** `true`
|
||||
@@ -899,6 +894,11 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
|
||||
#### `skills`
|
||||
|
||||
- **`skills.enabled`** (boolean):
|
||||
- **Description:** Enable Agent Skills.
|
||||
- **Default:** `true`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`skills.disabled`** (array):
|
||||
- **Description:** List of disabled skills.
|
||||
- **Default:** `[]`
|
||||
|
||||
@@ -56,8 +56,8 @@ This documentation is organized into the following sections:
|
||||
commands with `/model`.
|
||||
- **[Sandbox](./cli/sandbox.md):** Isolate tool execution in a secure,
|
||||
containerized environment.
|
||||
- **[Agent Skills](./cli/skills.md):** (Experimental) Extend the CLI with
|
||||
specialized expertise and procedural workflows.
|
||||
- **[Agent Skills](./cli/skills.md):** Extend the CLI with specialized expertise
|
||||
and procedural workflows.
|
||||
- **[Settings](./cli/settings.md):** Configure various aspects of the CLI's
|
||||
behavior and appearance with `/settings`.
|
||||
- **[Telemetry](./cli/telemetry.md):** Overview of telemetry in the CLI.
|
||||
|
||||
@@ -204,21 +204,18 @@
|
||||
{
|
||||
"label": "Hooks",
|
||||
"items": [
|
||||
{ "label": "Getting started", "slug": "docs/hooks/getting-started" },
|
||||
{ "label": "Hook types", "slug": "docs/hooks/hook-types" },
|
||||
{ "label": "API reference", "slug": "docs/hooks/api-reference" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Ecosystem and extensibility",
|
||||
"items": [
|
||||
{ "label": "Agent skills", "slug": "docs/cli/skills" },
|
||||
{
|
||||
"label": "Introduction",
|
||||
"slug": "docs/hooks"
|
||||
},
|
||||
{
|
||||
"label": "Writing hooks",
|
||||
"slug": "docs/hooks/writing-hooks"
|
||||
},
|
||||
{
|
||||
"label": "Hooks reference",
|
||||
"slug": "docs/hooks/reference"
|
||||
},
|
||||
{
|
||||
"label": "Best practices",
|
||||
"slug": "docs/hooks/best-practices"
|
||||
"label": "Sub-agents (experimental)",
|
||||
"slug": "docs/core/subagents"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -91,8 +91,8 @@ Additionally, these tools incorporate:
|
||||
|
||||
- **[MCP servers](./mcp-server.md)**: MCP servers act as a bridge between the
|
||||
Gemini model and your local environment or other services like APIs.
|
||||
- **[Agent Skills](../cli/skills.md)**: (Experimental) On-demand expertise
|
||||
packages that are activated via the `activate_skill` tool to provide
|
||||
specialized guidance and resources.
|
||||
- **[Agent Skills](../cli/skills.md)**: On-demand expertise packages that are
|
||||
activated via the `activate_skill` tool to provide specialized guidance and
|
||||
resources.
|
||||
- **[Sandboxing](../cli/sandbox.md)**: Sandboxing isolates the model and its
|
||||
changes from your environment to reduce potential risk.
|
||||
|
||||
@@ -534,6 +534,42 @@ describe('parseArguments', () => {
|
||||
'Use whoami to write a poem in file poem.md about my username in pig latin and use wc to tell me how many lines are in the poem you wrote.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should set isCommand to true for mcp command', async () => {
|
||||
process.argv = ['node', 'script.js', 'mcp', 'list'];
|
||||
const argv = await parseArguments(createTestMergedSettings());
|
||||
expect(argv.isCommand).toBe(true);
|
||||
});
|
||||
|
||||
it('should set isCommand to true for extensions command', async () => {
|
||||
process.argv = ['node', 'script.js', 'extensions', 'list'];
|
||||
// Extensions command uses experimental settings
|
||||
const settings = createTestMergedSettings({
|
||||
experimental: { extensionManagement: true },
|
||||
});
|
||||
const argv = await parseArguments(settings);
|
||||
expect(argv.isCommand).toBe(true);
|
||||
});
|
||||
|
||||
it('should set isCommand to true for skills command', async () => {
|
||||
process.argv = ['node', 'script.js', 'skills', 'list'];
|
||||
// Skills command enabled by default or via experimental
|
||||
const settings = createTestMergedSettings({
|
||||
skills: { enabled: true },
|
||||
});
|
||||
const argv = await parseArguments(settings);
|
||||
expect(argv.isCommand).toBe(true);
|
||||
});
|
||||
|
||||
it('should set isCommand to true for hooks command', async () => {
|
||||
process.argv = ['node', 'script.js', 'hooks', 'migrate'];
|
||||
// Hooks command enabled via tools settings
|
||||
const settings = createTestMergedSettings({
|
||||
tools: { enableHooks: true },
|
||||
});
|
||||
const argv = await parseArguments(settings);
|
||||
expect(argv.isCommand).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadCliConfig', () => {
|
||||
|
||||
@@ -289,7 +289,7 @@ export async function parseArguments(
|
||||
yargsInstance.command(extensionsCommand);
|
||||
}
|
||||
|
||||
if (settings.experimental?.skills || (settings.skills?.enabled ?? true)) {
|
||||
if (settings.skills?.enabled ?? true) {
|
||||
yargsInstance.command(skillsCommand);
|
||||
}
|
||||
// Register hooks command if hooks are enabled
|
||||
@@ -725,8 +725,7 @@ export async function loadCliConfig(
|
||||
plan: settings.experimental?.plan,
|
||||
enableEventDrivenScheduler:
|
||||
settings.experimental?.enableEventDrivenScheduler,
|
||||
skillsSupport:
|
||||
settings.experimental?.skills || (settings.skills?.enabled ?? true),
|
||||
skillsSupport: settings.skills?.enabled ?? true,
|
||||
disabledSkills: settings.skills?.disabled,
|
||||
experimentalJitContext: settings.experimental?.jitContext,
|
||||
noBrowser: !!process.env['NO_BROWSER'],
|
||||
|
||||
@@ -1452,15 +1452,6 @@ const SETTINGS_SCHEMA = {
|
||||
description: 'Enable Just-In-Time (JIT) context loading.',
|
||||
showInDialog: false,
|
||||
},
|
||||
skills: {
|
||||
type: 'boolean',
|
||||
label: 'Agent Skills',
|
||||
category: 'Experimental',
|
||||
requiresRestart: true,
|
||||
default: false,
|
||||
description: 'Enable Agent Skills (experimental).',
|
||||
showInDialog: true,
|
||||
},
|
||||
codebaseInvestigatorSettings: {
|
||||
type: 'object',
|
||||
label: 'Codebase Investigator Settings',
|
||||
@@ -1615,7 +1606,6 @@ const SETTINGS_SCHEMA = {
|
||||
default: true,
|
||||
description: 'Enable Agent Skills.',
|
||||
showInDialog: true,
|
||||
ignoreInDocs: true,
|
||||
},
|
||||
disabled: {
|
||||
type: 'array',
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { loadCliConfig, parseArguments } from './config.js';
|
||||
import * as trustedFolders from './trustedFolders.js';
|
||||
import { loadServerHierarchicalMemory } from '@google/gemini-cli-core';
|
||||
import { type Settings, createTestMergedSettings } from './settings.js';
|
||||
|
||||
vi.mock('./trustedFolders.js');
|
||||
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import('@google/gemini-cli-core')>();
|
||||
return {
|
||||
...actual,
|
||||
loadServerHierarchicalMemory: vi.fn(),
|
||||
getPty: vi.fn().mockResolvedValue({ name: 'test-pty' }),
|
||||
getVersion: vi.fn().mockResolvedValue('0.0.0-test'),
|
||||
};
|
||||
});
|
||||
|
||||
describe('Agent Skills Backward Compatibility', () => {
|
||||
const originalArgv = process.argv;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
vi.mocked(trustedFolders.isWorkspaceTrusted).mockReturnValue({
|
||||
isTrusted: true,
|
||||
} as unknown as trustedFolders.TrustResult);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.argv = originalArgv;
|
||||
});
|
||||
|
||||
describe('loadCliConfig', () => {
|
||||
it('should default skillsSupport to true when no settings are present', async () => {
|
||||
vi.mocked(loadServerHierarchicalMemory).mockResolvedValue({
|
||||
memoryContent: '',
|
||||
fileCount: 0,
|
||||
filePaths: [],
|
||||
});
|
||||
|
||||
process.argv = ['node', 'gemini'];
|
||||
const settings = createTestMergedSettings({});
|
||||
const config = await loadCliConfig(
|
||||
settings,
|
||||
'test-session',
|
||||
await parseArguments(settings),
|
||||
);
|
||||
expect(
|
||||
(
|
||||
config as unknown as { isSkillsSupportEnabled: () => boolean }
|
||||
).isSkillsSupportEnabled(),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should prioritize skills.enabled=false from settings', async () => {
|
||||
vi.mocked(loadServerHierarchicalMemory).mockResolvedValue({
|
||||
memoryContent: '',
|
||||
fileCount: 0,
|
||||
filePaths: [],
|
||||
});
|
||||
|
||||
const settings = createTestMergedSettings({
|
||||
skills: { enabled: false },
|
||||
} as unknown as Settings);
|
||||
|
||||
process.argv = ['node', 'gemini'];
|
||||
const config = await loadCliConfig(
|
||||
settings,
|
||||
'test-session',
|
||||
await parseArguments(settings),
|
||||
);
|
||||
expect(
|
||||
(
|
||||
config as unknown as { isSkillsSupportEnabled: () => boolean }
|
||||
).isSkillsSupportEnabled(),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should support legacy experimental.skills=true from settings', async () => {
|
||||
vi.mocked(loadServerHierarchicalMemory).mockResolvedValue({
|
||||
memoryContent: '',
|
||||
fileCount: 0,
|
||||
filePaths: [],
|
||||
});
|
||||
|
||||
const settings = createTestMergedSettings({
|
||||
experimental: { skills: true },
|
||||
} as unknown as Settings);
|
||||
|
||||
process.argv = ['node', 'gemini'];
|
||||
const config = await loadCliConfig(
|
||||
settings,
|
||||
'test-session',
|
||||
await parseArguments(settings),
|
||||
);
|
||||
expect(
|
||||
(
|
||||
config as unknown as { isSkillsSupportEnabled: () => boolean }
|
||||
).isSkillsSupportEnabled(),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should prioritize legacy experimental.skills=true over new skills.enabled=false', async () => {
|
||||
vi.mocked(loadServerHierarchicalMemory).mockResolvedValue({
|
||||
memoryContent: '',
|
||||
fileCount: 0,
|
||||
filePaths: [],
|
||||
});
|
||||
|
||||
const settings = createTestMergedSettings({
|
||||
skills: { enabled: false },
|
||||
experimental: { skills: true },
|
||||
} as unknown as Settings);
|
||||
|
||||
process.argv = ['node', 'gemini'];
|
||||
const config = await loadCliConfig(
|
||||
settings,
|
||||
'test-session',
|
||||
await parseArguments(settings),
|
||||
);
|
||||
expect(
|
||||
(
|
||||
config as unknown as { isSkillsSupportEnabled: () => boolean }
|
||||
).isSkillsSupportEnabled(),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should still be enabled by default if legacy experimental.skills is false (since new default is true)', async () => {
|
||||
vi.mocked(loadServerHierarchicalMemory).mockResolvedValue({
|
||||
memoryContent: '',
|
||||
fileCount: 0,
|
||||
filePaths: [],
|
||||
});
|
||||
|
||||
const settings = createTestMergedSettings({
|
||||
experimental: { skills: false },
|
||||
} as unknown as Settings);
|
||||
|
||||
process.argv = ['node', 'gemini'];
|
||||
const config = await loadCliConfig(
|
||||
settings,
|
||||
'test-session',
|
||||
await parseArguments(settings),
|
||||
);
|
||||
expect(
|
||||
(
|
||||
config as unknown as { isSkillsSupportEnabled: () => boolean }
|
||||
).isSkillsSupportEnabled(),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -119,11 +119,12 @@ describe('BuiltinCommandLoader', () => {
|
||||
getEnableHooks: () => false,
|
||||
getEnableHooksUI: () => false,
|
||||
getExtensionsEnabled: vi.fn().mockReturnValue(true),
|
||||
isSkillsSupportEnabled: vi.fn().mockReturnValue(false),
|
||||
isSkillsSupportEnabled: vi.fn().mockReturnValue(true),
|
||||
isAgentsEnabled: vi.fn().mockReturnValue(false),
|
||||
getMcpEnabled: vi.fn().mockReturnValue(true),
|
||||
getSkillManager: vi.fn().mockReturnValue({
|
||||
getAllSkills: vi.fn().mockReturnValue([]),
|
||||
isAdminEnabled: vi.fn().mockReturnValue(true),
|
||||
}),
|
||||
} as unknown as Config;
|
||||
|
||||
@@ -260,11 +261,12 @@ describe('BuiltinCommandLoader profile', () => {
|
||||
getEnableHooks: () => false,
|
||||
getEnableHooksUI: () => false,
|
||||
getExtensionsEnabled: vi.fn().mockReturnValue(true),
|
||||
isSkillsSupportEnabled: vi.fn().mockReturnValue(false),
|
||||
isSkillsSupportEnabled: vi.fn().mockReturnValue(true),
|
||||
isAgentsEnabled: vi.fn().mockReturnValue(false),
|
||||
getMcpEnabled: vi.fn().mockReturnValue(true),
|
||||
getSkillManager: vi.fn().mockReturnValue({
|
||||
getAllSkills: vi.fn().mockReturnValue([]),
|
||||
isAdminEnabled: vi.fn().mockReturnValue(true),
|
||||
}),
|
||||
} as unknown as Config;
|
||||
});
|
||||
|
||||
@@ -634,7 +634,7 @@ export class Config {
|
||||
this.planEnabled = params.plan ?? false;
|
||||
this.enableEventDrivenScheduler =
|
||||
params.enableEventDrivenScheduler ?? false;
|
||||
this.skillsSupport = params.skillsSupport ?? false;
|
||||
this.skillsSupport = params.skillsSupport ?? true;
|
||||
this.disabledSkills = params.disabledSkills ?? [];
|
||||
this.adminSkillsEnabled = params.adminSkillsEnabled ?? true;
|
||||
this.modelAvailabilityService = new ModelAvailabilityService();
|
||||
|
||||
@@ -1421,13 +1421,6 @@
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"skills": {
|
||||
"title": "Agent Skills",
|
||||
"description": "Enable Agent Skills (experimental).",
|
||||
"markdownDescription": "Enable Agent Skills (experimental).\n\n- Category: `Experimental`\n- Requires restart: `yes`\n- Default: `false`",
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"codebaseInvestigatorSettings": {
|
||||
"title": "Codebase Investigator Settings",
|
||||
"description": "Configuration for Codebase Investigator.",
|
||||
|
||||
Reference in New Issue
Block a user