Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/cli/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,18 @@ See [Extensions Documentation](../extensions/index.md) for more details.
| `gemini mcp list` | List all configured MCP servers | `gemini mcp list` |

See [MCP Server Integration](../tools/mcp-server.md) for more details.

## Skills management

| Command | Description | Example |
| -------------------------------- | ------------------------------------- | ------------------------------------------------- |
| `gemini skills list` | List all discovered agent skills | `gemini skills list` |
| `gemini skills install <source>` | Install skill from Git, path, or file | `gemini skills install https://github.com/u/repo` |
| `gemini skills link <path>` | Link local agent skills via symlink | `gemini skills link /path/to/my-skills` |
| `gemini skills uninstall <name>` | Uninstall an agent skill | `gemini skills uninstall my-skill` |
| `gemini skills enable <name>` | Enable an agent skill | `gemini skills enable my-skill` |
| `gemini skills disable <name>` | Disable an agent skill | `gemini skills disable my-skill` |
| `gemini skills enable --all` | Enable all skills | `gemini skills enable --all` |
| `gemini skills disable --all` | Disable all skills | `gemini skills disable --all` |

See [Agent Skills Documentation](./skills.md) for more details.
8 changes: 8 additions & 0 deletions docs/cli/skills.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ locations override lower ones: **Workspace > User > Extension**.
Use the `/skills` slash command to view and manage available expertise:

- `/skills list` (default): Shows all discovered skills and their status.
- `/skills link <path>`: Links agent skills from a local directory via symlink.
- `/skills disable <name>`: Prevents a specific skill from being used.
- `/skills enable <name>`: Re-enables a disabled skill.
- `/skills reload`: Refreshes the list of discovered skills from all tiers.
Expand All @@ -67,6 +68,13 @@ The `gemini skills` command provides management utilities:
# List all discovered skills
gemini skills list

# Link agent skills from a local directory via symlink
# Discovers skills (SKILL.md or */SKILL.md) and creates symlinks in ~/.gemini/skills (user)
gemini skills link /path/to/my-skills-repo

# Link to the workspace scope (.gemini/skills)
gemini skills link /path/to/my-skills-repo --scope workspace

# Install a skill from a Git repository, local directory, or zipped skill file (.skill)
# Uses the user scope by default (~/.gemini/skills)
gemini skills install https://github.com/user/repo.git
Expand Down
25 changes: 24 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions packages/cli/src/commands/skills.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { listCommand } from './skills/list.js';
import { enableCommand } from './skills/enable.js';
import { disableCommand } from './skills/disable.js';
import { installCommand } from './skills/install.js';
import { linkCommand } from './skills/link.js';
import { uninstallCommand } from './skills/uninstall.js';
import { initializeOutputListenersAndFlush } from '../gemini.js';
import { defer } from '../deferred.js';
Expand All @@ -27,6 +28,7 @@ export const skillsCommand: CommandModule = {
.command(defer(enableCommand, 'skills'))
.command(defer(disableCommand, 'skills'))
.command(defer(installCommand, 'skills'))
.command(defer(linkCommand, 'skills'))
.command(defer(uninstallCommand, 'skills'))
.demandCommand(1, 'You need at least one command before continuing.')
.version(false),
Expand Down
69 changes: 69 additions & 0 deletions packages/cli/src/commands/skills/link.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { handleLink, linkCommand } from './link.js';

const mockLinkSkill = vi.hoisted(() => vi.fn());
const mockRequestConsentNonInteractive = vi.hoisted(() => vi.fn());
const mockSkillsConsentString = vi.hoisted(() => vi.fn());

vi.mock('../../utils/skillUtils.js', () => ({
linkSkill: mockLinkSkill,
}));

vi.mock('@google/gemini-cli-core', () => ({
debugLogger: { log: vi.fn(), error: vi.fn() },
}));

vi.mock('../../config/extensions/consent.js', () => ({
requestConsentNonInteractive: mockRequestConsentNonInteractive,
skillsConsentString: mockSkillsConsentString,
}));

import { debugLogger } from '@google/gemini-cli-core';

describe('skills link command', () => {
beforeEach(() => {
vi.clearAllMocks();
vi.spyOn(process, 'exit').mockImplementation(() => undefined as never);
});

describe('linkCommand', () => {
it('should have correct command and describe', () => {
expect(linkCommand.command).toBe('link <path>');
expect(linkCommand.describe).toContain('Links an agent skill');
});
});

it('should call linkSkill with correct arguments', async () => {
const sourcePath = '/source/path';
mockLinkSkill.mockResolvedValue([
{ name: 'test-skill', location: '/dest/path' },
]);

await handleLink({ path: sourcePath, scope: 'user' });

expect(mockLinkSkill).toHaveBeenCalledWith(
sourcePath,
'user',
expect.any(Function),
expect.any(Function),
);
expect(debugLogger.log).toHaveBeenCalledWith(
expect.stringContaining('Successfully linked skills'),
);
});

it('should handle linkSkill failure', async () => {
mockLinkSkill.mockRejectedValue(new Error('Link failed'));

await handleLink({ path: '/some/path' });

expect(debugLogger.error).toHaveBeenCalledWith('Link failed');
expect(process.exit).toHaveBeenCalledWith(1);
});
});
Loading
Loading