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: 12 additions & 3 deletions skills/docs/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,12 +216,21 @@ All write tools (`writeText`, `replaceText`, `formatText`) accept an optional

### Finding Documents

Use `docs.find` to search by title. Supports pagination with `pageToken`.
Use `drive.search` with a document MIME type filter to find Google Docs:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah didn't realize there are specific skills that need to be updated with #256. Thanks!


```
drive.search({
query: "mimeType='application/vnd.google-apps.document' and name contains 'Weekly Report'"
})
```

For full-text search across document content, use `fullText contains` instead of
`name contains`.

### Moving Documents

Use `docs.move` to move a document to a named folder. If multiple folders share
the same name, the first match is used.
Use `drive.moveFile` to move a document to a different folder. You can specify
the destination by folder ID or folder name.

## Comments & Suggestions

Expand Down
60 changes: 60 additions & 0 deletions skills/sheets/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
name: sheets
description: >
Activate this skill when the user wants to find, read, or analyze Google
Sheets spreadsheets. Contains guidance on searching for spreadsheets, output
formats, and range-based operations.
---

# Google Sheets Expert

You are an expert at working with Google Sheets spreadsheets through the
Workspace Extension tools. Follow these guidelines when helping users with
spreadsheet tasks.

## Finding Spreadsheets

Use `drive.search` with a Sheets MIME type filter to find spreadsheets:

```
drive.search({
query: "mimeType='application/vnd.google-apps.spreadsheet' and name contains 'Budget'"
})
```

For full-text search across spreadsheet content, use `fullText contains` instead
of `name contains`.

## Reading Data

### Full Spreadsheet

Use `sheets.getText` to read all sheets in a spreadsheet. Choose the output
format based on the use case:

- **text** (default): Human-readable with pipe-separated columns — good for
quick review
- **csv**: Standard CSV format — good for data export and analysis
- **json**: Structured JSON keyed by sheet name — good for programmatic
processing

### Specific Range

Use `sheets.getRange` with A1 notation to read a specific cell range:

```
sheets.getRange({
spreadsheetId: "spreadsheet-id",
range: "Sheet1!A1:D10"
})
```

### Metadata

Use `sheets.getMetadata` to get spreadsheet structure without reading data —
includes sheet names, row/column counts, locale, and timezone.

## ID Handling

- All tools accept Google Drive URLs directly — no manual ID extraction needed
- IDs and URLs are interchangeable in all `spreadsheetId` parameters
57 changes: 57 additions & 0 deletions skills/slides/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
name: slides
description: >
Activate this skill when the user wants to find, read, or extract content from
Google Slides presentations. Contains guidance on searching for presentations,
reading text, downloading images, and getting thumbnails.
---

# Google Slides Expert

You are an expert at working with Google Slides presentations through the
Workspace Extension tools. Follow these guidelines when helping users with
presentation tasks.

## Finding Presentations

Use `drive.search` with a Slides MIME type filter to find presentations:

```
drive.search({
query: "mimeType='application/vnd.google-apps.presentation' and name contains 'Quarterly Review'"
})
```

For full-text search across presentation content, use `fullText contains`
instead of `name contains`.

## Reading Content

### Text Extraction

Use `slides.getText` to extract all text content from a presentation. Text is
organized by slide with clear separators.

### Metadata

Use `slides.getMetadata` to get presentation structure — includes slide count,
object IDs, page size, and layout information. Slide object IDs from metadata
can be used with `slides.getSlideThumbnail`.

## Downloading Images

### All Images

Use `slides.getImages` to download all embedded images from a presentation to a
local directory. Requires an **absolute path** for the output directory.

### Slide Thumbnails

Use `slides.getSlideThumbnail` to download a thumbnail of a specific slide.
Requires the slide's `objectId` (from `slides.getMetadata` or `slides.getText`)
and an **absolute path** for the output file.

## ID Handling

- All tools accept Google Drive URLs directly — no manual ID extraction needed
- IDs and URLs are interchangeable in all `presentationId` parameters
35 changes: 15 additions & 20 deletions workspace-server/WORKSPACE-Context.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,24 +82,15 @@ When creating documents in specific folders:
2. Move it to the target folder with `drive.moveFile`
3. Confirm successful completion

To find Google Docs, use `drive.search` with a document MIME type filter rather
than searching by name alone.
To find Google Docs, Sheets, or Slides, use `drive.search` with a MIME type
filter rather than searching by name alone. Example MIME type queries:

## 📄 Docs, Sheets, and Slides

### Format Selection (Sheets)

Choose output format based on use case:

- **text**: Human-readable, good for quick review
- **csv**: Data export, analysis in other tools
- **json**: Programmatic processing, structured data

### Content Handling

- Docs/Sheets/Slides tools accept URLs directly - no ID extraction needed
- Use markdown for initial document creation when appropriate
- Preserve formatting when reading/modifying content
- Docs:
`mimeType='application/vnd.google-apps.document' and name contains 'query'`
- Sheets:
`mimeType='application/vnd.google-apps.spreadsheet' and name contains 'query'`
- Slides:
`mimeType='application/vnd.google-apps.presentation' and name contains 'query'`

## 🚫 Common Pitfalls to Avoid

Expand Down Expand Up @@ -186,9 +177,13 @@ Choose output format based on use case:

### Google Sheets

- Multiple output formats available
- Range-based operations with A1 notation
- Metadata includes sheet structure information
- See the **Sheets skill** for detailed guidance on finding spreadsheets, output
format selection, and range-based operations.

### Google Slides

- See the **Slides skill** for detailed guidance on finding presentations, text
extraction, image downloads, and slide thumbnails.

### Google Calendar

Expand Down
62 changes: 0 additions & 62 deletions workspace-server/src/__tests__/services/SheetsService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ describe('SheetsService', () => {
let sheetsService: SheetsService;
let mockAuthManager: jest.Mocked<AuthManager>;
let mockSheetsAPI: any;
let mockDriveAPI: any;

beforeEach(() => {
// Clear all mocks before each test
Expand All @@ -45,15 +44,8 @@ describe('SheetsService', () => {
},
};

mockDriveAPI = {
files: {
list: jest.fn(),
},
};

// Mock the google constructors
(google.sheets as jest.Mock) = jest.fn().mockReturnValue(mockSheetsAPI);
(google.drive as jest.Mock) = jest.fn().mockReturnValue(mockDriveAPI);

// Create SheetsService instance
sheetsService = new SheetsService(mockAuthManager);
Expand Down Expand Up @@ -306,60 +298,6 @@ describe('SheetsService', () => {
});
});

describe('find', () => {
it('should find spreadsheets by query', async () => {
const mockResponse = {
data: {
files: [
{ id: 'sheet1', name: 'Spreadsheet 1' },
{ id: 'sheet2', name: 'Spreadsheet 2' },
],
nextPageToken: 'next-token',
},
};

mockDriveAPI.files.list.mockResolvedValue(mockResponse);

const result = await sheetsService.find({ query: 'budget' });
const response = JSON.parse(result.content[0].text);

expect(mockDriveAPI.files.list).toHaveBeenCalledWith({
pageSize: 10,
fields: 'nextPageToken, files(id, name)',
q: "mimeType='application/vnd.google-apps.spreadsheet' and fullText contains 'budget'",
pageToken: undefined,
supportsAllDrives: true,
includeItemsFromAllDrives: true,
});

expect(response.files).toHaveLength(2);
expect(response.files[0].name).toBe('Spreadsheet 1');
expect(response.nextPageToken).toBe('next-token');
});

it('should handle title-specific searches', async () => {
const mockResponse = {
data: {
files: [{ id: 'sheet1', name: 'Q4 Budget' }],
},
};

mockDriveAPI.files.list.mockResolvedValue(mockResponse);

const result = await sheetsService.find({ query: 'title:"Q4 Budget"' });
const response = JSON.parse(result.content[0].text);

expect(mockDriveAPI.files.list).toHaveBeenCalledWith(
expect.objectContaining({
q: "mimeType='application/vnd.google-apps.spreadsheet' and name contains 'Q4 Budget'",
}),
);

expect(response.files).toHaveLength(1);
expect(response.files[0].name).toBe('Q4 Budget');
});
});

describe('getMetadata', () => {
it('should retrieve spreadsheet metadata', async () => {
const mockSpreadsheet = {
Expand Down
64 changes: 0 additions & 64 deletions workspace-server/src/__tests__/services/SlidesService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ describe('SlidesService', () => {
let slidesService: SlidesService;
let mockAuthManager: jest.Mocked<AuthManager>;
let mockSlidesAPI: any;
let mockDriveAPI: any;

beforeEach(() => {
// Clear all mocks before each test
Expand All @@ -62,15 +61,8 @@ describe('SlidesService', () => {
},
};

mockDriveAPI = {
files: {
list: jest.fn(),
},
};

// Mock the google constructors
(google.slides as jest.Mock) = jest.fn().mockReturnValue(mockSlidesAPI);
(google.drive as jest.Mock) = jest.fn().mockReturnValue(mockDriveAPI);

// Create SlidesService instance
slidesService = new SlidesService(mockAuthManager);
Expand Down Expand Up @@ -195,62 +187,6 @@ describe('SlidesService', () => {
});
});

describe('find', () => {
it('should find presentations by query', async () => {
const mockResponse = {
data: {
files: [
{ id: 'pres1', name: 'Presentation 1' },
{ id: 'pres2', name: 'Presentation 2' },
],
nextPageToken: 'next-token',
},
};

mockDriveAPI.files.list.mockResolvedValue(mockResponse);

const result = await slidesService.find({ query: 'test query' });
const response = JSON.parse(result.content[0].text);

expect(mockDriveAPI.files.list).toHaveBeenCalledWith({
pageSize: 10,
fields: 'nextPageToken, files(id, name)',
q: "mimeType='application/vnd.google-apps.presentation' and fullText contains 'test query'",
pageToken: undefined,
supportsAllDrives: true,
includeItemsFromAllDrives: true,
});

expect(response.files).toHaveLength(2);
expect(response.files[0].name).toBe('Presentation 1');
expect(response.nextPageToken).toBe('next-token');
});

it('should handle title-specific searches', async () => {
const mockResponse = {
data: {
files: [{ id: 'pres1', name: 'Specific Title' }],
},
};

mockDriveAPI.files.list.mockResolvedValue(mockResponse);

const result = await slidesService.find({
query: 'title:"Specific Title"',
});
const response = JSON.parse(result.content[0].text);

expect(mockDriveAPI.files.list).toHaveBeenCalledWith(
expect.objectContaining({
q: "mimeType='application/vnd.google-apps.presentation' and name contains 'Specific Title'",
}),
);

expect(response.files).toHaveLength(1);
expect(response.files[0].name).toBe('Specific Title');
});
});

describe('getMetadata', () => {
it('should retrieve presentation metadata', async () => {
const mockPresentation = {
Expand Down
Loading
Loading