diff --git a/.changeset/consumer-recipes.md b/.changeset/consumer-recipes.md new file mode 100644 index 00000000..587dbbc0 --- /dev/null +++ b/.changeset/consumer-recipes.md @@ -0,0 +1,11 @@ +--- +"@googleworkspace/cli": minor +--- + +Add workflow helpers, personas, and 50 consumer-focused recipes + +- Add `gws workflow` subcommand with 5 built-in helpers: `+standup-report`, `+meeting-prep`, `+email-to-task`, `+weekly-digest`, `+file-announce` +- Add 10 agent personas (exec-assistant, project-manager, sales-ops, etc.) with curated skill sets +- Add `docs/skills.md` skills index and `registry/recipes.yaml` with 50 multi-step recipes for Gmail, Drive, Docs, Calendar, and Sheets +- Update README with skills index link and accurate skill count +- Fix lefthook pre-commit to run fmt and clippy sequentially diff --git a/Cargo.lock b/Cargo.lock index 676a04fd..3baa09b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -865,6 +865,7 @@ dependencies = [ "reqwest", "serde", "serde_json", + "serde_yaml", "serial_test", "sha2", "tempfile", @@ -2257,6 +2258,19 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "serial_test" version = "3.4.0" @@ -2797,6 +2811,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "untrusted" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index 84b39af0..f2f34ad3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,8 @@ categories = ["command-line-utilities", "web-programming"] name = "gws" path = "src/main.rs" + + [dependencies] aes-gcm = "0.10" anyhow = "1" @@ -52,6 +54,7 @@ crossterm = "0.29.0" chrono = "0.4.44" keyring = "3.6.3" async-trait = "0.1.89" +serde_yaml = "0.9.34" # The profile that 'cargo dist' will build with diff --git a/README.md b/README.md index 037369f8..47fb8bb8 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ Environment variables can also live in a `.env` file. ## AI Agent Skills -The repo ships 40+ [Agent Skills](https://github.com/vercel-labs/agent-skills) (`SKILL.md` files) — one for every supported API, plus higher-level helpers for common workflows like sending email, triaging a Gmail inbox, or subscribing to calendar events. +The repo ships 100+ Agent Skills (`SKILL.md` files) — one for every supported API, plus higher-level helpers for common workflows and 50 curated recipes for Gmail, Drive, Docs, Calendar, and Sheets. See the full [Skills Index](docs/skills.md) for the complete list. ```bash # Install all skills at once diff --git a/docs/skills.md b/docs/skills.md new file mode 100644 index 00000000..8025e545 --- /dev/null +++ b/docs/skills.md @@ -0,0 +1,139 @@ +# Skills Index + +> Auto-generated by `gws generate-skills`. Do not edit manually. + +## Services + +Core Google Workspace API skills. + +| Skill | Description | +|-------|-------------| +| [gws-shared](../skills/gws-shared/SKILL.md) | gws CLI: Shared patterns for authentication, global flags, and output formatting. | +| [gws-drive](../skills/gws-drive/SKILL.md) | Google Drive: Manage files, folders, and shared drives. | +| [gws-sheets](../skills/gws-sheets/SKILL.md) | Google Sheets: Read and write spreadsheets. | +| [gws-gmail](../skills/gws-gmail/SKILL.md) | Gmail: Send, read, and manage email. | +| [gws-calendar](../skills/gws-calendar/SKILL.md) | Google Calendar: Manage calendars and events. | +| [gws-admin](../skills/gws-admin/SKILL.md) | Google Workspace Admin SDK: Manage users, groups, and devices. | +| [gws-admin-reports](../skills/gws-admin-reports/SKILL.md) | Google Workspace Admin SDK: Audit logs and usage reports. | +| [gws-docs](../skills/gws-docs/SKILL.md) | Read and write Google Docs. | +| [gws-slides](../skills/gws-slides/SKILL.md) | Google Slides: Read and write presentations. | +| [gws-tasks](../skills/gws-tasks/SKILL.md) | Google Tasks: Manage task lists and tasks. | +| [gws-people](../skills/gws-people/SKILL.md) | Google People: Manage contacts and profiles. | +| [gws-chat](../skills/gws-chat/SKILL.md) | Google Chat: Manage Chat spaces and messages. | +| [gws-vault](../skills/gws-vault/SKILL.md) | Google Vault: Manage eDiscovery holds and exports. | +| [gws-groupssettings](../skills/gws-groupssettings/SKILL.md) | Manage Google Groups settings. | +| [gws-reseller](../skills/gws-reseller/SKILL.md) | Google Workspace Reseller: Manage Workspace subscriptions. | +| [gws-licensing](../skills/gws-licensing/SKILL.md) | Google Workspace Enterprise License Manager: Manage product licenses. | +| [gws-apps-script](../skills/gws-apps-script/SKILL.md) | Google Apps Script: Manage and execute Apps Script projects. | +| [gws-classroom](../skills/gws-classroom/SKILL.md) | Google Classroom: Manage classes, rosters, and coursework. | +| [gws-cloudidentity](../skills/gws-cloudidentity/SKILL.md) | Google Cloud Identity: Manage identity groups and memberships. | +| [gws-alertcenter](../skills/gws-alertcenter/SKILL.md) | Google Workspace Alert Center: Manage Workspace security alerts. | +| [gws-forms](../skills/gws-forms/SKILL.md) | Read and write Google Forms. | +| [gws-keep](../skills/gws-keep/SKILL.md) | Manage Google Keep notes. | +| [gws-meet](../skills/gws-meet/SKILL.md) | Manage Google Meet conferences. | +| [gws-events](../skills/gws-events/SKILL.md) | Subscribe to Google Workspace events. | +| [gws-modelarmor](../skills/gws-modelarmor/SKILL.md) | Google Model Armor: Filter user-generated content for safety. | +| [gws-workflow](../skills/gws-workflow/SKILL.md) | Google Workflow: Cross-service productivity workflows. | + +## Helpers + +Shortcut commands for common operations. + +| Skill | Description | +|-------|-------------| +| [gws-drive-upload](../skills/gws-drive-upload/SKILL.md) | Google Drive: Upload a file with automatic metadata. | +| [gws-sheets-append](../skills/gws-sheets-append/SKILL.md) | Google Sheets: Append a row to a spreadsheet. | +| [gws-sheets-read](../skills/gws-sheets-read/SKILL.md) | Google Sheets: Read values from a spreadsheet. | +| [gws-gmail-send](../skills/gws-gmail-send/SKILL.md) | Gmail: Send an email. | +| [gws-gmail-triage](../skills/gws-gmail-triage/SKILL.md) | Gmail: Show unread inbox summary (sender, subject, date). | +| [gws-gmail-watch](../skills/gws-gmail-watch/SKILL.md) | Gmail: Watch for new emails and stream them as NDJSON. | +| [gws-calendar-insert](../skills/gws-calendar-insert/SKILL.md) | Google Calendar: Create a new event. | +| [gws-calendar-agenda](../skills/gws-calendar-agenda/SKILL.md) | Google Calendar: Show upcoming events across all calendars. | +| [gws-docs-write](../skills/gws-docs-write/SKILL.md) | Google Docs: Append text to a document. | +| [gws-chat-send](../skills/gws-chat-send/SKILL.md) | Google Chat: Send a message to a space. | +| [gws-apps-script-push](../skills/gws-apps-script-push/SKILL.md) | Google Apps Script: Upload local files to an Apps Script project. | +| [gws-events-subscribe](../skills/gws-events-subscribe/SKILL.md) | Google Workspace Events: Subscribe to Workspace events and stream them as NDJSON. | +| [gws-events-renew](../skills/gws-events-renew/SKILL.md) | Google Workspace Events: Renew/reactivate Workspace Events subscriptions. | +| [gws-modelarmor-sanitize-prompt](../skills/gws-modelarmor-sanitize-prompt/SKILL.md) | Google Model Armor: Sanitize a user prompt through a Model Armor template. | +| [gws-modelarmor-sanitize-response](../skills/gws-modelarmor-sanitize-response/SKILL.md) | Google Model Armor: Sanitize a model response through a Model Armor template. | +| [gws-modelarmor-create-template](../skills/gws-modelarmor-create-template/SKILL.md) | Google Model Armor: Create a new Model Armor template. | +| [gws-workflow-standup-report](../skills/gws-workflow-standup-report/SKILL.md) | Google Workflow: Today's meetings + open tasks as a standup summary. | +| [gws-workflow-meeting-prep](../skills/gws-workflow-meeting-prep/SKILL.md) | Google Workflow: Prepare for your next meeting: agenda, attendees, and linked docs. | +| [gws-workflow-email-to-task](../skills/gws-workflow-email-to-task/SKILL.md) | Google Workflow: Convert a Gmail message into a Google Tasks entry. | +| [gws-workflow-weekly-digest](../skills/gws-workflow-weekly-digest/SKILL.md) | Google Workflow: Weekly summary: this week's meetings + unread email count. | +| [gws-workflow-file-announce](../skills/gws-workflow-file-announce/SKILL.md) | Google Workflow: Announce a Drive file in a Chat space. | + +## Personas + +Role-based skill bundles. + +| Skill | Description | +|-------|-------------| +| [persona-exec-assistant](../skills/persona-exec-assistant/SKILL.md) | Manage an executive's schedule, inbox, and communications. | +| [persona-project-manager](../skills/persona-project-manager/SKILL.md) | Coordinate projects — track tasks, schedule meetings, and share docs. | +| [persona-hr-coordinator](../skills/persona-hr-coordinator/SKILL.md) | Handle HR workflows — onboarding, announcements, and employee comms. | +| [persona-sales-ops](../skills/persona-sales-ops/SKILL.md) | Manage sales workflows — track deals, schedule calls, client comms. | +| [persona-it-admin](../skills/persona-it-admin/SKILL.md) | Administer IT — manage users, monitor security, configure Workspace. | +| [persona-content-creator](../skills/persona-content-creator/SKILL.md) | Create, organize, and distribute content across Workspace. | +| [persona-customer-support](../skills/persona-customer-support/SKILL.md) | Manage customer support — track tickets, respond, escalate issues. | +| [persona-event-coordinator](../skills/persona-event-coordinator/SKILL.md) | Plan and manage events — scheduling, invitations, and logistics. | +| [persona-team-lead](../skills/persona-team-lead/SKILL.md) | Lead a team — run standups, coordinate tasks, and communicate. | +| [persona-researcher](../skills/persona-researcher/SKILL.md) | Organize research — manage references, notes, and collaboration. | + +## Recipes + +Multi-step task sequences with real commands. + +| Skill | Description | +|-------|-------------| +| [recipe-audit-external-sharing](../skills/recipe-audit-external-sharing/SKILL.md) | Find and review Google Drive files shared outside the organization. | +| [recipe-label-and-archive-emails](../skills/recipe-label-and-archive-emails/SKILL.md) | Apply Gmail labels to matching messages and archive them to keep your inbox clean. | +| [recipe-send-personalized-emails](../skills/recipe-send-personalized-emails/SKILL.md) | Read recipient data from Google Sheets and send personalized Gmail messages to each row. | +| [recipe-draft-email-from-doc](../skills/recipe-draft-email-from-doc/SKILL.md) | Read content from a Google Doc and use it as the body of a Gmail message. | +| [recipe-organize-drive-folder](../skills/recipe-organize-drive-folder/SKILL.md) | Create a Google Drive folder structure and move files into the right locations. | +| [recipe-share-folder-with-team](../skills/recipe-share-folder-with-team/SKILL.md) | Share a Google Drive folder and all its contents with a list of collaborators. | +| [recipe-email-drive-link](../skills/recipe-email-drive-link/SKILL.md) | Share a Google Drive file and email the link with a message to recipients. | +| [recipe-create-doc-from-template](../skills/recipe-create-doc-from-template/SKILL.md) | Copy a Google Docs template, fill in content, and share with collaborators. | +| [recipe-create-expense-tracker](../skills/recipe-create-expense-tracker/SKILL.md) | Set up a Google Sheets spreadsheet for tracking expenses with headers and initial entries. | +| [recipe-copy-sheet-for-new-month](../skills/recipe-copy-sheet-for-new-month/SKILL.md) | Duplicate a Google Sheets template tab for a new month of tracking. | +| [recipe-block-focus-time](../skills/recipe-block-focus-time/SKILL.md) | Create recurring focus time blocks on Google Calendar to protect deep work hours. | +| [recipe-reschedule-meeting](../skills/recipe-reschedule-meeting/SKILL.md) | Move a Google Calendar event to a new time and automatically notify all attendees. | +| [recipe-search-and-export-emails](../skills/recipe-search-and-export-emails/SKILL.md) | Find Gmail messages matching a query and export them for review. | +| [recipe-create-gmail-filter](../skills/recipe-create-gmail-filter/SKILL.md) | Create a Gmail filter to automatically label, star, or categorize incoming messages. | +| [recipe-cancel-and-notify](../skills/recipe-cancel-and-notify/SKILL.md) | Delete a Google Calendar event and send a cancellation email via Gmail. | +| [recipe-schedule-recurring-event](../skills/recipe-schedule-recurring-event/SKILL.md) | Create a recurring Google Calendar event with attendees. | +| [recipe-find-free-time](../skills/recipe-find-free-time/SKILL.md) | Query Google Calendar free/busy status for multiple users to find a meeting slot. | +| [recipe-bulk-download-folder](../skills/recipe-bulk-download-folder/SKILL.md) | List and download all files from a Google Drive folder. | +| [recipe-find-large-files](../skills/recipe-find-large-files/SKILL.md) | Identify large Google Drive files consuming storage quota. | +| [recipe-create-shared-drive](../skills/recipe-create-shared-drive/SKILL.md) | Create a Google Shared Drive and add members with appropriate roles. | +| [recipe-transfer-file-ownership](../skills/recipe-transfer-file-ownership/SKILL.md) | Transfer ownership of Google Drive files from one user to another. | +| [recipe-log-deal-update](../skills/recipe-log-deal-update/SKILL.md) | Append a deal status update to a Google Sheets sales tracking spreadsheet. | +| [recipe-collect-form-responses](../skills/recipe-collect-form-responses/SKILL.md) | Retrieve and review responses from a Google Form. | +| [recipe-post-mortem-setup](../skills/recipe-post-mortem-setup/SKILL.md) | Create a Google Docs post-mortem, schedule a Google Calendar review, and notify via Chat. | +| [recipe-create-task-list](../skills/recipe-create-task-list/SKILL.md) | Set up a new Google Tasks list with initial tasks. | +| [recipe-review-overdue-tasks](../skills/recipe-review-overdue-tasks/SKILL.md) | Find Google Tasks that are past due and need attention. | +| [recipe-deploy-apps-script](../skills/recipe-deploy-apps-script/SKILL.md) | Push local files to a Google Apps Script project. | +| [recipe-watch-drive-changes](../skills/recipe-watch-drive-changes/SKILL.md) | Subscribe to change notifications on a Google Drive file or folder. | +| [recipe-create-classroom-course](../skills/recipe-create-classroom-course/SKILL.md) | Create a Google Classroom course and invite students. | +| [recipe-create-meet-space](../skills/recipe-create-meet-space/SKILL.md) | Create a Google Meet meeting space and share the join link. | +| [recipe-review-meet-participants](../skills/recipe-review-meet-participants/SKILL.md) | Review who attended a Google Meet conference and for how long. | +| [recipe-create-presentation](../skills/recipe-create-presentation/SKILL.md) | Create a new Google Slides presentation and add initial slides. | +| [recipe-triage-security-alerts](../skills/recipe-triage-security-alerts/SKILL.md) | List and review Google Workspace security alerts from Alert Center. | +| [recipe-save-email-attachments](../skills/recipe-save-email-attachments/SKILL.md) | Find Gmail messages with attachments and save them to a Google Drive folder. | +| [recipe-send-team-announcement](../skills/recipe-send-team-announcement/SKILL.md) | Send a team announcement via both Gmail and a Google Chat space. | +| [recipe-create-feedback-form](../skills/recipe-create-feedback-form/SKILL.md) | Create a Google Form for feedback and share it via Gmail. | +| [recipe-sync-contacts-to-sheet](../skills/recipe-sync-contacts-to-sheet/SKILL.md) | Export Google Contacts directory to a Google Sheets spreadsheet. | +| [recipe-share-event-materials](../skills/recipe-share-event-materials/SKILL.md) | Share Google Drive files with all attendees of a Google Calendar event. | +| [recipe-create-vacation-responder](../skills/recipe-create-vacation-responder/SKILL.md) | Enable a Gmail out-of-office auto-reply with a custom message and date range. | +| [recipe-create-events-from-sheet](../skills/recipe-create-events-from-sheet/SKILL.md) | Read event data from a Google Sheets spreadsheet and create Google Calendar entries for each row. | +| [recipe-plan-weekly-schedule](../skills/recipe-plan-weekly-schedule/SKILL.md) | Review your Google Calendar week, identify gaps, and add events to fill them. | +| [recipe-share-doc-and-notify](../skills/recipe-share-doc-and-notify/SKILL.md) | Share a Google Docs document with edit access and email collaborators the link. | +| [recipe-backup-sheet-as-csv](../skills/recipe-backup-sheet-as-csv/SKILL.md) | Export a Google Sheets spreadsheet as a CSV file for local backup or processing. | +| [recipe-save-email-to-doc](../skills/recipe-save-email-to-doc/SKILL.md) | Save a Gmail message body into a Google Doc for archival or reference. | +| [recipe-batch-reply-to-emails](../skills/recipe-batch-reply-to-emails/SKILL.md) | Find Gmail messages matching a query and send a standard reply to each one. | +| [recipe-batch-rename-files](../skills/recipe-batch-rename-files/SKILL.md) | Rename multiple Google Drive files matching a pattern to follow a consistent naming convention. | +| [recipe-compare-sheet-tabs](../skills/recipe-compare-sheet-tabs/SKILL.md) | Read data from two tabs in a Google Sheet to compare and identify differences. | +| [recipe-batch-invite-to-event](../skills/recipe-batch-invite-to-event/SKILL.md) | Add a list of attendees to an existing Google Calendar event and send notifications. | +| [recipe-forward-labeled-emails](../skills/recipe-forward-labeled-emails/SKILL.md) | Find Gmail messages with a specific label and forward them to another address. | +| [recipe-generate-report-from-sheet](../skills/recipe-generate-report-from-sheet/SKILL.md) | Read data from a Google Sheet and create a formatted Google Docs report. | + diff --git a/lefthook.yml b/lefthook.yml index 4c887be3..621cd4ab 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -1,5 +1,5 @@ pre-commit: - parallel: true + parallel: false commands: fmt: glob: "*.rs" diff --git a/registry/personas.yaml b/registry/personas.yaml new file mode 100644 index 00000000..4a5ddc6d --- /dev/null +++ b/registry/personas.yaml @@ -0,0 +1,171 @@ +# Persona Packs — Role-based skill bundles for AI agents +# +# Each persona defines a role-based context with: +# - name: unique id (used as directory name: persona-{name}) +# - title: human-readable name +# - description: when to use this persona +# - services: which gws services this persona commonly uses +# - workflows: which workflow commands are relevant +# - instructions: step-by-step guidance for agents adopting this role +# - tips: useful reminders + +personas: + - name: exec-assistant + title: Executive Assistant + description: "Manage an executive's schedule, inbox, and communications." + services: [gmail, calendar, drive, chat] + workflows: ["+standup-report", "+meeting-prep", "+weekly-digest"] + instructions: + - "Start each day with `gws workflow +standup-report` to get the executive's agenda and open tasks." + - "Before each meeting, run `gws workflow +meeting-prep` to see attendees, description, and linked docs." + - "Triage the inbox with `gws gmail +triage --max 10` — prioritize emails from direct reports and leadership." + - "Schedule meetings with `gws calendar +insert` — always check for conflicts first using `gws calendar +agenda`." + - "Draft replies with `gws gmail +send` — keep tone professional and concise." + tips: + - "Always confirm calendar changes with the executive before committing." + - "Use `--format table` for quick visual scans of agenda and triage output." + - "Check `gws calendar +agenda --week` on Monday mornings for weekly planning." + + - name: project-manager + title: Project Manager + description: "Coordinate projects — track tasks, schedule meetings, and share docs." + services: [drive, sheets, calendar, gmail, chat] + workflows: ["+standup-report", "+weekly-digest", "+file-announce"] + instructions: + - "Start the week with `gws workflow +weekly-digest` for a snapshot of upcoming meetings and unread items." + - "Track project status in Sheets using `gws sheets +append` to log updates." + - "Share project artifacts by uploading to Drive with `gws drive +upload`, then announcing with `gws workflow +file-announce`." + - "Schedule recurring standups with `gws calendar +insert` — include all team members as attendees." + - "Send status update emails to stakeholders with `gws gmail +send`." + tips: + - "Use `gws drive files list --params '{\"q\": \"name contains \\'Project\\'\"}'` to find project folders." + - "Pipe triage output through `jq` for filtering by sender or subject." + - "Use `--dry-run` before any write operations to preview what will happen." + + - name: hr-coordinator + title: HR Coordinator + description: "Handle HR workflows — onboarding, announcements, and employee comms." + services: [gmail, calendar, drive, chat, admin] + workflows: ["+email-to-task", "+file-announce"] + instructions: + - "For new hire onboarding, create calendar events for orientation sessions with `gws calendar +insert`." + - "Upload onboarding docs to a shared Drive folder with `gws drive +upload`." + - "Announce new hires in Chat spaces with `gws workflow +file-announce` to share their profile doc." + - "Convert email requests into tracked tasks with `gws workflow +email-to-task`." + - "Send bulk announcements with `gws gmail +send` — use clear subject lines." + tips: + - "Always use `--sanitize` for PII-sensitive operations." + - "Create a dedicated 'HR Onboarding' calendar for tracking orientation schedules." + - "Use `gws admin` for user account management (creating accounts, resetting passwords)." + + - name: sales-ops + title: Sales Operations + description: "Manage sales workflows — track deals, schedule calls, client comms." + services: [gmail, calendar, sheets, drive] + workflows: ["+meeting-prep", "+email-to-task", "+weekly-digest"] + instructions: + - "Prepare for client calls with `gws workflow +meeting-prep` to review attendees and agenda." + - "Log deal updates in a tracking spreadsheet with `gws sheets +append`." + - "Convert follow-up emails into tasks with `gws workflow +email-to-task`." + - "Share proposals by uploading to Drive with `gws drive +upload`." + - "Get a weekly sales pipeline summary with `gws workflow +weekly-digest`." + tips: + - "Use `gws gmail +triage --query 'from:client-domain.com'` to filter client emails." + - "Schedule follow-up calls immediately after meetings to maintain momentum." + - "Keep all client-facing documents in a dedicated shared Drive folder." + + - name: it-admin + title: IT Administrator + description: "Administer IT — manage users, monitor security, configure Workspace." + services: [admin, gmail, drive, calendar] + workflows: ["+standup-report"] + instructions: + - "Start the day with `gws workflow +standup-report` to review any pending IT requests." + - "Manage user accounts with `gws admin` — create, suspend, or update users." + - "Monitor suspicious login activity and review audit logs." + - "Configure Drive sharing policies to enforce organizational security." + - "Set up group email aliases and distribution lists." + tips: + - "Use `gws admin` extensively — it covers user management, groups, and org units." + - "Always use `--dry-run` before bulk user operations." + - "Review `gws auth status` regularly to verify service account permissions." + + - name: content-creator + title: Content Creator + description: "Create, organize, and distribute content across Workspace." + services: [docs, drive, gmail, chat, slides] + workflows: ["+file-announce"] + instructions: + - "Draft content in Google Docs with `gws docs +write`." + - "Organize content assets in Drive folders — use `gws drive files list` to browse." + - "Share finished content by announcing in Chat with `gws workflow +file-announce`." + - "Send content review requests via email with `gws gmail +send`." + - "Upload media assets to Drive with `gws drive +upload`." + tips: + - "Use `gws docs +write` for quick content updates — it handles the Docs API formatting." + - "Keep a 'Content Calendar' in a shared Sheet for tracking publication schedules." + - "Use `--format yaml` for human-readable output when debugging API responses." + + - name: customer-support + title: Customer Support Agent + description: "Manage customer support — track tickets, respond, escalate issues." + services: [gmail, sheets, chat, calendar] + workflows: ["+email-to-task", "+standup-report"] + instructions: + - "Triage the support inbox with `gws gmail +triage --query 'label:support'`." + - "Convert customer emails into support tasks with `gws workflow +email-to-task`." + - "Log ticket status updates in a tracking sheet with `gws sheets +append`." + - "Escalate urgent issues to the team Chat space." + - "Schedule follow-up calls with customers using `gws calendar +insert`." + tips: + - "Use `gws gmail +triage --labels` to see email categories at a glance." + - "Set up Gmail filters for auto-labeling support requests." + - "Use `--format table` for quick status dashboard views." + + - name: event-coordinator + title: Event Coordinator + description: "Plan and manage events — scheduling, invitations, and logistics." + services: [calendar, gmail, drive, chat, sheets] + workflows: ["+meeting-prep", "+file-announce", "+weekly-digest"] + instructions: + - "Create event calendar entries with `gws calendar +insert` — include location and attendee lists." + - "Prepare event materials and upload to Drive with `gws drive +upload`." + - "Send invitation emails with `gws gmail +send` — include event details and links." + - "Announce updates in Chat spaces with `gws workflow +file-announce`." + - "Track RSVPs and logistics in Sheets with `gws sheets +append`." + tips: + - "Use `gws calendar +agenda --days 30` for long-range event planning." + - "Create a dedicated calendar for each major event series." + - "Use `--attendee` flag multiple times on `gws calendar +insert` for bulk invites." + + - name: team-lead + title: Team Lead + description: "Lead a team — run standups, coordinate tasks, and communicate." + services: [calendar, gmail, chat, drive, sheets] + workflows: ["+standup-report", "+meeting-prep", "+weekly-digest", "+email-to-task"] + instructions: + - "Run daily standups with `gws workflow +standup-report` — share output in team Chat." + - "Prepare for 1:1s with `gws workflow +meeting-prep`." + - "Get weekly snapshots with `gws workflow +weekly-digest`." + - "Delegate email action items with `gws workflow +email-to-task`." + - "Track team OKRs in a shared Sheet with `gws sheets +append`." + tips: + - "Use `gws calendar +agenda --week --format table` for weekly team calendar views." + - "Pipe standup reports to Chat with `gws chat spaces messages create`." + - "Use `--sanitize` for any operations involving sensitive team data." + + - name: researcher + title: Researcher + description: "Organize research — manage references, notes, and collaboration." + services: [drive, docs, sheets, gmail] + workflows: ["+file-announce"] + instructions: + - "Organize research papers and notes in Drive folders." + - "Write research notes and summaries with `gws docs +write`." + - "Track research data in Sheets — use `gws sheets +append` for data logging." + - "Share findings with collaborators via `gws workflow +file-announce`." + - "Request peer reviews via `gws gmail +send`." + tips: + - "Use `gws drive files list` with search queries to find specific documents." + - "Keep a running log of experiments and findings in a shared Sheet." + - "Use `--format csv` when exporting data for analysis tools." diff --git a/registry/recipes.yaml b/registry/recipes.yaml new file mode 100644 index 00000000..b3ac6e0b --- /dev/null +++ b/registry/recipes.yaml @@ -0,0 +1,646 @@ +# Curated Recipe Registry — Real-world Google Workspace workflows +# +# Each recipe defines a reusable multi-step task with: +# - name: unique id (directory: recipe-{name}) +# - title: human-readable name +# - description: compact intent (under 130 chars) +# - category: domain +# - services: which gws services this recipe uses +# - steps: concrete gws commands +# - caution: optional warning for destructive operations + +recipes: + # ============================================================ + # IT / SECURITY / COMPLIANCE + # ============================================================ + - name: audit-external-sharing + title: Audit External Drive Sharing + description: "Find and review Google Drive files shared outside the organization." + category: security + services: [drive] + steps: + - "List externally shared files: `gws drive files list --params '{\"q\": \"visibility = '\\''anyoneWithLink'\\''\"}'`" + - "Check permissions on a file: `gws drive permissions list --params '{\"fileId\": \"FILE_ID\"}'`" + - "Revoke if needed: `gws drive permissions delete --params '{\"fileId\": \"FILE_ID\", \"permissionId\": \"PERM_ID\"}'`" + caution: "Revoking permissions immediately removes access. Confirm with the file owner first." + + # ============================================================ + # GMAIL WORKFLOWS + # ============================================================ + - name: label-and-archive-emails + title: Label and Archive Gmail Threads + description: "Apply Gmail labels to matching messages and archive them to keep your inbox clean." + category: productivity + services: [gmail] + steps: + - "Search for matching emails: `gws gmail users messages list --params '{\"userId\": \"me\", \"q\": \"from:notifications@service.com\"}' --format table`" + - "Apply a label: `gws gmail users messages modify --params '{\"userId\": \"me\", \"id\": \"MESSAGE_ID\"}' --json '{\"addLabelIds\": [\"LABEL_ID\"]}'`" + - "Archive (remove from inbox): `gws gmail users messages modify --params '{\"userId\": \"me\", \"id\": \"MESSAGE_ID\"}' --json '{\"removeLabelIds\": [\"INBOX\"]}'`" + + - name: send-personalized-emails + title: Send Personalized Emails from a Sheet + description: "Read recipient data from Google Sheets and send personalized Gmail messages to each row." + category: productivity + services: [sheets, gmail] + steps: + - "Read recipient list: `gws sheets +read --spreadsheet-id SHEET_ID --range 'Contacts!A2:C'`" + - "For each row, send a personalized email: `gws gmail +send --to recipient@example.com --subject 'Hello, Name' --body 'Hi Name, your report is ready.'`" + + - name: draft-email-from-doc + title: Draft a Gmail Message from a Google Doc + description: "Read content from a Google Doc and use it as the body of a Gmail message." + category: productivity + services: [docs, gmail] + steps: + - "Get the document content: `gws docs documents get --params '{\"documentId\": \"DOC_ID\"}'`" + - "Copy the text from the body content" + - "Send the email: `gws gmail +send --to recipient@example.com --subject 'Newsletter Update' --body 'CONTENT_FROM_DOC'`" + + # ============================================================ + # GOOGLE DRIVE WORKFLOWS + # ============================================================ + - name: organize-drive-folder + title: Organize Files into Google Drive Folders + description: "Create a Google Drive folder structure and move files into the right locations." + category: productivity + services: [drive] + steps: + - "Create a project folder: `gws drive files create --json '{\"name\": \"Q2 Project\", \"mimeType\": \"application/vnd.google-apps.folder\"}'`" + - "Create sub-folders: `gws drive files create --json '{\"name\": \"Documents\", \"mimeType\": \"application/vnd.google-apps.folder\", \"parents\": [\"PARENT_FOLDER_ID\"]}'`" + - "Move existing files into folder: `gws drive files update --params '{\"fileId\": \"FILE_ID\", \"addParents\": \"FOLDER_ID\", \"removeParents\": \"OLD_PARENT_ID\"}'`" + - "Verify structure: `gws drive files list --params '{\"q\": \"FOLDER_ID in parents\"}' --format table`" + + - name: share-folder-with-team + title: Share a Google Drive Folder with a Team + description: "Share a Google Drive folder and all its contents with a list of collaborators." + category: productivity + services: [drive] + steps: + - "Find the folder: `gws drive files list --params '{\"q\": \"name = '\\''Project X'\\'' and mimeType = '\\''application/vnd.google-apps.folder'\\''\"}'`" + - "Share as editor: `gws drive permissions create --params '{\"fileId\": \"FOLDER_ID\"}' --json '{\"role\": \"writer\", \"type\": \"user\", \"emailAddress\": \"colleague@company.com\"}'`" + - "Share as viewer: `gws drive permissions create --params '{\"fileId\": \"FOLDER_ID\"}' --json '{\"role\": \"reader\", \"type\": \"user\", \"emailAddress\": \"stakeholder@company.com\"}'`" + - "Verify permissions: `gws drive permissions list --params '{\"fileId\": \"FOLDER_ID\"}' --format table`" + + - name: email-drive-link + title: Email a Google Drive File Link + description: "Share a Google Drive file and email the link with a message to recipients." + category: productivity + services: [drive, gmail] + steps: + - "Find the file: `gws drive files list --params '{\"q\": \"name = '\\''Quarterly Report'\\''\"}'`" + - "Share the file: `gws drive permissions create --params '{\"fileId\": \"FILE_ID\"}' --json '{\"role\": \"reader\", \"type\": \"user\", \"emailAddress\": \"client@example.com\"}'`" + - "Email the link: `gws gmail +send --to client@example.com --subject 'Quarterly Report' --body 'Hi, please find the report here: https://docs.google.com/document/d/FILE_ID'`" + + # ============================================================ + # GOOGLE DOCS WORKFLOWS + # ============================================================ + - name: create-doc-from-template + title: Create a Google Doc from a Template + description: "Copy a Google Docs template, fill in content, and share with collaborators." + category: productivity + services: [drive, docs] + steps: + - "Copy the template: `gws drive files copy --params '{\"fileId\": \"TEMPLATE_DOC_ID\"}' --json '{\"name\": \"Project Brief - Q2 Launch\"}'`" + - "Get the new doc ID from the response" + - "Add content: `gws docs +write --document-id NEW_DOC_ID --text '## Project: Q2 Launch\n\n### Objective\nLaunch the new feature by end of Q2.'`" + - "Share with team: `gws drive permissions create --params '{\"fileId\": \"NEW_DOC_ID\"}' --json '{\"role\": \"writer\", \"type\": \"user\", \"emailAddress\": \"team@company.com\"}'`" + + # ============================================================ + # GOOGLE SHEETS WORKFLOWS + # ============================================================ + - name: create-expense-tracker + title: Create a Google Sheets Expense Tracker + description: "Set up a Google Sheets spreadsheet for tracking expenses with headers and initial entries." + category: productivity + services: [sheets, drive] + steps: + - "Create spreadsheet: `gws drive files create --json '{\"name\": \"Expense Tracker 2025\", \"mimeType\": \"application/vnd.google-apps.spreadsheet\"}'`" + - "Add headers: `gws sheets +append --spreadsheet-id SHEET_ID --range 'Sheet1' --values '[\"Date\", \"Category\", \"Description\", \"Amount\"]'`" + - "Add first entry: `gws sheets +append --spreadsheet-id SHEET_ID --range 'Sheet1' --values '[\"2025-01-15\", \"Travel\", \"Flight to NYC\", \"450.00\"]'`" + - "Share with manager: `gws drive permissions create --params '{\"fileId\": \"SHEET_ID\"}' --json '{\"role\": \"reader\", \"type\": \"user\", \"emailAddress\": \"manager@company.com\"}'`" + + - name: copy-sheet-for-new-month + title: Copy a Google Sheet for a New Month + description: "Duplicate a Google Sheets template tab for a new month of tracking." + category: productivity + services: [sheets] + steps: + - "Get spreadsheet details: `gws sheets spreadsheets get --params '{\"spreadsheetId\": \"SHEET_ID\"}'`" + - "Copy the template sheet: `gws sheets spreadsheets sheets copyTo --params '{\"spreadsheetId\": \"SHEET_ID\", \"sheetId\": 0}' --json '{\"destinationSpreadsheetId\": \"SHEET_ID\"}'`" + - "Rename the new tab: `gws sheets spreadsheets batchUpdate --params '{\"spreadsheetId\": \"SHEET_ID\"}' --json '{\"requests\": [{\"updateSheetProperties\": {\"properties\": {\"sheetId\": 123, \"title\": \"February 2025\"}, \"fields\": \"title\"}}]}'`" + + # ============================================================ + # GOOGLE CALENDAR WORKFLOWS + # ============================================================ + - name: block-focus-time + title: Block Focus Time on Google Calendar + description: "Create recurring focus time blocks on Google Calendar to protect deep work hours." + category: scheduling + services: [calendar] + steps: + - "Create recurring focus block: `gws calendar events insert --params '{\"calendarId\": \"primary\"}' --json '{\"summary\": \"Focus Time\", \"description\": \"Protected deep work block\", \"start\": {\"dateTime\": \"2025-01-20T09:00:00\", \"timeZone\": \"America/New_York\"}, \"end\": {\"dateTime\": \"2025-01-20T11:00:00\", \"timeZone\": \"America/New_York\"}, \"recurrence\": [\"RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR\"], \"transparency\": \"opaque\"}'`" + - "Verify it shows as busy: `gws calendar +agenda`" + + - name: reschedule-meeting + title: Reschedule a Google Calendar Meeting + description: "Move a Google Calendar event to a new time and automatically notify all attendees." + category: scheduling + services: [calendar] + steps: + - "Find the event: `gws calendar +agenda`" + - "Get event details: `gws calendar events get --params '{\"calendarId\": \"primary\", \"eventId\": \"EVENT_ID\"}'`" + - "Update the time: `gws calendar events patch --params '{\"calendarId\": \"primary\", \"eventId\": \"EVENT_ID\", \"sendUpdates\": \"all\"}' --json '{\"start\": {\"dateTime\": \"2025-01-22T14:00:00\", \"timeZone\": \"America/New_York\"}, \"end\": {\"dateTime\": \"2025-01-22T15:00:00\", \"timeZone\": \"America/New_York\"}}'`" + + # ============================================================ + # EMAIL / GMAIL + # ============================================================ + - name: search-and-export-emails + title: Search and Export Emails + description: "Find Gmail messages matching a query and export them for review." + category: productivity + services: [gmail] + steps: + - "Search for emails: `gws gmail users messages list --params '{\"userId\": \"me\", \"q\": \"from:client@example.com after:2024/01/01\"}'`" + - "Get full message: `gws gmail users messages get --params '{\"userId\": \"me\", \"id\": \"MSG_ID\"}'`" + - "Export results: `gws gmail users messages list --params '{\"userId\": \"me\", \"q\": \"label:project-x\"}' --format json > project-emails.json`" + + - name: create-gmail-filter + title: Create a Gmail Filter + description: "Create a Gmail filter to automatically label, star, or categorize incoming messages." + category: productivity + services: [gmail] + steps: + - "List existing labels: `gws gmail users labels list --params '{\"userId\": \"me\"}' --format table`" + - "Create a new label: `gws gmail users labels create --params '{\"userId\": \"me\"}' --json '{\"name\": \"Receipts\"}'`" + - "Create a filter: `gws gmail users settings filters create --params '{\"userId\": \"me\"}' --json '{\"criteria\": {\"from\": \"receipts@example.com\"}, \"action\": {\"addLabelIds\": [\"LABEL_ID\"], \"removeLabelIds\": [\"INBOX\"]}}'`" + - "Verify filter: `gws gmail users settings filters list --params '{\"userId\": \"me\"}' --format table`" + + # ============================================================ + # CALENDAR / SCHEDULING + # ============================================================ + - name: cancel-and-notify + title: Cancel Meeting and Notify Attendees + description: "Delete a Google Calendar event and send a cancellation email via Gmail." + category: scheduling + services: [calendar, gmail] + steps: + - "Find the meeting: `gws calendar +agenda --format json` and locate the event ID" + - "Delete the event: `gws calendar events delete --params '{\"calendarId\": \"primary\", \"eventId\": \"EVENT_ID\", \"sendUpdates\": \"all\"}'`" + - "Send follow-up: `gws gmail +send --to attendees --subject 'Meeting Cancelled: [Title]' --body 'Apologies, this meeting has been cancelled.'`" + caution: "Deleting with sendUpdates sends cancellation emails to all attendees." + + - name: schedule-recurring-event + title: Schedule a Recurring Meeting + description: "Create a recurring Google Calendar event with attendees." + category: scheduling + services: [calendar] + steps: + - "Create recurring event: `gws calendar events insert --params '{\"calendarId\": \"primary\"}' --json '{\"summary\": \"Weekly Standup\", \"start\": {\"dateTime\": \"2024-03-18T09:00:00\", \"timeZone\": \"America/New_York\"}, \"end\": {\"dateTime\": \"2024-03-18T09:30:00\", \"timeZone\": \"America/New_York\"}, \"recurrence\": [\"RRULE:FREQ=WEEKLY;BYDAY=MO\"], \"attendees\": [{\"email\": \"team@company.com\"}]}'`" + - "Verify it was created: `gws calendar +agenda --days 14 --format table`" + + - name: find-free-time + title: Find Free Time Across Calendars + description: "Query Google Calendar free/busy status for multiple users to find a meeting slot." + category: scheduling + services: [calendar] + steps: + - "Query free/busy: `gws calendar freebusy query --json '{\"timeMin\": \"2024-03-18T08:00:00Z\", \"timeMax\": \"2024-03-18T18:00:00Z\", \"items\": [{\"id\": \"user1@company.com\"}, {\"id\": \"user2@company.com\"}]}'`" + - "Review the output to find overlapping free slots" + - "Create event in the free slot: `gws calendar +insert --summary 'Meeting' --attendees user1@company.com,user2@company.com --start '2024-03-18T14:00:00' --duration 30`" + + # ============================================================ + # DRIVE / FILE MANAGEMENT + # ============================================================ + - name: bulk-download-folder + title: Bulk Download Drive Folder + description: "List and download all files from a Google Drive folder." + category: productivity + services: [drive] + steps: + - "List files in folder: `gws drive files list --params '{\"q\": \"'\\''FOLDER_ID'\\'' in parents\"}' --format json`" + - "Download each file: `gws drive files get --params '{\"fileId\": \"FILE_ID\", \"alt\": \"media\"}' -o filename.ext`" + - "Export Google Docs as PDF: `gws drive files export --params '{\"fileId\": \"FILE_ID\", \"mimeType\": \"application/pdf\"}' -o document.pdf`" + + - name: find-large-files + title: Find Largest Files in Drive + description: "Identify large Google Drive files consuming storage quota." + category: productivity + services: [drive] + steps: + - "List files sorted by size: `gws drive files list --params '{\"orderBy\": \"quotaBytesUsed desc\", \"pageSize\": 20, \"fields\": \"files(id,name,size,mimeType,owners)\"}' --format table`" + - "Review the output and identify files to delete or move" + - "Delete if needed: `gws drive files delete --params '{\"fileId\": \"FILE_ID\"}'`" + caution: "Deleting files is permanent if the trash is emptied. Confirm before deleting." + + - name: create-shared-drive + title: Create and Configure a Shared Drive + description: "Create a Google Shared Drive and add members with appropriate roles." + category: productivity + services: [drive] + steps: + - "Create shared drive: `gws drive drives create --params '{\"requestId\": \"unique-id-123\"}' --json '{\"name\": \"Project X\"}'`" + - "Add a member: `gws drive permissions create --params '{\"fileId\": \"DRIVE_ID\", \"supportsAllDrives\": true}' --json '{\"role\": \"writer\", \"type\": \"user\", \"emailAddress\": \"member@company.com\"}'`" + - "List members: `gws drive permissions list --params '{\"fileId\": \"DRIVE_ID\", \"supportsAllDrives\": true}'`" + + - name: transfer-file-ownership + title: Transfer File Ownership + description: "Transfer ownership of Google Drive files from one user to another." + category: it + services: [drive] + steps: + - "List files owned by the user: `gws drive files list --params '{\"q\": \"'\\''user@company.com'\\'' in owners\"}'`" + - "Transfer ownership: `gws drive permissions create --params '{\"fileId\": \"FILE_ID\", \"transferOwnership\": true}' --json '{\"role\": \"owner\", \"type\": \"user\", \"emailAddress\": \"newowner@company.com\"}'`" + caution: "Transferring ownership is irreversible without the new owner's cooperation." + + # ============================================================ + # SHEETS / DATA + # ============================================================ + - name: log-deal-update + title: Log Deal Update to Sheet + description: "Append a deal status update to a Google Sheets sales tracking spreadsheet." + category: sales + services: [sheets, drive] + steps: + - "Find the tracking sheet: `gws drive files list --params '{\"q\": \"name = '\\''Sales Pipeline'\\'' and mimeType = '\\''application/vnd.google-apps.spreadsheet'\\''\"}'`" + - "Read current data: `gws sheets +read --spreadsheet-id SHEET_ID --range 'Pipeline!A1:F'`" + - "Append new row: `gws sheets +append --spreadsheet-id SHEET_ID --range 'Pipeline' --values '[\"2024-03-15\", \"Acme Corp\", \"Proposal Sent\", \"$50,000\", \"Q2\", \"jdoe\"]'`" + + - name: collect-form-responses + title: Check Form Responses + description: "Retrieve and review responses from a Google Form." + category: productivity + services: [forms] + steps: + - "List forms: `gws forms forms list` (if you don't have the form ID)" + - "Get form details: `gws forms forms get --params '{\"formId\": \"FORM_ID\"}'`" + - "Get responses: `gws forms forms responses list --params '{\"formId\": \"FORM_ID\"}' --format table`" + + # ============================================================ + # CHAT / TEAM COMMUNICATION + # ============================================================ + # ============================================================ + # ENGINEERING + # ============================================================ + - name: post-mortem-setup + title: Set Up Post-Mortem + description: "Create a Google Docs post-mortem, schedule a Google Calendar review, and notify via Chat." + category: engineering + services: [docs, calendar, chat] + steps: + - "Create post-mortem doc: `gws docs +write --title 'Post-Mortem: [Incident]' --body '## Summary\\n\\n## Timeline\\n\\n## Root Cause\\n\\n## Action Items'`" + - "Schedule review meeting: `gws calendar +insert --summary 'Post-Mortem Review: [Incident]' --attendees team@company.com --start 'next monday 14:00' --duration 60`" + - "Notify in Chat: `gws chat +send --space spaces/ENG_SPACE --text '🔍 Post-mortem scheduled for [Incident].'`" + + # ============================================================ + # TASKS + # ============================================================ + - name: create-task-list + title: Create a Task List and Add Tasks + description: "Set up a new Google Tasks list with initial tasks." + category: productivity + services: [tasks] + steps: + - "Create task list: `gws tasks tasklists insert --json '{\"title\": \"Q2 Goals\"}'`" + - "Add a task: `gws tasks tasks insert --params '{\"tasklist\": \"TASKLIST_ID\"}' --json '{\"title\": \"Review Q1 metrics\", \"notes\": \"Pull data from analytics dashboard\", \"due\": \"2024-04-01T00:00:00Z\"}'`" + - "Add another task: `gws tasks tasks insert --params '{\"tasklist\": \"TASKLIST_ID\"}' --json '{\"title\": \"Draft Q2 OKRs\"}'`" + - "List tasks: `gws tasks tasks list --params '{\"tasklist\": \"TASKLIST_ID\"}' --format table`" + + - name: review-overdue-tasks + title: Review Overdue Tasks + description: "Find Google Tasks that are past due and need attention." + category: productivity + services: [tasks] + steps: + - "List task lists: `gws tasks tasklists list --format table`" + - "List tasks with status: `gws tasks tasks list --params '{\"tasklist\": \"TASKLIST_ID\", \"showCompleted\": false}' --format table`" + - "Review due dates and prioritize overdue items" + + # ============================================================ + # CONTACTS / PEOPLE + # ============================================================ + # ============================================================ + # APPS SCRIPT + # ============================================================ + - name: deploy-apps-script + title: Deploy an Apps Script Project + description: "Push local files to a Google Apps Script project." + category: engineering + services: [apps-script] + steps: + - "List existing projects: `gws apps-script projects list --format table`" + - "Get project content: `gws apps-script projects getContent --params '{\"scriptId\": \"SCRIPT_ID\"}'`" + - "Update content: `gws apps-script projects updateContent --params '{\"scriptId\": \"SCRIPT_ID\"}' --json '{\"files\": [{\"name\": \"Code\", \"type\": \"SERVER_JS\", \"source\": \"function main() { ... }\"}]}'`" + - "Create a new version: `gws apps-script projects versions create --params '{\"scriptId\": \"SCRIPT_ID\"}' --json '{\"description\": \"v2 release\"}'`" + + # ============================================================ + # EVENT SUBSCRIPTIONS + # ============================================================ + - name: watch-drive-changes + title: Watch for Drive Changes + description: "Subscribe to change notifications on a Google Drive file or folder." + category: engineering + services: [events] + steps: + - "Create subscription: `gws events subscriptions create --json '{\"targetResource\": \"//drive.googleapis.com/drives/DRIVE_ID\", \"eventTypes\": [\"google.workspace.drive.file.v1.updated\"], \"notificationEndpoint\": {\"pubsubTopic\": \"projects/PROJECT/topics/TOPIC\"}, \"payloadOptions\": {\"includeResource\": true}}'`" + - "List active subscriptions: `gws events subscriptions list`" + - "Renew before expiry: `gws events +renew --subscription SUBSCRIPTION_ID`" + + # ============================================================ + # CLASSROOM + # ============================================================ + - name: create-classroom-course + title: Create a Google Classroom Course + description: "Create a Google Classroom course and invite students." + category: education + services: [classroom] + steps: + - "Create the course: `gws classroom courses create --json '{\"name\": \"Introduction to CS\", \"section\": \"Period 1\", \"room\": \"Room 101\", \"ownerId\": \"me\"}'`" + - "Invite a student: `gws classroom invitations create --json '{\"courseId\": \"COURSE_ID\", \"userId\": \"student@school.edu\", \"role\": \"STUDENT\"}'`" + - "List enrolled students: `gws classroom courses students list --params '{\"courseId\": \"COURSE_ID\"}' --format table`" + + # ============================================================ + # MEET + # ============================================================ + - name: create-meet-space + title: Create a Google Meet Conference + description: "Create a Google Meet meeting space and share the join link." + category: scheduling + services: [meet, gmail] + steps: + - "Create meeting space: `gws meet spaces create --json '{\"config\": {\"accessType\": \"OPEN\"}}'`" + - "Copy the meeting URI from the response" + - "Email the link: `gws gmail +send --to team@company.com --subject 'Join the meeting' --body 'Join here: MEETING_URI'`" + + - name: review-meet-participants + title: Review Google Meet Attendance + description: "Review who attended a Google Meet conference and for how long." + category: productivity + services: [meet] + steps: + - "List recent conferences: `gws meet conferenceRecords list --format table`" + - "List participants: `gws meet conferenceRecords participants list --params '{\"parent\": \"conferenceRecords/CONFERENCE_ID\"}' --format table`" + - "Get session details: `gws meet conferenceRecords participants participantSessions list --params '{\"parent\": \"conferenceRecords/CONFERENCE_ID/participants/PARTICIPANT_ID\"}' --format table`" + + # ============================================================ + # KEEP + # ============================================================ + # ============================================================ + # SLIDES + # ============================================================ + - name: create-presentation + title: Create a Google Slides Presentation + description: "Create a new Google Slides presentation and add initial slides." + category: productivity + services: [slides] + steps: + - "Create presentation: `gws slides presentations create --json '{\"title\": \"Quarterly Review Q2\"}'`" + - "Get the presentation ID from the response" + - "Share with team: `gws drive permissions create --params '{\"fileId\": \"PRESENTATION_ID\"}' --json '{\"role\": \"writer\", \"type\": \"user\", \"emailAddress\": \"team@company.com\"}'`" + + # ============================================================ + # ALERT CENTER + # ============================================================ + - name: triage-security-alerts + title: Triage Google Workspace Security Alerts + description: "List and review Google Workspace security alerts from Alert Center." + category: security + services: [alertcenter] + steps: + - "List active alerts: `gws alertcenter alerts list --format table`" + - "Get alert details: `gws alertcenter alerts get --params '{\"alertId\": \"ALERT_ID\"}'`" + - "Acknowledge an alert: `gws alertcenter alerts undelete --params '{\"alertId\": \"ALERT_ID\"}'`" + + # ============================================================ + # CONSUMER PRODUCTIVITY + # ============================================================ + - name: save-email-attachments + title: Save Gmail Attachments to Google Drive + description: "Find Gmail messages with attachments and save them to a Google Drive folder." + category: productivity + services: [gmail, drive] + steps: + - "Search for emails with attachments: `gws gmail users messages list --params '{\"userId\": \"me\", \"q\": \"has:attachment from:client@example.com\"}' --format table`" + - "Get message details: `gws gmail users messages get --params '{\"userId\": \"me\", \"id\": \"MESSAGE_ID\"}'`" + - "Download attachment: `gws gmail users messages attachments get --params '{\"userId\": \"me\", \"messageId\": \"MESSAGE_ID\", \"id\": \"ATTACHMENT_ID\"}'`" + - "Upload to Drive folder: `gws drive +upload --file ./attachment.pdf --parent FOLDER_ID`" + + # ============================================================ + # CROSS-SERVICE WORKFLOWS + # ============================================================ + - name: send-team-announcement + title: Announce via Gmail and Google Chat + description: "Send a team announcement via both Gmail and a Google Chat space." + category: communication + services: [gmail, chat] + steps: + - "Send email: `gws gmail +send --to team@company.com --subject 'Important Update' --body 'Please review the attached policy changes.'`" + - "Post in Chat: `gws chat +send --space spaces/TEAM_SPACE --text '📢 Important Update: Please check your email for policy changes.'`" + + - name: create-feedback-form + title: Create and Share a Google Form + description: "Create a Google Form for feedback and share it via Gmail." + category: productivity + services: [forms, gmail] + steps: + - "Create form: `gws forms forms create --json '{\"info\": {\"title\": \"Event Feedback\", \"documentTitle\": \"Event Feedback Form\"}}'`" + - "Get the form URL from the response (responderUri field)" + - "Email the form: `gws gmail +send --to attendees@company.com --subject 'Please share your feedback' --body 'Fill out the form: FORM_URL'`" + + - name: sync-contacts-to-sheet + title: Export Google Contacts to Sheets + description: "Export Google Contacts directory to a Google Sheets spreadsheet." + category: productivity + services: [people, sheets] + steps: + - "List contacts: `gws people people listDirectoryPeople --params '{\"readMask\": \"names,emailAddresses,phoneNumbers\", \"sources\": [\"DIRECTORY_SOURCE_TYPE_DOMAIN_PROFILE\"], \"pageSize\": 100}' --format json`" + - "Create a sheet: `gws sheets +append --spreadsheet-id SHEET_ID --range 'Contacts' --values '[\"Name\", \"Email\", \"Phone\"]'`" + - "Append each contact row: `gws sheets +append --spreadsheet-id SHEET_ID --range 'Contacts' --values '[\"Jane Doe\", \"jane@company.com\", \"+1-555-0100\"]'`" + + # ============================================================ + # CONSUMER — COLLABORATION + # ============================================================ + - name: share-event-materials + title: Share Files with Meeting Attendees + description: "Share Google Drive files with all attendees of a Google Calendar event." + category: productivity + services: [calendar, drive] + steps: + - "Get event attendees: `gws calendar events get --params '{\"calendarId\": \"primary\", \"eventId\": \"EVENT_ID\"}'`" + - "Share file with each attendee: `gws drive permissions create --params '{\"fileId\": \"FILE_ID\"}' --json '{\"role\": \"reader\", \"type\": \"user\", \"emailAddress\": \"attendee@company.com\"}'`" + - "Verify sharing: `gws drive permissions list --params '{\"fileId\": \"FILE_ID\"}' --format table`" + # ============================================================ + # GMAIL — ORGANIZATION + # ============================================================ + - name: create-vacation-responder + title: Set Up a Gmail Vacation Responder + description: "Enable a Gmail out-of-office auto-reply with a custom message and date range." + category: productivity + services: [gmail] + steps: + - "Enable vacation responder: `gws gmail users settings updateVacation --params '{\"userId\": \"me\"}' --json '{\"enableAutoReply\": true, \"responseSubject\": \"Out of Office\", \"responseBodyPlainText\": \"I am out of the office until Jan 20. For urgent matters, contact backup@company.com.\", \"restrictToContacts\": false, \"restrictToDomain\": false}'`" + - "Verify settings: `gws gmail users settings getVacation --params '{\"userId\": \"me\"}'`" + - "Disable when back: `gws gmail users settings updateVacation --params '{\"userId\": \"me\"}' --json '{\"enableAutoReply\": false}'`" + + # ============================================================ + # DRIVE — FILE OPERATIONS + # ============================================================ + # ============================================================ + # SHEETS — DATA WORKFLOWS + # ============================================================ + - name: create-events-from-sheet + title: Create Google Calendar Events from a Sheet + description: "Read event data from a Google Sheets spreadsheet and create Google Calendar entries for each row." + category: productivity + services: [sheets, calendar] + steps: + - "Read event data: `gws sheets +read --spreadsheet-id SHEET_ID --range 'Events!A2:D'`" + - "For each row, create a calendar event: `gws calendar +insert --summary 'Team Standup' --start '2025-01-20T09:00' --duration 30 --attendees alice@company.com,bob@company.com`" + + # ============================================================ + # CALENDAR — PLANNING + # ============================================================ + - name: plan-weekly-schedule + title: Plan Your Weekly Google Calendar Schedule + description: "Review your Google Calendar week, identify gaps, and add events to fill them." + category: scheduling + services: [calendar] + steps: + - "Check this week's agenda: `gws calendar +agenda`" + - "Check free/busy for the week: `gws calendar freebusy query --json '{\"timeMin\": \"2025-01-20T00:00:00Z\", \"timeMax\": \"2025-01-25T00:00:00Z\", \"items\": [{\"id\": \"primary\"}]}'`" + - "Add a new event: `gws calendar +insert --summary 'Deep Work Block' --start '2025-01-21T14:00' --duration 120`" + - "Review updated schedule: `gws calendar +agenda`" + # ============================================================ + # MULTI-SERVICE PROJECT SETUP + # ============================================================ + # ============================================================ + # DOCS — COLLABORATION + # ============================================================ + - name: share-doc-and-notify + title: Share a Google Doc and Notify Collaborators + description: "Share a Google Docs document with edit access and email collaborators the link." + category: productivity + services: [drive, docs, gmail] + steps: + - "Find the doc: `gws drive files list --params '{\"q\": \"name contains '\\''Project Brief'\\'' and mimeType = '\\''application/vnd.google-apps.document'\\''\"}'`" + - "Share with editor access: `gws drive permissions create --params '{\"fileId\": \"DOC_ID\"}' --json '{\"role\": \"writer\", \"type\": \"user\", \"emailAddress\": \"reviewer@company.com\"}'`" + - "Email the link: `gws gmail +send --to reviewer@company.com --subject 'Please review: Project Brief' --body 'I have shared the project brief with you: https://docs.google.com/document/d/DOC_ID'`" + + # ============================================================ + # SHEETS — BACKUP + # ============================================================ + - name: backup-sheet-as-csv + title: Export a Google Sheet as CSV + description: "Export a Google Sheets spreadsheet as a CSV file for local backup or processing." + category: productivity + services: [sheets, drive] + steps: + - "Get spreadsheet details: `gws sheets spreadsheets get --params '{\"spreadsheetId\": \"SHEET_ID\"}'`" + - "Export as CSV: `gws drive files export --params '{\"fileId\": \"SHEET_ID\", \"mimeType\": \"text/csv\"}'`" + - "Or read values directly: `gws sheets +read --spreadsheet-id SHEET_ID --range 'Sheet1' --format csv`" + + # ============================================================ + # CALENDAR — TEAM + # ============================================================ + # ============================================================ + # DRIVE — CLEANUP + # ============================================================ + # ============================================================ + # GMAIL — ARCHIVING + # ============================================================ + - name: save-email-to-doc + title: Save a Gmail Message to Google Docs + description: "Save a Gmail message body into a Google Doc for archival or reference." + category: productivity + services: [gmail, docs] + steps: + - "Find the message: `gws gmail users messages list --params '{\"userId\": \"me\", \"q\": \"subject:important from:boss@company.com\"}' --format table`" + - "Get message content: `gws gmail users messages get --params '{\"userId\": \"me\", \"id\": \"MSG_ID\"}'`" + - "Create a doc with the content: `gws docs documents create --json '{\"title\": \"Saved Email - Important Update\"}'`" + - "Write the email body: `gws docs +write --document-id DOC_ID --text 'From: boss@company.com\nSubject: Important Update\n\n[EMAIL BODY]'`" + + # ============================================================ + # SHEETS — FORMULAS + # ============================================================ + # ============================================================ + # REPETITIVE WORKFLOWS + # ============================================================ + # ============================================================ + # GMAIL — AUTOMATED REPLIES + # ============================================================ + - name: batch-reply-to-emails + title: Batch Reply to Similar Gmail Messages + description: "Find Gmail messages matching a query and send a standard reply to each one." + category: productivity + services: [gmail] + steps: + - "Find messages needing replies: `gws gmail users messages list --params '{\"userId\": \"me\", \"q\": \"is:unread from:customers label:support\"}' --format table`" + - "Read a message: `gws gmail users messages get --params '{\"userId\": \"me\", \"id\": \"MSG_ID\"}'`" + - "Send a reply: `gws gmail +send --to sender@example.com --subject 'Re: Your Request' --body 'Thank you for reaching out. We have received your request and will respond within 24 hours.'`" + - "Mark as read: `gws gmail users messages modify --params '{\"userId\": \"me\", \"id\": \"MSG_ID\"}' --json '{\"removeLabelIds\": [\"UNREAD\"]}'`" + + # ============================================================ + # DRIVE — BATCH OPERATIONS + # ============================================================ + - name: batch-rename-files + title: Batch Rename Google Drive Files + description: "Rename multiple Google Drive files matching a pattern to follow a consistent naming convention." + category: productivity + services: [drive] + steps: + - "Find files to rename: `gws drive files list --params '{\"q\": \"name contains '\\''Report'\\''\"}' --format table`" + - "Rename a file: `gws drive files update --params '{\"fileId\": \"FILE_ID\"}' --json '{\"name\": \"2025-Q1 Report - Final\"}'`" + - "Verify the rename: `gws drive files get --params '{\"fileId\": \"FILE_ID\", \"fields\": \"name\"}'`" + + # ============================================================ + # SHEETS — DATA SYNC + # ============================================================ + - name: compare-sheet-tabs + title: Compare Two Google Sheets Tabs + description: "Read data from two tabs in a Google Sheet to compare and identify differences." + category: productivity + services: [sheets] + steps: + - "Read the first tab: `gws sheets +read --spreadsheet-id SHEET_ID --range 'January!A1:D'`" + - "Read the second tab: `gws sheets +read --spreadsheet-id SHEET_ID --range 'February!A1:D'`" + - "Compare the data and identify changes" + + # ============================================================ + # CALENDAR — COORDINATION + # ============================================================ + - name: batch-invite-to-event + title: Add Multiple Attendees to a Calendar Event + description: "Add a list of attendees to an existing Google Calendar event and send notifications." + category: scheduling + services: [calendar] + steps: + - "Get the event: `gws calendar events get --params '{\"calendarId\": \"primary\", \"eventId\": \"EVENT_ID\"}'`" + - "Add attendees: `gws calendar events patch --params '{\"calendarId\": \"primary\", \"eventId\": \"EVENT_ID\", \"sendUpdates\": \"all\"}' --json '{\"attendees\": [{\"email\": \"alice@company.com\"}, {\"email\": \"bob@company.com\"}, {\"email\": \"carol@company.com\"}]}'`" + - "Verify attendees: `gws calendar events get --params '{\"calendarId\": \"primary\", \"eventId\": \"EVENT_ID\"}'`" + + # ============================================================ + # GMAIL — NOTIFICATION ROUTING + # ============================================================ + - name: forward-labeled-emails + title: Forward Labeled Gmail Messages + description: "Find Gmail messages with a specific label and forward them to another address." + category: productivity + services: [gmail] + steps: + - "Find labeled messages: `gws gmail users messages list --params '{\"userId\": \"me\", \"q\": \"label:needs-review\"}' --format table`" + - "Get message content: `gws gmail users messages get --params '{\"userId\": \"me\", \"id\": \"MSG_ID\"}'`" + - "Forward via new email: `gws gmail +send --to manager@company.com --subject 'FW: [Original Subject]' --body 'Forwarding for your review:\n\n[Original Message Body]'`" + + # ============================================================ + # DOCS + SHEETS — CROSS-SERVICE + # ============================================================ + - name: generate-report-from-sheet + title: Generate a Google Docs Report from Sheet Data + description: "Read data from a Google Sheet and create a formatted Google Docs report." + category: productivity + services: [sheets, docs, drive] + steps: + - "Read the data: `gws sheets +read --spreadsheet-id SHEET_ID --range 'Sales!A1:D'`" + - "Create the report doc: `gws docs documents create --json '{\"title\": \"Sales Report - January 2025\"}'`" + - "Write the report: `gws docs +write --document-id DOC_ID --text '## Sales Report - January 2025\n\n### Summary\nTotal deals: 45\nRevenue: $125,000\n\n### Top Deals\n1. Acme Corp - $25,000\n2. Widget Inc - $18,000'`" + - "Share with stakeholders: `gws drive permissions create --params '{\"fileId\": \"DOC_ID\"}' --json '{\"role\": \"reader\", \"type\": \"user\", \"emailAddress\": \"cfo@company.com\"}'`" + diff --git a/skills/gws-admin-reports/SKILL.md b/skills/gws-admin-reports/SKILL.md index a40c0303..fd623ca1 100644 --- a/skills/gws-admin-reports/SKILL.md +++ b/skills/gws-admin-reports/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-admin-reports version: 1.0.0 -description: "USE WHEN the user wants to audit logs and usage reports via the `gws` CLI." +description: "Google Workspace Admin SDK: Audit logs and usage reports." metadata: openclaw: category: "productivity" diff --git a/skills/gws-admin/SKILL.md b/skills/gws-admin/SKILL.md index 0bbe172c..db7ff818 100644 --- a/skills/gws-admin/SKILL.md +++ b/skills/gws-admin/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-admin version: 1.0.0 -description: "USE WHEN the user wants to manage users, groups, and devices via the `gws` CLI." +description: "Google Workspace Admin SDK: Manage users, groups, and devices." metadata: openclaw: category: "productivity" diff --git a/skills/gws-alertcenter/SKILL.md b/skills/gws-alertcenter/SKILL.md index 02de50ae..a003a0b3 100644 --- a/skills/gws-alertcenter/SKILL.md +++ b/skills/gws-alertcenter/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-alertcenter version: 1.0.0 -description: "USE WHEN the user wants to manage workspace security alerts via the `gws` CLI." +description: "Google Workspace Alert Center: Manage Workspace security alerts." metadata: openclaw: category: "productivity" diff --git a/skills/gws-apps-script-push/SKILL.md b/skills/gws-apps-script-push/SKILL.md index ec5c14fd..1866e0ee 100644 --- a/skills/gws-apps-script-push/SKILL.md +++ b/skills/gws-apps-script-push/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-apps-script-push version: 1.0.0 -description: "Upload local files to an Apps Script project" +description: "Google Apps Script: Upload local files to an Apps Script project." metadata: openclaw: category: "productivity" diff --git a/skills/gws-apps-script/SKILL.md b/skills/gws-apps-script/SKILL.md index 4ebec093..75f47445 100644 --- a/skills/gws-apps-script/SKILL.md +++ b/skills/gws-apps-script/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-apps-script version: 1.0.0 -description: "USE WHEN the user wants to manage and execute apps script projects via the `gws` CLI." +description: "Google Apps Script: Manage and execute Apps Script projects." metadata: openclaw: category: "productivity" diff --git a/skills/gws-calendar-agenda/SKILL.md b/skills/gws-calendar-agenda/SKILL.md index c5527ecf..b040b879 100644 --- a/skills/gws-calendar-agenda/SKILL.md +++ b/skills/gws-calendar-agenda/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-calendar-agenda version: 1.0.0 -description: "Show upcoming events across all calendars" +description: "Google Calendar: Show upcoming events across all calendars." metadata: openclaw: category: "productivity" diff --git a/skills/gws-calendar-insert/SKILL.md b/skills/gws-calendar-insert/SKILL.md index 58c882af..bd0aeb2e 100644 --- a/skills/gws-calendar-insert/SKILL.md +++ b/skills/gws-calendar-insert/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-calendar-insert version: 1.0.0 -description: "create a new event" +description: "Google Calendar: Create a new event." metadata: openclaw: category: "productivity" diff --git a/skills/gws-calendar/SKILL.md b/skills/gws-calendar/SKILL.md index 834c89da..31e9004a 100644 --- a/skills/gws-calendar/SKILL.md +++ b/skills/gws-calendar/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-calendar version: 1.0.0 -description: "USE WHEN the user wants to manage calendars and events via the `gws` CLI." +description: "Google Calendar: Manage calendars and events." metadata: openclaw: category: "productivity" diff --git a/skills/gws-chat-send/SKILL.md b/skills/gws-chat-send/SKILL.md index 6a9517e9..80dd5ee0 100644 --- a/skills/gws-chat-send/SKILL.md +++ b/skills/gws-chat-send/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-chat-send version: 1.0.0 -description: "Send a message to a space" +description: "Google Chat: Send a message to a space." metadata: openclaw: category: "productivity" diff --git a/skills/gws-chat/SKILL.md b/skills/gws-chat/SKILL.md index 3be23749..f9df2cb3 100644 --- a/skills/gws-chat/SKILL.md +++ b/skills/gws-chat/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-chat version: 1.0.0 -description: "USE WHEN the user wants to manage chat spaces and messages via the `gws` CLI." +description: "Google Chat: Manage Chat spaces and messages." metadata: openclaw: category: "productivity" diff --git a/skills/gws-classroom/SKILL.md b/skills/gws-classroom/SKILL.md index d8227de5..b9000840 100644 --- a/skills/gws-classroom/SKILL.md +++ b/skills/gws-classroom/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-classroom version: 1.0.0 -description: "USE WHEN the user wants to manage classes, rosters, and coursework via the `gws` CLI." +description: "Google Classroom: Manage classes, rosters, and coursework." metadata: openclaw: category: "productivity" diff --git a/skills/gws-cloudidentity/SKILL.md b/skills/gws-cloudidentity/SKILL.md index 61f68e1a..af924096 100644 --- a/skills/gws-cloudidentity/SKILL.md +++ b/skills/gws-cloudidentity/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-cloudidentity version: 1.0.0 -description: "USE WHEN the user wants to manage identity groups and memberships via the `gws` CLI." +description: "Google Cloud Identity: Manage identity groups and memberships." metadata: openclaw: category: "productivity" diff --git a/skills/gws-docs-write/SKILL.md b/skills/gws-docs-write/SKILL.md index c9e358d0..15e950b8 100644 --- a/skills/gws-docs-write/SKILL.md +++ b/skills/gws-docs-write/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-docs-write version: 1.0.0 -description: "Append text to a document" +description: "Google Docs: Append text to a document." metadata: openclaw: category: "productivity" diff --git a/skills/gws-docs/SKILL.md b/skills/gws-docs/SKILL.md index 78fa9763..6b870613 100644 --- a/skills/gws-docs/SKILL.md +++ b/skills/gws-docs/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-docs version: 1.0.0 -description: "USE WHEN the user wants to read and write google docs via the `gws` CLI." +description: "Read and write Google Docs." metadata: openclaw: category: "productivity" diff --git a/skills/gws-drive-upload/SKILL.md b/skills/gws-drive-upload/SKILL.md index ab6f1350..68f42c7c 100644 --- a/skills/gws-drive-upload/SKILL.md +++ b/skills/gws-drive-upload/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-drive-upload version: 1.0.0 -description: "Upload a file with automatic metadata" +description: "Google Drive: Upload a file with automatic metadata." metadata: openclaw: category: "productivity" diff --git a/skills/gws-drive/SKILL.md b/skills/gws-drive/SKILL.md index ed875383..41d403ac 100644 --- a/skills/gws-drive/SKILL.md +++ b/skills/gws-drive/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-drive version: 1.0.0 -description: "USE WHEN the user wants to manage files, folders, and shared drives via the `gws` CLI." +description: "Google Drive: Manage files, folders, and shared drives." metadata: openclaw: category: "productivity" diff --git a/skills/gws-events-renew/SKILL.md b/skills/gws-events-renew/SKILL.md index 34f4f319..1e59e183 100644 --- a/skills/gws-events-renew/SKILL.md +++ b/skills/gws-events-renew/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-events-renew version: 1.0.0 -description: "Renew/reactivate Workspace Events subscriptions" +description: "Google Workspace Events: Renew/reactivate Workspace Events subscriptions." metadata: openclaw: category: "productivity" diff --git a/skills/gws-events-subscribe/SKILL.md b/skills/gws-events-subscribe/SKILL.md index 29acd7d9..fe96291c 100644 --- a/skills/gws-events-subscribe/SKILL.md +++ b/skills/gws-events-subscribe/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-events-subscribe version: 1.0.0 -description: "Subscribe to Workspace events and stream them as NDJSON" +description: "Google Workspace Events: Subscribe to Workspace events and stream them as NDJSON." metadata: openclaw: category: "productivity" diff --git a/skills/gws-events/SKILL.md b/skills/gws-events/SKILL.md index e1e07494..9693eef2 100644 --- a/skills/gws-events/SKILL.md +++ b/skills/gws-events/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-events version: 1.0.0 -description: "USE WHEN the user wants to subscribe to google workspace events via the `gws` CLI." +description: "Subscribe to Google Workspace events." metadata: openclaw: category: "productivity" diff --git a/skills/gws-forms/SKILL.md b/skills/gws-forms/SKILL.md index 38a9f65a..330965e7 100644 --- a/skills/gws-forms/SKILL.md +++ b/skills/gws-forms/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-forms version: 1.0.0 -description: "USE WHEN the user wants to read and write google forms via the `gws` CLI." +description: "Read and write Google Forms." metadata: openclaw: category: "productivity" diff --git a/skills/gws-gmail-send/SKILL.md b/skills/gws-gmail-send/SKILL.md index d8ff8fd6..fea6a42b 100644 --- a/skills/gws-gmail-send/SKILL.md +++ b/skills/gws-gmail-send/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-gmail-send version: 1.0.0 -description: "Send an email" +description: "Gmail: Send an email." metadata: openclaw: category: "productivity" diff --git a/skills/gws-gmail-triage/SKILL.md b/skills/gws-gmail-triage/SKILL.md index 2d7f4a70..f78dfb02 100644 --- a/skills/gws-gmail-triage/SKILL.md +++ b/skills/gws-gmail-triage/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-gmail-triage version: 1.0.0 -description: "Show unread inbox summary (sender, subject, date)" +description: "Gmail: Show unread inbox summary (sender, subject, date)." metadata: openclaw: category: "productivity" diff --git a/skills/gws-gmail-watch/SKILL.md b/skills/gws-gmail-watch/SKILL.md index c30039ad..c45cb251 100644 --- a/skills/gws-gmail-watch/SKILL.md +++ b/skills/gws-gmail-watch/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-gmail-watch version: 1.0.0 -description: "Watch for new emails and stream them as NDJSON" +description: "Gmail: Watch for new emails and stream them as NDJSON." metadata: openclaw: category: "productivity" diff --git a/skills/gws-gmail/SKILL.md b/skills/gws-gmail/SKILL.md index f7cb7090..d9eee3ef 100644 --- a/skills/gws-gmail/SKILL.md +++ b/skills/gws-gmail/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-gmail version: 1.0.0 -description: "USE WHEN the user wants to send, read, and manage email via the `gws` CLI." +description: "Gmail: Send, read, and manage email." metadata: openclaw: category: "productivity" diff --git a/skills/gws-groupssettings/SKILL.md b/skills/gws-groupssettings/SKILL.md index 30004353..a265e744 100644 --- a/skills/gws-groupssettings/SKILL.md +++ b/skills/gws-groupssettings/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-groupssettings version: 1.0.0 -description: "USE WHEN the user wants to manage google groups settings via the `gws` CLI." +description: "Manage Google Groups settings." metadata: openclaw: category: "productivity" diff --git a/skills/gws-keep/SKILL.md b/skills/gws-keep/SKILL.md index 8e81b0dc..3a3a083e 100644 --- a/skills/gws-keep/SKILL.md +++ b/skills/gws-keep/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-keep version: 1.0.0 -description: "USE WHEN the user wants to manage google keep notes via the `gws` CLI." +description: "Manage Google Keep notes." metadata: openclaw: category: "productivity" diff --git a/skills/gws-licensing/SKILL.md b/skills/gws-licensing/SKILL.md index 27b9b7f6..1c5ab6be 100644 --- a/skills/gws-licensing/SKILL.md +++ b/skills/gws-licensing/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-licensing version: 1.0.0 -description: "USE WHEN the user wants to manage product licenses via the `gws` CLI." +description: "Google Workspace Enterprise License Manager: Manage product licenses." metadata: openclaw: category: "productivity" diff --git a/skills/gws-meet/SKILL.md b/skills/gws-meet/SKILL.md index e3d090f3..99170d01 100644 --- a/skills/gws-meet/SKILL.md +++ b/skills/gws-meet/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-meet version: 1.0.0 -description: "USE WHEN the user wants to manage google meet conferences via the `gws` CLI." +description: "Manage Google Meet conferences." metadata: openclaw: category: "productivity" diff --git a/skills/gws-modelarmor-create-template/SKILL.md b/skills/gws-modelarmor-create-template/SKILL.md index 83bfb50b..745c639f 100644 --- a/skills/gws-modelarmor-create-template/SKILL.md +++ b/skills/gws-modelarmor-create-template/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-modelarmor-create-template version: 1.0.0 -description: "Create a new Model Armor template" +description: "Google Model Armor: Create a new Model Armor template." metadata: openclaw: category: "security" diff --git a/skills/gws-modelarmor-sanitize-prompt/SKILL.md b/skills/gws-modelarmor-sanitize-prompt/SKILL.md index fece02c0..f6c05fda 100644 --- a/skills/gws-modelarmor-sanitize-prompt/SKILL.md +++ b/skills/gws-modelarmor-sanitize-prompt/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-modelarmor-sanitize-prompt version: 1.0.0 -description: "Sanitize a user prompt through a Model Armor template" +description: "Google Model Armor: Sanitize a user prompt through a Model Armor template." metadata: openclaw: category: "security" diff --git a/skills/gws-modelarmor-sanitize-response/SKILL.md b/skills/gws-modelarmor-sanitize-response/SKILL.md index d4d8a62a..901ed2d0 100644 --- a/skills/gws-modelarmor-sanitize-response/SKILL.md +++ b/skills/gws-modelarmor-sanitize-response/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-modelarmor-sanitize-response version: 1.0.0 -description: "Sanitize a model response through a Model Armor template" +description: "Google Model Armor: Sanitize a model response through a Model Armor template." metadata: openclaw: category: "security" diff --git a/skills/gws-modelarmor/SKILL.md b/skills/gws-modelarmor/SKILL.md index 7514c035..0f97c2d4 100644 --- a/skills/gws-modelarmor/SKILL.md +++ b/skills/gws-modelarmor/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-modelarmor version: 1.0.0 -description: "USE WHEN the user wants to filter user-generated content for safety via the `gws` CLI." +description: "Google Model Armor: Filter user-generated content for safety." metadata: openclaw: category: "productivity" diff --git a/skills/gws-people/SKILL.md b/skills/gws-people/SKILL.md index f50ad00f..fd56b771 100644 --- a/skills/gws-people/SKILL.md +++ b/skills/gws-people/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-people version: 1.0.0 -description: "USE WHEN the user wants to manage contacts and profiles via the `gws` CLI." +description: "Google People: Manage contacts and profiles." metadata: openclaw: category: "productivity" diff --git a/skills/gws-reseller/SKILL.md b/skills/gws-reseller/SKILL.md index c36b73f7..f6239a9f 100644 --- a/skills/gws-reseller/SKILL.md +++ b/skills/gws-reseller/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-reseller version: 1.0.0 -description: "USE WHEN the user wants to manage workspace subscriptions via the `gws` CLI." +description: "Google Workspace Reseller: Manage Workspace subscriptions." metadata: openclaw: category: "productivity" diff --git a/skills/gws-shared/SKILL.md b/skills/gws-shared/SKILL.md index 3b22e6a2..1e4f938a 100644 --- a/skills/gws-shared/SKILL.md +++ b/skills/gws-shared/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-shared version: 1.0.0 -description: "Shared patterns, authentication, and global flags for all gws commands." +description: "gws CLI: Shared patterns for authentication, global flags, and output formatting." metadata: openclaw: category: "productivity" diff --git a/skills/gws-sheets-append/SKILL.md b/skills/gws-sheets-append/SKILL.md index 58daae6d..1d1fbccc 100644 --- a/skills/gws-sheets-append/SKILL.md +++ b/skills/gws-sheets-append/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-sheets-append version: 1.0.0 -description: "Append a row to a spreadsheet" +description: "Google Sheets: Append a row to a spreadsheet." metadata: openclaw: category: "productivity" diff --git a/skills/gws-sheets-read/SKILL.md b/skills/gws-sheets-read/SKILL.md index 5223be7d..4b23bed2 100644 --- a/skills/gws-sheets-read/SKILL.md +++ b/skills/gws-sheets-read/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-sheets-read version: 1.0.0 -description: "Read values from a spreadsheet" +description: "Google Sheets: Read values from a spreadsheet." metadata: openclaw: category: "productivity" diff --git a/skills/gws-sheets/SKILL.md b/skills/gws-sheets/SKILL.md index 6428ece7..83a7cb47 100644 --- a/skills/gws-sheets/SKILL.md +++ b/skills/gws-sheets/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-sheets version: 1.0.0 -description: "USE WHEN the user wants to read and write spreadsheets via the `gws` CLI." +description: "Google Sheets: Read and write spreadsheets." metadata: openclaw: category: "productivity" diff --git a/skills/gws-slides/SKILL.md b/skills/gws-slides/SKILL.md index 63258aba..eba76467 100644 --- a/skills/gws-slides/SKILL.md +++ b/skills/gws-slides/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-slides version: 1.0.0 -description: "USE WHEN the user wants to read and write presentations via the `gws` CLI." +description: "Google Slides: Read and write presentations." metadata: openclaw: category: "productivity" diff --git a/skills/gws-tasks/SKILL.md b/skills/gws-tasks/SKILL.md index 8f1cb6b1..6f3c502a 100644 --- a/skills/gws-tasks/SKILL.md +++ b/skills/gws-tasks/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-tasks version: 1.0.0 -description: "USE WHEN the user wants to manage task lists and tasks via the `gws` CLI." +description: "Google Tasks: Manage task lists and tasks." metadata: openclaw: category: "productivity" diff --git a/skills/gws-vault/SKILL.md b/skills/gws-vault/SKILL.md index 94323184..117fe3d9 100644 --- a/skills/gws-vault/SKILL.md +++ b/skills/gws-vault/SKILL.md @@ -1,7 +1,7 @@ --- name: gws-vault version: 1.0.0 -description: "USE WHEN the user wants to manage ediscovery holds and exports via the `gws` CLI." +description: "Google Vault: Manage eDiscovery holds and exports." metadata: openclaw: category: "productivity" diff --git a/skills/gws-workflow-email-to-task/SKILL.md b/skills/gws-workflow-email-to-task/SKILL.md new file mode 100644 index 00000000..8fb6ba96 --- /dev/null +++ b/skills/gws-workflow-email-to-task/SKILL.md @@ -0,0 +1,47 @@ +--- +name: gws-workflow-email-to-task +version: 1.0.0 +description: "Google Workflow: Convert a Gmail message into a Google Tasks entry." +metadata: + openclaw: + category: "productivity" + requires: + bins: ["gws"] + cliHelp: "gws workflow +email-to-task --help" +--- + +# workflow +email-to-task + +> **PREREQUISITE:** Read `../gws-shared/SKILL.md` for auth, global flags, and security rules. If missing, run `gws generate-skills` to create it. + +Convert a Gmail message into a Google Tasks entry + +## Usage + +```bash +gws workflow +email-to-task --message-id +``` + +## Flags + +| Flag | Required | Default | Description | +|------|----------|---------|-------------| +| `--message-id` | ✓ | — | Gmail message ID to convert | +| `--tasklist` | — | @default | Task list ID (default: @default) | + +## Examples + +```bash +gws workflow +email-to-task --message-id MSG_ID +gws workflow +email-to-task --message-id MSG_ID --tasklist LIST_ID +``` + +## Tips + +- Reads the email subject as the task title and snippet as notes. +- Creates a new task — confirm with the user before executing. + +## See Also + +- [gws-shared](../gws-shared/SKILL.md) — Global flags and auth +- [gws-workflow](../gws-workflow/SKILL.md) — All cross-service productivity workflows commands diff --git a/skills/gws-workflow-file-announce/SKILL.md b/skills/gws-workflow-file-announce/SKILL.md new file mode 100644 index 00000000..5653d13f --- /dev/null +++ b/skills/gws-workflow-file-announce/SKILL.md @@ -0,0 +1,50 @@ +--- +name: gws-workflow-file-announce +version: 1.0.0 +description: "Google Workflow: Announce a Drive file in a Chat space." +metadata: + openclaw: + category: "productivity" + requires: + bins: ["gws"] + cliHelp: "gws workflow +file-announce --help" +--- + +# workflow +file-announce + +> **PREREQUISITE:** Read `../gws-shared/SKILL.md` for auth, global flags, and security rules. If missing, run `gws generate-skills` to create it. + +Announce a Drive file in a Chat space + +## Usage + +```bash +gws workflow +file-announce --file-id --space +``` + +## Flags + +| Flag | Required | Default | Description | +|------|----------|---------|-------------| +| `--file-id` | ✓ | — | Drive file ID to announce | +| `--space` | ✓ | — | Chat space name (e.g. spaces/SPACE_ID) | +| `--message` | — | — | Custom announcement message | +| `--format` | — | — | Output format: json (default), table, yaml, csv | + +## Examples + +```bash +gws workflow +file-announce --file-id FILE_ID --space spaces/ABC123 +gws workflow +file-announce --file-id FILE_ID --space spaces/ABC123 --message 'Check this out!' +``` + +## Tips + +- This is a write command — sends a Chat message. +- Use `gws drive +upload` first to upload the file, then announce it here. +- Fetches the file name from Drive to build the announcement. + +## See Also + +- [gws-shared](../gws-shared/SKILL.md) — Global flags and auth +- [gws-workflow](../gws-workflow/SKILL.md) — All cross-service productivity workflows commands diff --git a/skills/gws-workflow-meeting-prep/SKILL.md b/skills/gws-workflow-meeting-prep/SKILL.md new file mode 100644 index 00000000..4a127c0c --- /dev/null +++ b/skills/gws-workflow-meeting-prep/SKILL.md @@ -0,0 +1,47 @@ +--- +name: gws-workflow-meeting-prep +version: 1.0.0 +description: "Google Workflow: Prepare for your next meeting: agenda, attendees, and linked docs." +metadata: + openclaw: + category: "productivity" + requires: + bins: ["gws"] + cliHelp: "gws workflow +meeting-prep --help" +--- + +# workflow +meeting-prep + +> **PREREQUISITE:** Read `../gws-shared/SKILL.md` for auth, global flags, and security rules. If missing, run `gws generate-skills` to create it. + +Prepare for your next meeting: agenda, attendees, and linked docs + +## Usage + +```bash +gws workflow +meeting-prep +``` + +## Flags + +| Flag | Required | Default | Description | +|------|----------|---------|-------------| +| `--calendar` | — | primary | Calendar ID (default: primary) | +| `--format` | — | — | Output format: json (default), table, yaml, csv | + +## Examples + +```bash +gws workflow +meeting-prep +gws workflow +meeting-prep --calendar Work +``` + +## Tips + +- Read-only — never modifies data. +- Shows the next upcoming event with attendees and description. + +## See Also + +- [gws-shared](../gws-shared/SKILL.md) — Global flags and auth +- [gws-workflow](../gws-workflow/SKILL.md) — All cross-service productivity workflows commands diff --git a/skills/gws-workflow-standup-report/SKILL.md b/skills/gws-workflow-standup-report/SKILL.md new file mode 100644 index 00000000..dce76f33 --- /dev/null +++ b/skills/gws-workflow-standup-report/SKILL.md @@ -0,0 +1,46 @@ +--- +name: gws-workflow-standup-report +version: 1.0.0 +description: "Google Workflow: Today's meetings + open tasks as a standup summary." +metadata: + openclaw: + category: "productivity" + requires: + bins: ["gws"] + cliHelp: "gws workflow +standup-report --help" +--- + +# workflow +standup-report + +> **PREREQUISITE:** Read `../gws-shared/SKILL.md` for auth, global flags, and security rules. If missing, run `gws generate-skills` to create it. + +Today's meetings + open tasks as a standup summary + +## Usage + +```bash +gws workflow +standup-report +``` + +## Flags + +| Flag | Required | Default | Description | +|------|----------|---------|-------------| +| `--format` | — | — | Output format: json (default), table, yaml, csv | + +## Examples + +```bash +gws workflow +standup-report +gws workflow +standup-report --format table +``` + +## Tips + +- Read-only — never modifies data. +- Combines calendar agenda (today) with tasks list. + +## See Also + +- [gws-shared](../gws-shared/SKILL.md) — Global flags and auth +- [gws-workflow](../gws-workflow/SKILL.md) — All cross-service productivity workflows commands diff --git a/skills/gws-workflow-weekly-digest/SKILL.md b/skills/gws-workflow-weekly-digest/SKILL.md new file mode 100644 index 00000000..0c076e12 --- /dev/null +++ b/skills/gws-workflow-weekly-digest/SKILL.md @@ -0,0 +1,46 @@ +--- +name: gws-workflow-weekly-digest +version: 1.0.0 +description: "Google Workflow: Weekly summary: this week's meetings + unread email count." +metadata: + openclaw: + category: "productivity" + requires: + bins: ["gws"] + cliHelp: "gws workflow +weekly-digest --help" +--- + +# workflow +weekly-digest + +> **PREREQUISITE:** Read `../gws-shared/SKILL.md` for auth, global flags, and security rules. If missing, run `gws generate-skills` to create it. + +Weekly summary: this week's meetings + unread email count + +## Usage + +```bash +gws workflow +weekly-digest +``` + +## Flags + +| Flag | Required | Default | Description | +|------|----------|---------|-------------| +| `--format` | — | — | Output format: json (default), table, yaml, csv | + +## Examples + +```bash +gws workflow +weekly-digest +gws workflow +weekly-digest --format table +``` + +## Tips + +- Read-only — never modifies data. +- Combines calendar agenda (week) with gmail triage summary. + +## See Also + +- [gws-shared](../gws-shared/SKILL.md) — Global flags and auth +- [gws-workflow](../gws-workflow/SKILL.md) — All cross-service productivity workflows commands diff --git a/skills/gws-workflow/SKILL.md b/skills/gws-workflow/SKILL.md new file mode 100644 index 00000000..2325894e --- /dev/null +++ b/skills/gws-workflow/SKILL.md @@ -0,0 +1,44 @@ +--- +name: gws-workflow +version: 1.0.0 +description: "Google Workflow: Cross-service productivity workflows." +metadata: + openclaw: + category: "productivity" + requires: + bins: ["gws"] + cliHelp: "gws workflow --help" +--- + +# workflow (v1) + +> **PREREQUISITE:** Read `../gws-shared/SKILL.md` for auth, global flags, and security rules. If missing, run `gws generate-skills` to create it. + +```bash +gws workflow [flags] +``` + +## Helper Commands + +| Command | Description | +|---------|-------------| +| [`+standup-report`](../gws-workflow-standup-report/SKILL.md) | Today's meetings + open tasks as a standup summary | +| [`+meeting-prep`](../gws-workflow-meeting-prep/SKILL.md) | Prepare for your next meeting: agenda, attendees, and linked docs | +| [`+email-to-task`](../gws-workflow-email-to-task/SKILL.md) | Convert a Gmail message into a Google Tasks entry | +| [`+weekly-digest`](../gws-workflow-weekly-digest/SKILL.md) | Weekly summary: this week's meetings + unread email count | +| [`+file-announce`](../gws-workflow-file-announce/SKILL.md) | Announce a Drive file in a Chat space | + +## Discovering Commands + +Before calling any API method, inspect it: + +```bash +# Browse resources and methods +gws workflow --help + +# Inspect a method's required params, types, and defaults +gws schema workflow.. +``` + +Use `gws schema` output to build your `--params` and `--json` flags. + diff --git a/skills/persona-content-creator/SKILL.md b/skills/persona-content-creator/SKILL.md new file mode 100644 index 00000000..da33a81e --- /dev/null +++ b/skills/persona-content-creator/SKILL.md @@ -0,0 +1,33 @@ +--- +name: persona-content-creator +version: 1.0.0 +description: "Create, organize, and distribute content across Workspace." +metadata: + openclaw: + category: "persona" + requires: + bins: ["gws"] + skills: ["gws-docs", "gws-drive", "gws-gmail", "gws-chat", "gws-slides"] +--- + +# Content Creator + +> **PREREQUISITE:** Load the following utility skills to operate as this persona: `gws-docs`, `gws-drive`, `gws-gmail`, `gws-chat`, `gws-slides` + +Create, organize, and distribute content across Workspace. + +## Relevant Workflows +- `gws workflow +file-announce` + +## Instructions +- Draft content in Google Docs with `gws docs +write`. +- Organize content assets in Drive folders — use `gws drive files list` to browse. +- Share finished content by announcing in Chat with `gws workflow +file-announce`. +- Send content review requests via email with `gws gmail +send`. +- Upload media assets to Drive with `gws drive +upload`. + +## Tips +- Use `gws docs +write` for quick content updates — it handles the Docs API formatting. +- Keep a 'Content Calendar' in a shared Sheet for tracking publication schedules. +- Use `--format yaml` for human-readable output when debugging API responses. + diff --git a/skills/persona-customer-support/SKILL.md b/skills/persona-customer-support/SKILL.md new file mode 100644 index 00000000..c78725cb --- /dev/null +++ b/skills/persona-customer-support/SKILL.md @@ -0,0 +1,34 @@ +--- +name: persona-customer-support +version: 1.0.0 +description: "Manage customer support — track tickets, respond, escalate issues." +metadata: + openclaw: + category: "persona" + requires: + bins: ["gws"] + skills: ["gws-gmail", "gws-sheets", "gws-chat", "gws-calendar"] +--- + +# Customer Support Agent + +> **PREREQUISITE:** Load the following utility skills to operate as this persona: `gws-gmail`, `gws-sheets`, `gws-chat`, `gws-calendar` + +Manage customer support — track tickets, respond, escalate issues. + +## Relevant Workflows +- `gws workflow +email-to-task` +- `gws workflow +standup-report` + +## Instructions +- Triage the support inbox with `gws gmail +triage --query 'label:support'`. +- Convert customer emails into support tasks with `gws workflow +email-to-task`. +- Log ticket status updates in a tracking sheet with `gws sheets +append`. +- Escalate urgent issues to the team Chat space. +- Schedule follow-up calls with customers using `gws calendar +insert`. + +## Tips +- Use `gws gmail +triage --labels` to see email categories at a glance. +- Set up Gmail filters for auto-labeling support requests. +- Use `--format table` for quick status dashboard views. + diff --git a/skills/persona-event-coordinator/SKILL.md b/skills/persona-event-coordinator/SKILL.md new file mode 100644 index 00000000..3efb75a2 --- /dev/null +++ b/skills/persona-event-coordinator/SKILL.md @@ -0,0 +1,35 @@ +--- +name: persona-event-coordinator +version: 1.0.0 +description: "Plan and manage events — scheduling, invitations, and logistics." +metadata: + openclaw: + category: "persona" + requires: + bins: ["gws"] + skills: ["gws-calendar", "gws-gmail", "gws-drive", "gws-chat", "gws-sheets"] +--- + +# Event Coordinator + +> **PREREQUISITE:** Load the following utility skills to operate as this persona: `gws-calendar`, `gws-gmail`, `gws-drive`, `gws-chat`, `gws-sheets` + +Plan and manage events — scheduling, invitations, and logistics. + +## Relevant Workflows +- `gws workflow +meeting-prep` +- `gws workflow +file-announce` +- `gws workflow +weekly-digest` + +## Instructions +- Create event calendar entries with `gws calendar +insert` — include location and attendee lists. +- Prepare event materials and upload to Drive with `gws drive +upload`. +- Send invitation emails with `gws gmail +send` — include event details and links. +- Announce updates in Chat spaces with `gws workflow +file-announce`. +- Track RSVPs and logistics in Sheets with `gws sheets +append`. + +## Tips +- Use `gws calendar +agenda --days 30` for long-range event planning. +- Create a dedicated calendar for each major event series. +- Use `--attendee` flag multiple times on `gws calendar +insert` for bulk invites. + diff --git a/skills/persona-exec-assistant/SKILL.md b/skills/persona-exec-assistant/SKILL.md new file mode 100644 index 00000000..f0aed66e --- /dev/null +++ b/skills/persona-exec-assistant/SKILL.md @@ -0,0 +1,35 @@ +--- +name: persona-exec-assistant +version: 1.0.0 +description: "Manage an executive's schedule, inbox, and communications." +metadata: + openclaw: + category: "persona" + requires: + bins: ["gws"] + skills: ["gws-gmail", "gws-calendar", "gws-drive", "gws-chat"] +--- + +# Executive Assistant + +> **PREREQUISITE:** Load the following utility skills to operate as this persona: `gws-gmail`, `gws-calendar`, `gws-drive`, `gws-chat` + +Manage an executive's schedule, inbox, and communications. + +## Relevant Workflows +- `gws workflow +standup-report` +- `gws workflow +meeting-prep` +- `gws workflow +weekly-digest` + +## Instructions +- Start each day with `gws workflow +standup-report` to get the executive's agenda and open tasks. +- Before each meeting, run `gws workflow +meeting-prep` to see attendees, description, and linked docs. +- Triage the inbox with `gws gmail +triage --max 10` — prioritize emails from direct reports and leadership. +- Schedule meetings with `gws calendar +insert` — always check for conflicts first using `gws calendar +agenda`. +- Draft replies with `gws gmail +send` — keep tone professional and concise. + +## Tips +- Always confirm calendar changes with the executive before committing. +- Use `--format table` for quick visual scans of agenda and triage output. +- Check `gws calendar +agenda --week` on Monday mornings for weekly planning. + diff --git a/skills/persona-hr-coordinator/SKILL.md b/skills/persona-hr-coordinator/SKILL.md new file mode 100644 index 00000000..92177ff4 --- /dev/null +++ b/skills/persona-hr-coordinator/SKILL.md @@ -0,0 +1,34 @@ +--- +name: persona-hr-coordinator +version: 1.0.0 +description: "Handle HR workflows — onboarding, announcements, and employee comms." +metadata: + openclaw: + category: "persona" + requires: + bins: ["gws"] + skills: ["gws-gmail", "gws-calendar", "gws-drive", "gws-chat", "gws-admin"] +--- + +# HR Coordinator + +> **PREREQUISITE:** Load the following utility skills to operate as this persona: `gws-gmail`, `gws-calendar`, `gws-drive`, `gws-chat`, `gws-admin` + +Handle HR workflows — onboarding, announcements, and employee comms. + +## Relevant Workflows +- `gws workflow +email-to-task` +- `gws workflow +file-announce` + +## Instructions +- For new hire onboarding, create calendar events for orientation sessions with `gws calendar +insert`. +- Upload onboarding docs to a shared Drive folder with `gws drive +upload`. +- Announce new hires in Chat spaces with `gws workflow +file-announce` to share their profile doc. +- Convert email requests into tracked tasks with `gws workflow +email-to-task`. +- Send bulk announcements with `gws gmail +send` — use clear subject lines. + +## Tips +- Always use `--sanitize` for PII-sensitive operations. +- Create a dedicated 'HR Onboarding' calendar for tracking orientation schedules. +- Use `gws admin` for user account management (creating accounts, resetting passwords). + diff --git a/skills/persona-it-admin/SKILL.md b/skills/persona-it-admin/SKILL.md new file mode 100644 index 00000000..1c63616e --- /dev/null +++ b/skills/persona-it-admin/SKILL.md @@ -0,0 +1,33 @@ +--- +name: persona-it-admin +version: 1.0.0 +description: "Administer IT — manage users, monitor security, configure Workspace." +metadata: + openclaw: + category: "persona" + requires: + bins: ["gws"] + skills: ["gws-admin", "gws-gmail", "gws-drive", "gws-calendar"] +--- + +# IT Administrator + +> **PREREQUISITE:** Load the following utility skills to operate as this persona: `gws-admin`, `gws-gmail`, `gws-drive`, `gws-calendar` + +Administer IT — manage users, monitor security, configure Workspace. + +## Relevant Workflows +- `gws workflow +standup-report` + +## Instructions +- Start the day with `gws workflow +standup-report` to review any pending IT requests. +- Manage user accounts with `gws admin` — create, suspend, or update users. +- Monitor suspicious login activity and review audit logs. +- Configure Drive sharing policies to enforce organizational security. +- Set up group email aliases and distribution lists. + +## Tips +- Use `gws admin` extensively — it covers user management, groups, and org units. +- Always use `--dry-run` before bulk user operations. +- Review `gws auth status` regularly to verify service account permissions. + diff --git a/skills/persona-project-manager/SKILL.md b/skills/persona-project-manager/SKILL.md new file mode 100644 index 00000000..f76e2e3c --- /dev/null +++ b/skills/persona-project-manager/SKILL.md @@ -0,0 +1,35 @@ +--- +name: persona-project-manager +version: 1.0.0 +description: "Coordinate projects — track tasks, schedule meetings, and share docs." +metadata: + openclaw: + category: "persona" + requires: + bins: ["gws"] + skills: ["gws-drive", "gws-sheets", "gws-calendar", "gws-gmail", "gws-chat"] +--- + +# Project Manager + +> **PREREQUISITE:** Load the following utility skills to operate as this persona: `gws-drive`, `gws-sheets`, `gws-calendar`, `gws-gmail`, `gws-chat` + +Coordinate projects — track tasks, schedule meetings, and share docs. + +## Relevant Workflows +- `gws workflow +standup-report` +- `gws workflow +weekly-digest` +- `gws workflow +file-announce` + +## Instructions +- Start the week with `gws workflow +weekly-digest` for a snapshot of upcoming meetings and unread items. +- Track project status in Sheets using `gws sheets +append` to log updates. +- Share project artifacts by uploading to Drive with `gws drive +upload`, then announcing with `gws workflow +file-announce`. +- Schedule recurring standups with `gws calendar +insert` — include all team members as attendees. +- Send status update emails to stakeholders with `gws gmail +send`. + +## Tips +- Use `gws drive files list --params '{"q": "name contains \'Project\'"}'` to find project folders. +- Pipe triage output through `jq` for filtering by sender or subject. +- Use `--dry-run` before any write operations to preview what will happen. + diff --git a/skills/persona-researcher/SKILL.md b/skills/persona-researcher/SKILL.md new file mode 100644 index 00000000..e97bbac2 --- /dev/null +++ b/skills/persona-researcher/SKILL.md @@ -0,0 +1,33 @@ +--- +name: persona-researcher +version: 1.0.0 +description: "Organize research — manage references, notes, and collaboration." +metadata: + openclaw: + category: "persona" + requires: + bins: ["gws"] + skills: ["gws-drive", "gws-docs", "gws-sheets", "gws-gmail"] +--- + +# Researcher + +> **PREREQUISITE:** Load the following utility skills to operate as this persona: `gws-drive`, `gws-docs`, `gws-sheets`, `gws-gmail` + +Organize research — manage references, notes, and collaboration. + +## Relevant Workflows +- `gws workflow +file-announce` + +## Instructions +- Organize research papers and notes in Drive folders. +- Write research notes and summaries with `gws docs +write`. +- Track research data in Sheets — use `gws sheets +append` for data logging. +- Share findings with collaborators via `gws workflow +file-announce`. +- Request peer reviews via `gws gmail +send`. + +## Tips +- Use `gws drive files list` with search queries to find specific documents. +- Keep a running log of experiments and findings in a shared Sheet. +- Use `--format csv` when exporting data for analysis tools. + diff --git a/skills/persona-sales-ops/SKILL.md b/skills/persona-sales-ops/SKILL.md new file mode 100644 index 00000000..d541cefb --- /dev/null +++ b/skills/persona-sales-ops/SKILL.md @@ -0,0 +1,35 @@ +--- +name: persona-sales-ops +version: 1.0.0 +description: "Manage sales workflows — track deals, schedule calls, client comms." +metadata: + openclaw: + category: "persona" + requires: + bins: ["gws"] + skills: ["gws-gmail", "gws-calendar", "gws-sheets", "gws-drive"] +--- + +# Sales Operations + +> **PREREQUISITE:** Load the following utility skills to operate as this persona: `gws-gmail`, `gws-calendar`, `gws-sheets`, `gws-drive` + +Manage sales workflows — track deals, schedule calls, client comms. + +## Relevant Workflows +- `gws workflow +meeting-prep` +- `gws workflow +email-to-task` +- `gws workflow +weekly-digest` + +## Instructions +- Prepare for client calls with `gws workflow +meeting-prep` to review attendees and agenda. +- Log deal updates in a tracking spreadsheet with `gws sheets +append`. +- Convert follow-up emails into tasks with `gws workflow +email-to-task`. +- Share proposals by uploading to Drive with `gws drive +upload`. +- Get a weekly sales pipeline summary with `gws workflow +weekly-digest`. + +## Tips +- Use `gws gmail +triage --query 'from:client-domain.com'` to filter client emails. +- Schedule follow-up calls immediately after meetings to maintain momentum. +- Keep all client-facing documents in a dedicated shared Drive folder. + diff --git a/skills/persona-team-lead/SKILL.md b/skills/persona-team-lead/SKILL.md new file mode 100644 index 00000000..e4758ff6 --- /dev/null +++ b/skills/persona-team-lead/SKILL.md @@ -0,0 +1,36 @@ +--- +name: persona-team-lead +version: 1.0.0 +description: "Lead a team — run standups, coordinate tasks, and communicate." +metadata: + openclaw: + category: "persona" + requires: + bins: ["gws"] + skills: ["gws-calendar", "gws-gmail", "gws-chat", "gws-drive", "gws-sheets"] +--- + +# Team Lead + +> **PREREQUISITE:** Load the following utility skills to operate as this persona: `gws-calendar`, `gws-gmail`, `gws-chat`, `gws-drive`, `gws-sheets` + +Lead a team — run standups, coordinate tasks, and communicate. + +## Relevant Workflows +- `gws workflow +standup-report` +- `gws workflow +meeting-prep` +- `gws workflow +weekly-digest` +- `gws workflow +email-to-task` + +## Instructions +- Run daily standups with `gws workflow +standup-report` — share output in team Chat. +- Prepare for 1:1s with `gws workflow +meeting-prep`. +- Get weekly snapshots with `gws workflow +weekly-digest`. +- Delegate email action items with `gws workflow +email-to-task`. +- Track team OKRs in a shared Sheet with `gws sheets +append`. + +## Tips +- Use `gws calendar +agenda --week --format table` for weekly team calendar views. +- Pipe standup reports to Chat with `gws chat spaces messages create`. +- Use `--sanitize` for any operations involving sensitive team data. + diff --git a/skills/recipe-audit-external-sharing/SKILL.md b/skills/recipe-audit-external-sharing/SKILL.md new file mode 100644 index 00000000..2dcb8d05 --- /dev/null +++ b/skills/recipe-audit-external-sharing/SKILL.md @@ -0,0 +1,28 @@ +--- +name: recipe-audit-external-sharing +version: 1.0.0 +description: "Find and review Google Drive files shared outside the organization." +metadata: + openclaw: + category: "recipe" + domain: "security" + requires: + bins: ["gws"] + skills: ["gws-drive"] +--- + +# Audit External Drive Sharing + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-drive` + +Find and review Google Drive files shared outside the organization. + +> [!CAUTION] +> Revoking permissions immediately removes access. Confirm with the file owner first. + +## Steps + +1. List externally shared files: `gws drive files list --params '{"q": "visibility = '\''anyoneWithLink'\''"}'` +2. Check permissions on a file: `gws drive permissions list --params '{"fileId": "FILE_ID"}'` +3. Revoke if needed: `gws drive permissions delete --params '{"fileId": "FILE_ID", "permissionId": "PERM_ID"}'` + diff --git a/skills/recipe-backup-sheet-as-csv/SKILL.md b/skills/recipe-backup-sheet-as-csv/SKILL.md new file mode 100644 index 00000000..c2c38a31 --- /dev/null +++ b/skills/recipe-backup-sheet-as-csv/SKILL.md @@ -0,0 +1,25 @@ +--- +name: recipe-backup-sheet-as-csv +version: 1.0.0 +description: "Export a Google Sheets spreadsheet as a CSV file for local backup or processing." +metadata: + openclaw: + category: "recipe" + domain: "productivity" + requires: + bins: ["gws"] + skills: ["gws-sheets", "gws-drive"] +--- + +# Export a Google Sheet as CSV + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-sheets`, `gws-drive` + +Export a Google Sheets spreadsheet as a CSV file for local backup or processing. + +## Steps + +1. Get spreadsheet details: `gws sheets spreadsheets get --params '{"spreadsheetId": "SHEET_ID"}'` +2. Export as CSV: `gws drive files export --params '{"fileId": "SHEET_ID", "mimeType": "text/csv"}'` +3. Or read values directly: `gws sheets +read --spreadsheet-id SHEET_ID --range 'Sheet1' --format csv` + diff --git a/skills/recipe-batch-invite-to-event/SKILL.md b/skills/recipe-batch-invite-to-event/SKILL.md new file mode 100644 index 00000000..73333f50 --- /dev/null +++ b/skills/recipe-batch-invite-to-event/SKILL.md @@ -0,0 +1,25 @@ +--- +name: recipe-batch-invite-to-event +version: 1.0.0 +description: "Add a list of attendees to an existing Google Calendar event and send notifications." +metadata: + openclaw: + category: "recipe" + domain: "scheduling" + requires: + bins: ["gws"] + skills: ["gws-calendar"] +--- + +# Add Multiple Attendees to a Calendar Event + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-calendar` + +Add a list of attendees to an existing Google Calendar event and send notifications. + +## Steps + +1. Get the event: `gws calendar events get --params '{"calendarId": "primary", "eventId": "EVENT_ID"}'` +2. Add attendees: `gws calendar events patch --params '{"calendarId": "primary", "eventId": "EVENT_ID", "sendUpdates": "all"}' --json '{"attendees": [{"email": "alice@company.com"}, {"email": "bob@company.com"}, {"email": "carol@company.com"}]}'` +3. Verify attendees: `gws calendar events get --params '{"calendarId": "primary", "eventId": "EVENT_ID"}'` + diff --git a/skills/recipe-batch-rename-files/SKILL.md b/skills/recipe-batch-rename-files/SKILL.md new file mode 100644 index 00000000..f3eb7d10 --- /dev/null +++ b/skills/recipe-batch-rename-files/SKILL.md @@ -0,0 +1,25 @@ +--- +name: recipe-batch-rename-files +version: 1.0.0 +description: "Rename multiple Google Drive files matching a pattern to follow a consistent naming convention." +metadata: + openclaw: + category: "recipe" + domain: "productivity" + requires: + bins: ["gws"] + skills: ["gws-drive"] +--- + +# Batch Rename Google Drive Files + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-drive` + +Rename multiple Google Drive files matching a pattern to follow a consistent naming convention. + +## Steps + +1. Find files to rename: `gws drive files list --params '{"q": "name contains '\''Report'\''"}' --format table` +2. Rename a file: `gws drive files update --params '{"fileId": "FILE_ID"}' --json '{"name": "2025-Q1 Report - Final"}'` +3. Verify the rename: `gws drive files get --params '{"fileId": "FILE_ID", "fields": "name"}'` + diff --git a/skills/recipe-batch-reply-to-emails/SKILL.md b/skills/recipe-batch-reply-to-emails/SKILL.md new file mode 100644 index 00000000..bb8878f7 --- /dev/null +++ b/skills/recipe-batch-reply-to-emails/SKILL.md @@ -0,0 +1,26 @@ +--- +name: recipe-batch-reply-to-emails +version: 1.0.0 +description: "Find Gmail messages matching a query and send a standard reply to each one." +metadata: + openclaw: + category: "recipe" + domain: "productivity" + requires: + bins: ["gws"] + skills: ["gws-gmail"] +--- + +# Batch Reply to Similar Gmail Messages + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-gmail` + +Find Gmail messages matching a query and send a standard reply to each one. + +## Steps + +1. Find messages needing replies: `gws gmail users messages list --params '{"userId": "me", "q": "is:unread from:customers label:support"}' --format table` +2. Read a message: `gws gmail users messages get --params '{"userId": "me", "id": "MSG_ID"}'` +3. Send a reply: `gws gmail +send --to sender@example.com --subject 'Re: Your Request' --body 'Thank you for reaching out. We have received your request and will respond within 24 hours.'` +4. Mark as read: `gws gmail users messages modify --params '{"userId": "me", "id": "MSG_ID"}' --json '{"removeLabelIds": ["UNREAD"]}'` + diff --git a/skills/recipe-block-focus-time/SKILL.md b/skills/recipe-block-focus-time/SKILL.md new file mode 100644 index 00000000..c2798dc1 --- /dev/null +++ b/skills/recipe-block-focus-time/SKILL.md @@ -0,0 +1,24 @@ +--- +name: recipe-block-focus-time +version: 1.0.0 +description: "Create recurring focus time blocks on Google Calendar to protect deep work hours." +metadata: + openclaw: + category: "recipe" + domain: "scheduling" + requires: + bins: ["gws"] + skills: ["gws-calendar"] +--- + +# Block Focus Time on Google Calendar + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-calendar` + +Create recurring focus time blocks on Google Calendar to protect deep work hours. + +## Steps + +1. Create recurring focus block: `gws calendar events insert --params '{"calendarId": "primary"}' --json '{"summary": "Focus Time", "description": "Protected deep work block", "start": {"dateTime": "2025-01-20T09:00:00", "timeZone": "America/New_York"}, "end": {"dateTime": "2025-01-20T11:00:00", "timeZone": "America/New_York"}, "recurrence": ["RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR"], "transparency": "opaque"}'` +2. Verify it shows as busy: `gws calendar +agenda` + diff --git a/skills/recipe-bulk-download-folder/SKILL.md b/skills/recipe-bulk-download-folder/SKILL.md new file mode 100644 index 00000000..d45d0a0a --- /dev/null +++ b/skills/recipe-bulk-download-folder/SKILL.md @@ -0,0 +1,25 @@ +--- +name: recipe-bulk-download-folder +version: 1.0.0 +description: "List and download all files from a Google Drive folder." +metadata: + openclaw: + category: "recipe" + domain: "productivity" + requires: + bins: ["gws"] + skills: ["gws-drive"] +--- + +# Bulk Download Drive Folder + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-drive` + +List and download all files from a Google Drive folder. + +## Steps + +1. List files in folder: `gws drive files list --params '{"q": "'\''FOLDER_ID'\'' in parents"}' --format json` +2. Download each file: `gws drive files get --params '{"fileId": "FILE_ID", "alt": "media"}' -o filename.ext` +3. Export Google Docs as PDF: `gws drive files export --params '{"fileId": "FILE_ID", "mimeType": "application/pdf"}' -o document.pdf` + diff --git a/skills/recipe-cancel-and-notify/SKILL.md b/skills/recipe-cancel-and-notify/SKILL.md new file mode 100644 index 00000000..f53cdeb2 --- /dev/null +++ b/skills/recipe-cancel-and-notify/SKILL.md @@ -0,0 +1,28 @@ +--- +name: recipe-cancel-and-notify +version: 1.0.0 +description: "Delete a Google Calendar event and send a cancellation email via Gmail." +metadata: + openclaw: + category: "recipe" + domain: "scheduling" + requires: + bins: ["gws"] + skills: ["gws-calendar", "gws-gmail"] +--- + +# Cancel Meeting and Notify Attendees + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-calendar`, `gws-gmail` + +Delete a Google Calendar event and send a cancellation email via Gmail. + +> [!CAUTION] +> Deleting with sendUpdates sends cancellation emails to all attendees. + +## Steps + +1. Find the meeting: `gws calendar +agenda --format json` and locate the event ID +2. Delete the event: `gws calendar events delete --params '{"calendarId": "primary", "eventId": "EVENT_ID", "sendUpdates": "all"}'` +3. Send follow-up: `gws gmail +send --to attendees --subject 'Meeting Cancelled: [Title]' --body 'Apologies, this meeting has been cancelled.'` + diff --git a/skills/recipe-collect-form-responses/SKILL.md b/skills/recipe-collect-form-responses/SKILL.md new file mode 100644 index 00000000..35aeb6a7 --- /dev/null +++ b/skills/recipe-collect-form-responses/SKILL.md @@ -0,0 +1,25 @@ +--- +name: recipe-collect-form-responses +version: 1.0.0 +description: "Retrieve and review responses from a Google Form." +metadata: + openclaw: + category: "recipe" + domain: "productivity" + requires: + bins: ["gws"] + skills: ["gws-forms"] +--- + +# Check Form Responses + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-forms` + +Retrieve and review responses from a Google Form. + +## Steps + +1. List forms: `gws forms forms list` (if you don't have the form ID) +2. Get form details: `gws forms forms get --params '{"formId": "FORM_ID"}'` +3. Get responses: `gws forms forms responses list --params '{"formId": "FORM_ID"}' --format table` + diff --git a/skills/recipe-compare-sheet-tabs/SKILL.md b/skills/recipe-compare-sheet-tabs/SKILL.md new file mode 100644 index 00000000..a16dd75d --- /dev/null +++ b/skills/recipe-compare-sheet-tabs/SKILL.md @@ -0,0 +1,25 @@ +--- +name: recipe-compare-sheet-tabs +version: 1.0.0 +description: "Read data from two tabs in a Google Sheet to compare and identify differences." +metadata: + openclaw: + category: "recipe" + domain: "productivity" + requires: + bins: ["gws"] + skills: ["gws-sheets"] +--- + +# Compare Two Google Sheets Tabs + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-sheets` + +Read data from two tabs in a Google Sheet to compare and identify differences. + +## Steps + +1. Read the first tab: `gws sheets +read --spreadsheet-id SHEET_ID --range 'January!A1:D'` +2. Read the second tab: `gws sheets +read --spreadsheet-id SHEET_ID --range 'February!A1:D'` +3. Compare the data and identify changes + diff --git a/skills/recipe-copy-sheet-for-new-month/SKILL.md b/skills/recipe-copy-sheet-for-new-month/SKILL.md new file mode 100644 index 00000000..cb93b68c --- /dev/null +++ b/skills/recipe-copy-sheet-for-new-month/SKILL.md @@ -0,0 +1,25 @@ +--- +name: recipe-copy-sheet-for-new-month +version: 1.0.0 +description: "Duplicate a Google Sheets template tab for a new month of tracking." +metadata: + openclaw: + category: "recipe" + domain: "productivity" + requires: + bins: ["gws"] + skills: ["gws-sheets"] +--- + +# Copy a Google Sheet for a New Month + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-sheets` + +Duplicate a Google Sheets template tab for a new month of tracking. + +## Steps + +1. Get spreadsheet details: `gws sheets spreadsheets get --params '{"spreadsheetId": "SHEET_ID"}'` +2. Copy the template sheet: `gws sheets spreadsheets sheets copyTo --params '{"spreadsheetId": "SHEET_ID", "sheetId": 0}' --json '{"destinationSpreadsheetId": "SHEET_ID"}'` +3. Rename the new tab: `gws sheets spreadsheets batchUpdate --params '{"spreadsheetId": "SHEET_ID"}' --json '{"requests": [{"updateSheetProperties": {"properties": {"sheetId": 123, "title": "February 2025"}, "fields": "title"}}]}'` + diff --git a/skills/recipe-create-classroom-course/SKILL.md b/skills/recipe-create-classroom-course/SKILL.md new file mode 100644 index 00000000..05b7e7c6 --- /dev/null +++ b/skills/recipe-create-classroom-course/SKILL.md @@ -0,0 +1,25 @@ +--- +name: recipe-create-classroom-course +version: 1.0.0 +description: "Create a Google Classroom course and invite students." +metadata: + openclaw: + category: "recipe" + domain: "education" + requires: + bins: ["gws"] + skills: ["gws-classroom"] +--- + +# Create a Google Classroom Course + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-classroom` + +Create a Google Classroom course and invite students. + +## Steps + +1. Create the course: `gws classroom courses create --json '{"name": "Introduction to CS", "section": "Period 1", "room": "Room 101", "ownerId": "me"}'` +2. Invite a student: `gws classroom invitations create --json '{"courseId": "COURSE_ID", "userId": "student@school.edu", "role": "STUDENT"}'` +3. List enrolled students: `gws classroom courses students list --params '{"courseId": "COURSE_ID"}' --format table` + diff --git a/skills/recipe-create-doc-from-template/SKILL.md b/skills/recipe-create-doc-from-template/SKILL.md new file mode 100644 index 00000000..dd92337f --- /dev/null +++ b/skills/recipe-create-doc-from-template/SKILL.md @@ -0,0 +1,29 @@ +--- +name: recipe-create-doc-from-template +version: 1.0.0 +description: "Copy a Google Docs template, fill in content, and share with collaborators." +metadata: + openclaw: + category: "recipe" + domain: "productivity" + requires: + bins: ["gws"] + skills: ["gws-drive", "gws-docs"] +--- + +# Create a Google Doc from a Template + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-drive`, `gws-docs` + +Copy a Google Docs template, fill in content, and share with collaborators. + +## Steps + +1. Copy the template: `gws drive files copy --params '{"fileId": "TEMPLATE_DOC_ID"}' --json '{"name": "Project Brief - Q2 Launch"}'` +2. Get the new doc ID from the response +3. Add content: `gws docs +write --document-id NEW_DOC_ID --text '## Project: Q2 Launch + +### Objective +Launch the new feature by end of Q2.'` +4. Share with team: `gws drive permissions create --params '{"fileId": "NEW_DOC_ID"}' --json '{"role": "writer", "type": "user", "emailAddress": "team@company.com"}'` + diff --git a/skills/recipe-create-events-from-sheet/SKILL.md b/skills/recipe-create-events-from-sheet/SKILL.md new file mode 100644 index 00000000..589ec8a3 --- /dev/null +++ b/skills/recipe-create-events-from-sheet/SKILL.md @@ -0,0 +1,24 @@ +--- +name: recipe-create-events-from-sheet +version: 1.0.0 +description: "Read event data from a Google Sheets spreadsheet and create Google Calendar entries for each row." +metadata: + openclaw: + category: "recipe" + domain: "productivity" + requires: + bins: ["gws"] + skills: ["gws-sheets", "gws-calendar"] +--- + +# Create Google Calendar Events from a Sheet + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-sheets`, `gws-calendar` + +Read event data from a Google Sheets spreadsheet and create Google Calendar entries for each row. + +## Steps + +1. Read event data: `gws sheets +read --spreadsheet-id SHEET_ID --range 'Events!A2:D'` +2. For each row, create a calendar event: `gws calendar +insert --summary 'Team Standup' --start '2025-01-20T09:00' --duration 30 --attendees alice@company.com,bob@company.com` + diff --git a/skills/recipe-create-expense-tracker/SKILL.md b/skills/recipe-create-expense-tracker/SKILL.md new file mode 100644 index 00000000..c0b7ff85 --- /dev/null +++ b/skills/recipe-create-expense-tracker/SKILL.md @@ -0,0 +1,26 @@ +--- +name: recipe-create-expense-tracker +version: 1.0.0 +description: "Set up a Google Sheets spreadsheet for tracking expenses with headers and initial entries." +metadata: + openclaw: + category: "recipe" + domain: "productivity" + requires: + bins: ["gws"] + skills: ["gws-sheets", "gws-drive"] +--- + +# Create a Google Sheets Expense Tracker + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-sheets`, `gws-drive` + +Set up a Google Sheets spreadsheet for tracking expenses with headers and initial entries. + +## Steps + +1. Create spreadsheet: `gws drive files create --json '{"name": "Expense Tracker 2025", "mimeType": "application/vnd.google-apps.spreadsheet"}'` +2. Add headers: `gws sheets +append --spreadsheet-id SHEET_ID --range 'Sheet1' --values '["Date", "Category", "Description", "Amount"]'` +3. Add first entry: `gws sheets +append --spreadsheet-id SHEET_ID --range 'Sheet1' --values '["2025-01-15", "Travel", "Flight to NYC", "450.00"]'` +4. Share with manager: `gws drive permissions create --params '{"fileId": "SHEET_ID"}' --json '{"role": "reader", "type": "user", "emailAddress": "manager@company.com"}'` + diff --git a/skills/recipe-create-feedback-form/SKILL.md b/skills/recipe-create-feedback-form/SKILL.md new file mode 100644 index 00000000..27b04d4b --- /dev/null +++ b/skills/recipe-create-feedback-form/SKILL.md @@ -0,0 +1,25 @@ +--- +name: recipe-create-feedback-form +version: 1.0.0 +description: "Create a Google Form for feedback and share it via Gmail." +metadata: + openclaw: + category: "recipe" + domain: "productivity" + requires: + bins: ["gws"] + skills: ["gws-forms", "gws-gmail"] +--- + +# Create and Share a Google Form + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-forms`, `gws-gmail` + +Create a Google Form for feedback and share it via Gmail. + +## Steps + +1. Create form: `gws forms forms create --json '{"info": {"title": "Event Feedback", "documentTitle": "Event Feedback Form"}}'` +2. Get the form URL from the response (responderUri field) +3. Email the form: `gws gmail +send --to attendees@company.com --subject 'Please share your feedback' --body 'Fill out the form: FORM_URL'` + diff --git a/skills/recipe-create-gmail-filter/SKILL.md b/skills/recipe-create-gmail-filter/SKILL.md new file mode 100644 index 00000000..cadbe00a --- /dev/null +++ b/skills/recipe-create-gmail-filter/SKILL.md @@ -0,0 +1,26 @@ +--- +name: recipe-create-gmail-filter +version: 1.0.0 +description: "Create a Gmail filter to automatically label, star, or categorize incoming messages." +metadata: + openclaw: + category: "recipe" + domain: "productivity" + requires: + bins: ["gws"] + skills: ["gws-gmail"] +--- + +# Create a Gmail Filter + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-gmail` + +Create a Gmail filter to automatically label, star, or categorize incoming messages. + +## Steps + +1. List existing labels: `gws gmail users labels list --params '{"userId": "me"}' --format table` +2. Create a new label: `gws gmail users labels create --params '{"userId": "me"}' --json '{"name": "Receipts"}'` +3. Create a filter: `gws gmail users settings filters create --params '{"userId": "me"}' --json '{"criteria": {"from": "receipts@example.com"}, "action": {"addLabelIds": ["LABEL_ID"], "removeLabelIds": ["INBOX"]}}'` +4. Verify filter: `gws gmail users settings filters list --params '{"userId": "me"}' --format table` + diff --git a/skills/recipe-create-meet-space/SKILL.md b/skills/recipe-create-meet-space/SKILL.md new file mode 100644 index 00000000..beb2a909 --- /dev/null +++ b/skills/recipe-create-meet-space/SKILL.md @@ -0,0 +1,25 @@ +--- +name: recipe-create-meet-space +version: 1.0.0 +description: "Create a Google Meet meeting space and share the join link." +metadata: + openclaw: + category: "recipe" + domain: "scheduling" + requires: + bins: ["gws"] + skills: ["gws-meet", "gws-gmail"] +--- + +# Create a Google Meet Conference + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-meet`, `gws-gmail` + +Create a Google Meet meeting space and share the join link. + +## Steps + +1. Create meeting space: `gws meet spaces create --json '{"config": {"accessType": "OPEN"}}'` +2. Copy the meeting URI from the response +3. Email the link: `gws gmail +send --to team@company.com --subject 'Join the meeting' --body 'Join here: MEETING_URI'` + diff --git a/skills/recipe-create-presentation/SKILL.md b/skills/recipe-create-presentation/SKILL.md new file mode 100644 index 00000000..e0ed1368 --- /dev/null +++ b/skills/recipe-create-presentation/SKILL.md @@ -0,0 +1,25 @@ +--- +name: recipe-create-presentation +version: 1.0.0 +description: "Create a new Google Slides presentation and add initial slides." +metadata: + openclaw: + category: "recipe" + domain: "productivity" + requires: + bins: ["gws"] + skills: ["gws-slides"] +--- + +# Create a Google Slides Presentation + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-slides` + +Create a new Google Slides presentation and add initial slides. + +## Steps + +1. Create presentation: `gws slides presentations create --json '{"title": "Quarterly Review Q2"}'` +2. Get the presentation ID from the response +3. Share with team: `gws drive permissions create --params '{"fileId": "PRESENTATION_ID"}' --json '{"role": "writer", "type": "user", "emailAddress": "team@company.com"}'` + diff --git a/skills/recipe-create-shared-drive/SKILL.md b/skills/recipe-create-shared-drive/SKILL.md new file mode 100644 index 00000000..1e89c4cc --- /dev/null +++ b/skills/recipe-create-shared-drive/SKILL.md @@ -0,0 +1,25 @@ +--- +name: recipe-create-shared-drive +version: 1.0.0 +description: "Create a Google Shared Drive and add members with appropriate roles." +metadata: + openclaw: + category: "recipe" + domain: "productivity" + requires: + bins: ["gws"] + skills: ["gws-drive"] +--- + +# Create and Configure a Shared Drive + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-drive` + +Create a Google Shared Drive and add members with appropriate roles. + +## Steps + +1. Create shared drive: `gws drive drives create --params '{"requestId": "unique-id-123"}' --json '{"name": "Project X"}'` +2. Add a member: `gws drive permissions create --params '{"fileId": "DRIVE_ID", "supportsAllDrives": true}' --json '{"role": "writer", "type": "user", "emailAddress": "member@company.com"}'` +3. List members: `gws drive permissions list --params '{"fileId": "DRIVE_ID", "supportsAllDrives": true}'` + diff --git a/skills/recipe-create-task-list/SKILL.md b/skills/recipe-create-task-list/SKILL.md new file mode 100644 index 00000000..0bdb560a --- /dev/null +++ b/skills/recipe-create-task-list/SKILL.md @@ -0,0 +1,26 @@ +--- +name: recipe-create-task-list +version: 1.0.0 +description: "Set up a new Google Tasks list with initial tasks." +metadata: + openclaw: + category: "recipe" + domain: "productivity" + requires: + bins: ["gws"] + skills: ["gws-tasks"] +--- + +# Create a Task List and Add Tasks + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-tasks` + +Set up a new Google Tasks list with initial tasks. + +## Steps + +1. Create task list: `gws tasks tasklists insert --json '{"title": "Q2 Goals"}'` +2. Add a task: `gws tasks tasks insert --params '{"tasklist": "TASKLIST_ID"}' --json '{"title": "Review Q1 metrics", "notes": "Pull data from analytics dashboard", "due": "2024-04-01T00:00:00Z"}'` +3. Add another task: `gws tasks tasks insert --params '{"tasklist": "TASKLIST_ID"}' --json '{"title": "Draft Q2 OKRs"}'` +4. List tasks: `gws tasks tasks list --params '{"tasklist": "TASKLIST_ID"}' --format table` + diff --git a/skills/recipe-create-vacation-responder/SKILL.md b/skills/recipe-create-vacation-responder/SKILL.md new file mode 100644 index 00000000..9f279b4a --- /dev/null +++ b/skills/recipe-create-vacation-responder/SKILL.md @@ -0,0 +1,25 @@ +--- +name: recipe-create-vacation-responder +version: 1.0.0 +description: "Enable a Gmail out-of-office auto-reply with a custom message and date range." +metadata: + openclaw: + category: "recipe" + domain: "productivity" + requires: + bins: ["gws"] + skills: ["gws-gmail"] +--- + +# Set Up a Gmail Vacation Responder + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-gmail` + +Enable a Gmail out-of-office auto-reply with a custom message and date range. + +## Steps + +1. Enable vacation responder: `gws gmail users settings updateVacation --params '{"userId": "me"}' --json '{"enableAutoReply": true, "responseSubject": "Out of Office", "responseBodyPlainText": "I am out of the office until Jan 20. For urgent matters, contact backup@company.com.", "restrictToContacts": false, "restrictToDomain": false}'` +2. Verify settings: `gws gmail users settings getVacation --params '{"userId": "me"}'` +3. Disable when back: `gws gmail users settings updateVacation --params '{"userId": "me"}' --json '{"enableAutoReply": false}'` + diff --git a/skills/recipe-deploy-apps-script/SKILL.md b/skills/recipe-deploy-apps-script/SKILL.md new file mode 100644 index 00000000..9406e545 --- /dev/null +++ b/skills/recipe-deploy-apps-script/SKILL.md @@ -0,0 +1,26 @@ +--- +name: recipe-deploy-apps-script +version: 1.0.0 +description: "Push local files to a Google Apps Script project." +metadata: + openclaw: + category: "recipe" + domain: "engineering" + requires: + bins: ["gws"] + skills: ["gws-apps-script"] +--- + +# Deploy an Apps Script Project + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-apps-script` + +Push local files to a Google Apps Script project. + +## Steps + +1. List existing projects: `gws apps-script projects list --format table` +2. Get project content: `gws apps-script projects getContent --params '{"scriptId": "SCRIPT_ID"}'` +3. Update content: `gws apps-script projects updateContent --params '{"scriptId": "SCRIPT_ID"}' --json '{"files": [{"name": "Code", "type": "SERVER_JS", "source": "function main() { ... }"}]}'` +4. Create a new version: `gws apps-script projects versions create --params '{"scriptId": "SCRIPT_ID"}' --json '{"description": "v2 release"}'` + diff --git a/skills/recipe-draft-email-from-doc/SKILL.md b/skills/recipe-draft-email-from-doc/SKILL.md new file mode 100644 index 00000000..7b5cad54 --- /dev/null +++ b/skills/recipe-draft-email-from-doc/SKILL.md @@ -0,0 +1,25 @@ +--- +name: recipe-draft-email-from-doc +version: 1.0.0 +description: "Read content from a Google Doc and use it as the body of a Gmail message." +metadata: + openclaw: + category: "recipe" + domain: "productivity" + requires: + bins: ["gws"] + skills: ["gws-docs", "gws-gmail"] +--- + +# Draft a Gmail Message from a Google Doc + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-docs`, `gws-gmail` + +Read content from a Google Doc and use it as the body of a Gmail message. + +## Steps + +1. Get the document content: `gws docs documents get --params '{"documentId": "DOC_ID"}'` +2. Copy the text from the body content +3. Send the email: `gws gmail +send --to recipient@example.com --subject 'Newsletter Update' --body 'CONTENT_FROM_DOC'` + diff --git a/skills/recipe-email-drive-link/SKILL.md b/skills/recipe-email-drive-link/SKILL.md new file mode 100644 index 00000000..d672a440 --- /dev/null +++ b/skills/recipe-email-drive-link/SKILL.md @@ -0,0 +1,25 @@ +--- +name: recipe-email-drive-link +version: 1.0.0 +description: "Share a Google Drive file and email the link with a message to recipients." +metadata: + openclaw: + category: "recipe" + domain: "productivity" + requires: + bins: ["gws"] + skills: ["gws-drive", "gws-gmail"] +--- + +# Email a Google Drive File Link + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-drive`, `gws-gmail` + +Share a Google Drive file and email the link with a message to recipients. + +## Steps + +1. Find the file: `gws drive files list --params '{"q": "name = '\''Quarterly Report'\''"}'` +2. Share the file: `gws drive permissions create --params '{"fileId": "FILE_ID"}' --json '{"role": "reader", "type": "user", "emailAddress": "client@example.com"}'` +3. Email the link: `gws gmail +send --to client@example.com --subject 'Quarterly Report' --body 'Hi, please find the report here: https://docs.google.com/document/d/FILE_ID'` + diff --git a/skills/recipe-find-free-time/SKILL.md b/skills/recipe-find-free-time/SKILL.md new file mode 100644 index 00000000..d1231b8b --- /dev/null +++ b/skills/recipe-find-free-time/SKILL.md @@ -0,0 +1,25 @@ +--- +name: recipe-find-free-time +version: 1.0.0 +description: "Query Google Calendar free/busy status for multiple users to find a meeting slot." +metadata: + openclaw: + category: "recipe" + domain: "scheduling" + requires: + bins: ["gws"] + skills: ["gws-calendar"] +--- + +# Find Free Time Across Calendars + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-calendar` + +Query Google Calendar free/busy status for multiple users to find a meeting slot. + +## Steps + +1. Query free/busy: `gws calendar freebusy query --json '{"timeMin": "2024-03-18T08:00:00Z", "timeMax": "2024-03-18T18:00:00Z", "items": [{"id": "user1@company.com"}, {"id": "user2@company.com"}]}'` +2. Review the output to find overlapping free slots +3. Create event in the free slot: `gws calendar +insert --summary 'Meeting' --attendees user1@company.com,user2@company.com --start '2024-03-18T14:00:00' --duration 30` + diff --git a/skills/recipe-find-large-files/SKILL.md b/skills/recipe-find-large-files/SKILL.md new file mode 100644 index 00000000..a4cee757 --- /dev/null +++ b/skills/recipe-find-large-files/SKILL.md @@ -0,0 +1,28 @@ +--- +name: recipe-find-large-files +version: 1.0.0 +description: "Identify large Google Drive files consuming storage quota." +metadata: + openclaw: + category: "recipe" + domain: "productivity" + requires: + bins: ["gws"] + skills: ["gws-drive"] +--- + +# Find Largest Files in Drive + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-drive` + +Identify large Google Drive files consuming storage quota. + +> [!CAUTION] +> Deleting files is permanent if the trash is emptied. Confirm before deleting. + +## Steps + +1. List files sorted by size: `gws drive files list --params '{"orderBy": "quotaBytesUsed desc", "pageSize": 20, "fields": "files(id,name,size,mimeType,owners)"}' --format table` +2. Review the output and identify files to delete or move +3. Delete if needed: `gws drive files delete --params '{"fileId": "FILE_ID"}'` + diff --git a/skills/recipe-forward-labeled-emails/SKILL.md b/skills/recipe-forward-labeled-emails/SKILL.md new file mode 100644 index 00000000..5f0cb984 --- /dev/null +++ b/skills/recipe-forward-labeled-emails/SKILL.md @@ -0,0 +1,27 @@ +--- +name: recipe-forward-labeled-emails +version: 1.0.0 +description: "Find Gmail messages with a specific label and forward them to another address." +metadata: + openclaw: + category: "recipe" + domain: "productivity" + requires: + bins: ["gws"] + skills: ["gws-gmail"] +--- + +# Forward Labeled Gmail Messages + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-gmail` + +Find Gmail messages with a specific label and forward them to another address. + +## Steps + +1. Find labeled messages: `gws gmail users messages list --params '{"userId": "me", "q": "label:needs-review"}' --format table` +2. Get message content: `gws gmail users messages get --params '{"userId": "me", "id": "MSG_ID"}'` +3. Forward via new email: `gws gmail +send --to manager@company.com --subject 'FW: [Original Subject]' --body 'Forwarding for your review: + +[Original Message Body]'` + diff --git a/skills/recipe-generate-report-from-sheet/SKILL.md b/skills/recipe-generate-report-from-sheet/SKILL.md new file mode 100644 index 00000000..7da37ea9 --- /dev/null +++ b/skills/recipe-generate-report-from-sheet/SKILL.md @@ -0,0 +1,34 @@ +--- +name: recipe-generate-report-from-sheet +version: 1.0.0 +description: "Read data from a Google Sheet and create a formatted Google Docs report." +metadata: + openclaw: + category: "recipe" + domain: "productivity" + requires: + bins: ["gws"] + skills: ["gws-sheets", "gws-docs", "gws-drive"] +--- + +# Generate a Google Docs Report from Sheet Data + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-sheets`, `gws-docs`, `gws-drive` + +Read data from a Google Sheet and create a formatted Google Docs report. + +## Steps + +1. Read the data: `gws sheets +read --spreadsheet-id SHEET_ID --range 'Sales!A1:D'` +2. Create the report doc: `gws docs documents create --json '{"title": "Sales Report - January 2025"}'` +3. Write the report: `gws docs +write --document-id DOC_ID --text '## Sales Report - January 2025 + +### Summary +Total deals: 45 +Revenue: $125,000 + +### Top Deals +1. Acme Corp - $25,000 +2. Widget Inc - $18,000'` +4. Share with stakeholders: `gws drive permissions create --params '{"fileId": "DOC_ID"}' --json '{"role": "reader", "type": "user", "emailAddress": "cfo@company.com"}'` + diff --git a/skills/recipe-label-and-archive-emails/SKILL.md b/skills/recipe-label-and-archive-emails/SKILL.md new file mode 100644 index 00000000..e2071ac5 --- /dev/null +++ b/skills/recipe-label-and-archive-emails/SKILL.md @@ -0,0 +1,25 @@ +--- +name: recipe-label-and-archive-emails +version: 1.0.0 +description: "Apply Gmail labels to matching messages and archive them to keep your inbox clean." +metadata: + openclaw: + category: "recipe" + domain: "productivity" + requires: + bins: ["gws"] + skills: ["gws-gmail"] +--- + +# Label and Archive Gmail Threads + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-gmail` + +Apply Gmail labels to matching messages and archive them to keep your inbox clean. + +## Steps + +1. Search for matching emails: `gws gmail users messages list --params '{"userId": "me", "q": "from:notifications@service.com"}' --format table` +2. Apply a label: `gws gmail users messages modify --params '{"userId": "me", "id": "MESSAGE_ID"}' --json '{"addLabelIds": ["LABEL_ID"]}'` +3. Archive (remove from inbox): `gws gmail users messages modify --params '{"userId": "me", "id": "MESSAGE_ID"}' --json '{"removeLabelIds": ["INBOX"]}'` + diff --git a/skills/recipe-log-deal-update/SKILL.md b/skills/recipe-log-deal-update/SKILL.md new file mode 100644 index 00000000..24ca63d5 --- /dev/null +++ b/skills/recipe-log-deal-update/SKILL.md @@ -0,0 +1,25 @@ +--- +name: recipe-log-deal-update +version: 1.0.0 +description: "Append a deal status update to a Google Sheets sales tracking spreadsheet." +metadata: + openclaw: + category: "recipe" + domain: "sales" + requires: + bins: ["gws"] + skills: ["gws-sheets", "gws-drive"] +--- + +# Log Deal Update to Sheet + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-sheets`, `gws-drive` + +Append a deal status update to a Google Sheets sales tracking spreadsheet. + +## Steps + +1. Find the tracking sheet: `gws drive files list --params '{"q": "name = '\''Sales Pipeline'\'' and mimeType = '\''application/vnd.google-apps.spreadsheet'\''"}'` +2. Read current data: `gws sheets +read --spreadsheet-id SHEET_ID --range 'Pipeline!A1:F'` +3. Append new row: `gws sheets +append --spreadsheet-id SHEET_ID --range 'Pipeline' --values '["2024-03-15", "Acme Corp", "Proposal Sent", "$50,000", "Q2", "jdoe"]'` + diff --git a/skills/recipe-organize-drive-folder/SKILL.md b/skills/recipe-organize-drive-folder/SKILL.md new file mode 100644 index 00000000..33195f60 --- /dev/null +++ b/skills/recipe-organize-drive-folder/SKILL.md @@ -0,0 +1,26 @@ +--- +name: recipe-organize-drive-folder +version: 1.0.0 +description: "Create a Google Drive folder structure and move files into the right locations." +metadata: + openclaw: + category: "recipe" + domain: "productivity" + requires: + bins: ["gws"] + skills: ["gws-drive"] +--- + +# Organize Files into Google Drive Folders + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-drive` + +Create a Google Drive folder structure and move files into the right locations. + +## Steps + +1. Create a project folder: `gws drive files create --json '{"name": "Q2 Project", "mimeType": "application/vnd.google-apps.folder"}'` +2. Create sub-folders: `gws drive files create --json '{"name": "Documents", "mimeType": "application/vnd.google-apps.folder", "parents": ["PARENT_FOLDER_ID"]}'` +3. Move existing files into folder: `gws drive files update --params '{"fileId": "FILE_ID", "addParents": "FOLDER_ID", "removeParents": "OLD_PARENT_ID"}'` +4. Verify structure: `gws drive files list --params '{"q": "FOLDER_ID in parents"}' --format table` + diff --git a/skills/recipe-plan-weekly-schedule/SKILL.md b/skills/recipe-plan-weekly-schedule/SKILL.md new file mode 100644 index 00000000..e9ba3160 --- /dev/null +++ b/skills/recipe-plan-weekly-schedule/SKILL.md @@ -0,0 +1,26 @@ +--- +name: recipe-plan-weekly-schedule +version: 1.0.0 +description: "Review your Google Calendar week, identify gaps, and add events to fill them." +metadata: + openclaw: + category: "recipe" + domain: "scheduling" + requires: + bins: ["gws"] + skills: ["gws-calendar"] +--- + +# Plan Your Weekly Google Calendar Schedule + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-calendar` + +Review your Google Calendar week, identify gaps, and add events to fill them. + +## Steps + +1. Check this week's agenda: `gws calendar +agenda` +2. Check free/busy for the week: `gws calendar freebusy query --json '{"timeMin": "2025-01-20T00:00:00Z", "timeMax": "2025-01-25T00:00:00Z", "items": [{"id": "primary"}]}'` +3. Add a new event: `gws calendar +insert --summary 'Deep Work Block' --start '2025-01-21T14:00' --duration 120` +4. Review updated schedule: `gws calendar +agenda` + diff --git a/skills/recipe-post-mortem-setup/SKILL.md b/skills/recipe-post-mortem-setup/SKILL.md new file mode 100644 index 00000000..56c934a2 --- /dev/null +++ b/skills/recipe-post-mortem-setup/SKILL.md @@ -0,0 +1,25 @@ +--- +name: recipe-post-mortem-setup +version: 1.0.0 +description: "Create a Google Docs post-mortem, schedule a Google Calendar review, and notify via Chat." +metadata: + openclaw: + category: "recipe" + domain: "engineering" + requires: + bins: ["gws"] + skills: ["gws-docs", "gws-calendar", "gws-chat"] +--- + +# Set Up Post-Mortem + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-docs`, `gws-calendar`, `gws-chat` + +Create a Google Docs post-mortem, schedule a Google Calendar review, and notify via Chat. + +## Steps + +1. Create post-mortem doc: `gws docs +write --title 'Post-Mortem: [Incident]' --body '## Summary\n\n## Timeline\n\n## Root Cause\n\n## Action Items'` +2. Schedule review meeting: `gws calendar +insert --summary 'Post-Mortem Review: [Incident]' --attendees team@company.com --start 'next monday 14:00' --duration 60` +3. Notify in Chat: `gws chat +send --space spaces/ENG_SPACE --text '🔍 Post-mortem scheduled for [Incident].'` + diff --git a/skills/recipe-reschedule-meeting/SKILL.md b/skills/recipe-reschedule-meeting/SKILL.md new file mode 100644 index 00000000..2b6f7751 --- /dev/null +++ b/skills/recipe-reschedule-meeting/SKILL.md @@ -0,0 +1,25 @@ +--- +name: recipe-reschedule-meeting +version: 1.0.0 +description: "Move a Google Calendar event to a new time and automatically notify all attendees." +metadata: + openclaw: + category: "recipe" + domain: "scheduling" + requires: + bins: ["gws"] + skills: ["gws-calendar"] +--- + +# Reschedule a Google Calendar Meeting + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-calendar` + +Move a Google Calendar event to a new time and automatically notify all attendees. + +## Steps + +1. Find the event: `gws calendar +agenda` +2. Get event details: `gws calendar events get --params '{"calendarId": "primary", "eventId": "EVENT_ID"}'` +3. Update the time: `gws calendar events patch --params '{"calendarId": "primary", "eventId": "EVENT_ID", "sendUpdates": "all"}' --json '{"start": {"dateTime": "2025-01-22T14:00:00", "timeZone": "America/New_York"}, "end": {"dateTime": "2025-01-22T15:00:00", "timeZone": "America/New_York"}}'` + diff --git a/skills/recipe-review-meet-participants/SKILL.md b/skills/recipe-review-meet-participants/SKILL.md new file mode 100644 index 00000000..3d84ce3e --- /dev/null +++ b/skills/recipe-review-meet-participants/SKILL.md @@ -0,0 +1,25 @@ +--- +name: recipe-review-meet-participants +version: 1.0.0 +description: "Review who attended a Google Meet conference and for how long." +metadata: + openclaw: + category: "recipe" + domain: "productivity" + requires: + bins: ["gws"] + skills: ["gws-meet"] +--- + +# Review Google Meet Attendance + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-meet` + +Review who attended a Google Meet conference and for how long. + +## Steps + +1. List recent conferences: `gws meet conferenceRecords list --format table` +2. List participants: `gws meet conferenceRecords participants list --params '{"parent": "conferenceRecords/CONFERENCE_ID"}' --format table` +3. Get session details: `gws meet conferenceRecords participants participantSessions list --params '{"parent": "conferenceRecords/CONFERENCE_ID/participants/PARTICIPANT_ID"}' --format table` + diff --git a/skills/recipe-review-overdue-tasks/SKILL.md b/skills/recipe-review-overdue-tasks/SKILL.md new file mode 100644 index 00000000..e337e73a --- /dev/null +++ b/skills/recipe-review-overdue-tasks/SKILL.md @@ -0,0 +1,25 @@ +--- +name: recipe-review-overdue-tasks +version: 1.0.0 +description: "Find Google Tasks that are past due and need attention." +metadata: + openclaw: + category: "recipe" + domain: "productivity" + requires: + bins: ["gws"] + skills: ["gws-tasks"] +--- + +# Review Overdue Tasks + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-tasks` + +Find Google Tasks that are past due and need attention. + +## Steps + +1. List task lists: `gws tasks tasklists list --format table` +2. List tasks with status: `gws tasks tasks list --params '{"tasklist": "TASKLIST_ID", "showCompleted": false}' --format table` +3. Review due dates and prioritize overdue items + diff --git a/skills/recipe-save-email-attachments/SKILL.md b/skills/recipe-save-email-attachments/SKILL.md new file mode 100644 index 00000000..5bd56f25 --- /dev/null +++ b/skills/recipe-save-email-attachments/SKILL.md @@ -0,0 +1,26 @@ +--- +name: recipe-save-email-attachments +version: 1.0.0 +description: "Find Gmail messages with attachments and save them to a Google Drive folder." +metadata: + openclaw: + category: "recipe" + domain: "productivity" + requires: + bins: ["gws"] + skills: ["gws-gmail", "gws-drive"] +--- + +# Save Gmail Attachments to Google Drive + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-gmail`, `gws-drive` + +Find Gmail messages with attachments and save them to a Google Drive folder. + +## Steps + +1. Search for emails with attachments: `gws gmail users messages list --params '{"userId": "me", "q": "has:attachment from:client@example.com"}' --format table` +2. Get message details: `gws gmail users messages get --params '{"userId": "me", "id": "MESSAGE_ID"}'` +3. Download attachment: `gws gmail users messages attachments get --params '{"userId": "me", "messageId": "MESSAGE_ID", "id": "ATTACHMENT_ID"}'` +4. Upload to Drive folder: `gws drive +upload --file ./attachment.pdf --parent FOLDER_ID` + diff --git a/skills/recipe-save-email-to-doc/SKILL.md b/skills/recipe-save-email-to-doc/SKILL.md new file mode 100644 index 00000000..2ffa866f --- /dev/null +++ b/skills/recipe-save-email-to-doc/SKILL.md @@ -0,0 +1,29 @@ +--- +name: recipe-save-email-to-doc +version: 1.0.0 +description: "Save a Gmail message body into a Google Doc for archival or reference." +metadata: + openclaw: + category: "recipe" + domain: "productivity" + requires: + bins: ["gws"] + skills: ["gws-gmail", "gws-docs"] +--- + +# Save a Gmail Message to Google Docs + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-gmail`, `gws-docs` + +Save a Gmail message body into a Google Doc for archival or reference. + +## Steps + +1. Find the message: `gws gmail users messages list --params '{"userId": "me", "q": "subject:important from:boss@company.com"}' --format table` +2. Get message content: `gws gmail users messages get --params '{"userId": "me", "id": "MSG_ID"}'` +3. Create a doc with the content: `gws docs documents create --json '{"title": "Saved Email - Important Update"}'` +4. Write the email body: `gws docs +write --document-id DOC_ID --text 'From: boss@company.com +Subject: Important Update + +[EMAIL BODY]'` + diff --git a/skills/recipe-schedule-recurring-event/SKILL.md b/skills/recipe-schedule-recurring-event/SKILL.md new file mode 100644 index 00000000..3a9d02bb --- /dev/null +++ b/skills/recipe-schedule-recurring-event/SKILL.md @@ -0,0 +1,24 @@ +--- +name: recipe-schedule-recurring-event +version: 1.0.0 +description: "Create a recurring Google Calendar event with attendees." +metadata: + openclaw: + category: "recipe" + domain: "scheduling" + requires: + bins: ["gws"] + skills: ["gws-calendar"] +--- + +# Schedule a Recurring Meeting + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-calendar` + +Create a recurring Google Calendar event with attendees. + +## Steps + +1. Create recurring event: `gws calendar events insert --params '{"calendarId": "primary"}' --json '{"summary": "Weekly Standup", "start": {"dateTime": "2024-03-18T09:00:00", "timeZone": "America/New_York"}, "end": {"dateTime": "2024-03-18T09:30:00", "timeZone": "America/New_York"}, "recurrence": ["RRULE:FREQ=WEEKLY;BYDAY=MO"], "attendees": [{"email": "team@company.com"}]}'` +2. Verify it was created: `gws calendar +agenda --days 14 --format table` + diff --git a/skills/recipe-search-and-export-emails/SKILL.md b/skills/recipe-search-and-export-emails/SKILL.md new file mode 100644 index 00000000..236b3f42 --- /dev/null +++ b/skills/recipe-search-and-export-emails/SKILL.md @@ -0,0 +1,25 @@ +--- +name: recipe-search-and-export-emails +version: 1.0.0 +description: "Find Gmail messages matching a query and export them for review." +metadata: + openclaw: + category: "recipe" + domain: "productivity" + requires: + bins: ["gws"] + skills: ["gws-gmail"] +--- + +# Search and Export Emails + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-gmail` + +Find Gmail messages matching a query and export them for review. + +## Steps + +1. Search for emails: `gws gmail users messages list --params '{"userId": "me", "q": "from:client@example.com after:2024/01/01"}'` +2. Get full message: `gws gmail users messages get --params '{"userId": "me", "id": "MSG_ID"}'` +3. Export results: `gws gmail users messages list --params '{"userId": "me", "q": "label:project-x"}' --format json > project-emails.json` + diff --git a/skills/recipe-send-personalized-emails/SKILL.md b/skills/recipe-send-personalized-emails/SKILL.md new file mode 100644 index 00000000..a4b398c9 --- /dev/null +++ b/skills/recipe-send-personalized-emails/SKILL.md @@ -0,0 +1,24 @@ +--- +name: recipe-send-personalized-emails +version: 1.0.0 +description: "Read recipient data from Google Sheets and send personalized Gmail messages to each row." +metadata: + openclaw: + category: "recipe" + domain: "productivity" + requires: + bins: ["gws"] + skills: ["gws-sheets", "gws-gmail"] +--- + +# Send Personalized Emails from a Sheet + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-sheets`, `gws-gmail` + +Read recipient data from Google Sheets and send personalized Gmail messages to each row. + +## Steps + +1. Read recipient list: `gws sheets +read --spreadsheet-id SHEET_ID --range 'Contacts!A2:C'` +2. For each row, send a personalized email: `gws gmail +send --to recipient@example.com --subject 'Hello, Name' --body 'Hi Name, your report is ready.'` + diff --git a/skills/recipe-send-team-announcement/SKILL.md b/skills/recipe-send-team-announcement/SKILL.md new file mode 100644 index 00000000..d93b8421 --- /dev/null +++ b/skills/recipe-send-team-announcement/SKILL.md @@ -0,0 +1,24 @@ +--- +name: recipe-send-team-announcement +version: 1.0.0 +description: "Send a team announcement via both Gmail and a Google Chat space." +metadata: + openclaw: + category: "recipe" + domain: "communication" + requires: + bins: ["gws"] + skills: ["gws-gmail", "gws-chat"] +--- + +# Announce via Gmail and Google Chat + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-gmail`, `gws-chat` + +Send a team announcement via both Gmail and a Google Chat space. + +## Steps + +1. Send email: `gws gmail +send --to team@company.com --subject 'Important Update' --body 'Please review the attached policy changes.'` +2. Post in Chat: `gws chat +send --space spaces/TEAM_SPACE --text '📢 Important Update: Please check your email for policy changes.'` + diff --git a/skills/recipe-share-doc-and-notify/SKILL.md b/skills/recipe-share-doc-and-notify/SKILL.md new file mode 100644 index 00000000..cfe9c946 --- /dev/null +++ b/skills/recipe-share-doc-and-notify/SKILL.md @@ -0,0 +1,25 @@ +--- +name: recipe-share-doc-and-notify +version: 1.0.0 +description: "Share a Google Docs document with edit access and email collaborators the link." +metadata: + openclaw: + category: "recipe" + domain: "productivity" + requires: + bins: ["gws"] + skills: ["gws-drive", "gws-docs", "gws-gmail"] +--- + +# Share a Google Doc and Notify Collaborators + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-drive`, `gws-docs`, `gws-gmail` + +Share a Google Docs document with edit access and email collaborators the link. + +## Steps + +1. Find the doc: `gws drive files list --params '{"q": "name contains '\''Project Brief'\'' and mimeType = '\''application/vnd.google-apps.document'\''"}'` +2. Share with editor access: `gws drive permissions create --params '{"fileId": "DOC_ID"}' --json '{"role": "writer", "type": "user", "emailAddress": "reviewer@company.com"}'` +3. Email the link: `gws gmail +send --to reviewer@company.com --subject 'Please review: Project Brief' --body 'I have shared the project brief with you: https://docs.google.com/document/d/DOC_ID'` + diff --git a/skills/recipe-share-event-materials/SKILL.md b/skills/recipe-share-event-materials/SKILL.md new file mode 100644 index 00000000..88aec827 --- /dev/null +++ b/skills/recipe-share-event-materials/SKILL.md @@ -0,0 +1,25 @@ +--- +name: recipe-share-event-materials +version: 1.0.0 +description: "Share Google Drive files with all attendees of a Google Calendar event." +metadata: + openclaw: + category: "recipe" + domain: "productivity" + requires: + bins: ["gws"] + skills: ["gws-calendar", "gws-drive"] +--- + +# Share Files with Meeting Attendees + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-calendar`, `gws-drive` + +Share Google Drive files with all attendees of a Google Calendar event. + +## Steps + +1. Get event attendees: `gws calendar events get --params '{"calendarId": "primary", "eventId": "EVENT_ID"}'` +2. Share file with each attendee: `gws drive permissions create --params '{"fileId": "FILE_ID"}' --json '{"role": "reader", "type": "user", "emailAddress": "attendee@company.com"}'` +3. Verify sharing: `gws drive permissions list --params '{"fileId": "FILE_ID"}' --format table` + diff --git a/skills/recipe-share-folder-with-team/SKILL.md b/skills/recipe-share-folder-with-team/SKILL.md new file mode 100644 index 00000000..da979a27 --- /dev/null +++ b/skills/recipe-share-folder-with-team/SKILL.md @@ -0,0 +1,26 @@ +--- +name: recipe-share-folder-with-team +version: 1.0.0 +description: "Share a Google Drive folder and all its contents with a list of collaborators." +metadata: + openclaw: + category: "recipe" + domain: "productivity" + requires: + bins: ["gws"] + skills: ["gws-drive"] +--- + +# Share a Google Drive Folder with a Team + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-drive` + +Share a Google Drive folder and all its contents with a list of collaborators. + +## Steps + +1. Find the folder: `gws drive files list --params '{"q": "name = '\''Project X'\'' and mimeType = '\''application/vnd.google-apps.folder'\''"}'` +2. Share as editor: `gws drive permissions create --params '{"fileId": "FOLDER_ID"}' --json '{"role": "writer", "type": "user", "emailAddress": "colleague@company.com"}'` +3. Share as viewer: `gws drive permissions create --params '{"fileId": "FOLDER_ID"}' --json '{"role": "reader", "type": "user", "emailAddress": "stakeholder@company.com"}'` +4. Verify permissions: `gws drive permissions list --params '{"fileId": "FOLDER_ID"}' --format table` + diff --git a/skills/recipe-sync-contacts-to-sheet/SKILL.md b/skills/recipe-sync-contacts-to-sheet/SKILL.md new file mode 100644 index 00000000..794a4f05 --- /dev/null +++ b/skills/recipe-sync-contacts-to-sheet/SKILL.md @@ -0,0 +1,25 @@ +--- +name: recipe-sync-contacts-to-sheet +version: 1.0.0 +description: "Export Google Contacts directory to a Google Sheets spreadsheet." +metadata: + openclaw: + category: "recipe" + domain: "productivity" + requires: + bins: ["gws"] + skills: ["gws-people", "gws-sheets"] +--- + +# Export Google Contacts to Sheets + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-people`, `gws-sheets` + +Export Google Contacts directory to a Google Sheets spreadsheet. + +## Steps + +1. List contacts: `gws people people listDirectoryPeople --params '{"readMask": "names,emailAddresses,phoneNumbers", "sources": ["DIRECTORY_SOURCE_TYPE_DOMAIN_PROFILE"], "pageSize": 100}' --format json` +2. Create a sheet: `gws sheets +append --spreadsheet-id SHEET_ID --range 'Contacts' --values '["Name", "Email", "Phone"]'` +3. Append each contact row: `gws sheets +append --spreadsheet-id SHEET_ID --range 'Contacts' --values '["Jane Doe", "jane@company.com", "+1-555-0100"]'` + diff --git a/skills/recipe-transfer-file-ownership/SKILL.md b/skills/recipe-transfer-file-ownership/SKILL.md new file mode 100644 index 00000000..03d34dca --- /dev/null +++ b/skills/recipe-transfer-file-ownership/SKILL.md @@ -0,0 +1,27 @@ +--- +name: recipe-transfer-file-ownership +version: 1.0.0 +description: "Transfer ownership of Google Drive files from one user to another." +metadata: + openclaw: + category: "recipe" + domain: "it" + requires: + bins: ["gws"] + skills: ["gws-drive"] +--- + +# Transfer File Ownership + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-drive` + +Transfer ownership of Google Drive files from one user to another. + +> [!CAUTION] +> Transferring ownership is irreversible without the new owner's cooperation. + +## Steps + +1. List files owned by the user: `gws drive files list --params '{"q": "'\''user@company.com'\'' in owners"}'` +2. Transfer ownership: `gws drive permissions create --params '{"fileId": "FILE_ID", "transferOwnership": true}' --json '{"role": "owner", "type": "user", "emailAddress": "newowner@company.com"}'` + diff --git a/skills/recipe-triage-security-alerts/SKILL.md b/skills/recipe-triage-security-alerts/SKILL.md new file mode 100644 index 00000000..f5d9a2bf --- /dev/null +++ b/skills/recipe-triage-security-alerts/SKILL.md @@ -0,0 +1,25 @@ +--- +name: recipe-triage-security-alerts +version: 1.0.0 +description: "List and review Google Workspace security alerts from Alert Center." +metadata: + openclaw: + category: "recipe" + domain: "security" + requires: + bins: ["gws"] + skills: ["gws-alertcenter"] +--- + +# Triage Google Workspace Security Alerts + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-alertcenter` + +List and review Google Workspace security alerts from Alert Center. + +## Steps + +1. List active alerts: `gws alertcenter alerts list --format table` +2. Get alert details: `gws alertcenter alerts get --params '{"alertId": "ALERT_ID"}'` +3. Acknowledge an alert: `gws alertcenter alerts undelete --params '{"alertId": "ALERT_ID"}'` + diff --git a/skills/recipe-watch-drive-changes/SKILL.md b/skills/recipe-watch-drive-changes/SKILL.md new file mode 100644 index 00000000..dd77c457 --- /dev/null +++ b/skills/recipe-watch-drive-changes/SKILL.md @@ -0,0 +1,25 @@ +--- +name: recipe-watch-drive-changes +version: 1.0.0 +description: "Subscribe to change notifications on a Google Drive file or folder." +metadata: + openclaw: + category: "recipe" + domain: "engineering" + requires: + bins: ["gws"] + skills: ["gws-events"] +--- + +# Watch for Drive Changes + +> **PREREQUISITE:** Load the following skills to execute this recipe: `gws-events` + +Subscribe to change notifications on a Google Drive file or folder. + +## Steps + +1. Create subscription: `gws events subscriptions create --json '{"targetResource": "//drive.googleapis.com/drives/DRIVE_ID", "eventTypes": ["google.workspace.drive.file.v1.updated"], "notificationEndpoint": {"pubsubTopic": "projects/PROJECT/topics/TOPIC"}, "payloadOptions": {"includeResource": true}}'` +2. List active subscriptions: `gws events subscriptions list` +3. Renew before expiry: `gws events +renew --subscription SUBSCRIPTION_ID` + diff --git a/src/generate_skills.rs b/src/generate_skills.rs index 2f6e5b3a..d0b4edc5 100644 --- a/src/generate_skills.rs +++ b/src/generate_skills.rs @@ -23,11 +23,54 @@ use crate::services; use clap::Command; use std::path::Path; +const PERSONAS_YAML: &str = include_str!("../registry/personas.yaml"); +const RECIPES_YAML: &str = include_str!("../registry/recipes.yaml"); + +#[derive(serde::Deserialize)] +struct PersonaRegistry { + personas: Vec, +} + +#[derive(serde::Deserialize)] +struct PersonaEntry { + name: String, + title: String, + description: String, + services: Vec, + workflows: Vec, + instructions: Vec, + #[serde(default)] + tips: Vec, +} + +#[derive(serde::Deserialize)] +struct RecipeRegistry { + recipes: Vec, +} + +#[derive(serde::Deserialize)] +struct RecipeEntry { + name: String, + title: String, + description: String, + category: String, + services: Vec, + steps: Vec, + caution: Option, +} + +struct SkillIndexEntry { + name: String, + description: String, + category: String, +} + /// Entry point for `gws generate-skills`. pub async fn handle_generate_skills(args: &[String]) -> Result<(), GwsError> { let output_dir = parse_output_dir(args); let output_path = Path::new(&output_dir); let filter = parse_filter(args); + let mut index: Vec = Vec::new(); // Generate gws-shared skill if no filter or "shared" is in the filter if filter @@ -35,6 +78,13 @@ pub async fn handle_generate_skills(args: &[String]) -> Result<(), GwsError> { .is_none_or(|f| "shared".contains(f.as_str())) { generate_shared_skill(output_path)?; + index.push(SkillIndexEntry { + name: "gws-shared".to_string(), + description: + "gws CLI: Shared patterns for authentication, global flags, and output formatting." + .to_string(), + category: "service".to_string(), + }); } for entry in services::SERVICES { @@ -47,15 +97,28 @@ pub async fn handle_generate_skills(args: &[String]) -> Result<(), GwsError> { entry.api_name, entry.version ); - // Fetch discovery doc - let doc = match discovery::fetch_discovery_document(entry.api_name, entry.version).await { - Ok(d) => d, - Err(e) => { - eprintln!(" WARNING: Failed to fetch discovery doc for {alias}: {e}"); - continue; + // Synthetic services (no Discovery doc) use an empty RestDescription + let doc = if entry.api_name == "workflow" { + discovery::RestDescription { + name: "workflow".to_string(), + title: Some("Workflow".to_string()), + description: Some(entry.description.to_string()), + ..Default::default() + } + } else { + // Fetch discovery doc + match discovery::fetch_discovery_document(entry.api_name, entry.version).await { + Ok(d) => d, + Err(e) => { + eprintln!(" WARNING: Failed to fetch discovery doc for {alias}: {e}"); + continue; + } } }; + // Derive product name from Discovery title (e.g. "Google Drive API" -> "Google Drive") + let product_name = product_name_from_title(doc.title.as_deref().unwrap_or(alias)); + // Build the CLI tree (includes helpers) let cli = commands::build_cli(&doc); @@ -78,8 +141,14 @@ pub async fn handle_generate_skills(args: &[String]) -> Result<(), GwsError> { None => true, }; if emit_service { - let service_md = render_service_skill(alias, entry, &helpers, &resources); + let service_md = + render_service_skill(alias, entry, &helpers, &resources, &product_name); write_skill(output_path, &skill_name, &service_md)?; + index.push(SkillIndexEntry { + name: skill_name.clone(), + description: service_description(&product_name, entry.description), + category: "service".to_string(), + }); } // Generate per-helper skills @@ -95,12 +164,94 @@ pub async fn handle_generate_skills(args: &[String]) -> Result<(), GwsError> { }; if emit_helper { let helper_skill_name = format!("gws-{helper_key}"); - let helper_md = render_helper_skill(alias, helper_name, helper, entry); + let about_raw = helper + .get_about() + .map(|s| s.to_string()) + .unwrap_or_default(); + let about_clean = about_raw.strip_prefix("[Helper] ").unwrap_or(&about_raw); + let helper_md = + render_helper_skill(alias, helper_name, helper, entry, &product_name); write_skill(output_path, &helper_skill_name, &helper_md)?; + index.push(SkillIndexEntry { + name: helper_skill_name, + description: truncate_desc(&format!( + "{}: {}", + product_name, + capitalize_first(about_clean) + )), + category: "helper".to_string(), + }); } } } + // Generate Personas + if filter + .as_ref() + .is_none_or(|f| "persona".contains(f.as_str()) || "personas".contains(f.as_str())) + { + if let Ok(registry) = serde_yaml::from_str::(PERSONAS_YAML) { + eprintln!( + "Generating skills for {} personas...", + registry.personas.len() + ); + for persona in registry.personas { + let name = format!("persona-{}", persona.name); + let emit = match &filter { + Some(f) => name.contains(f.as_str()), + None => true, + }; + if emit { + let md = render_persona_skill(&persona); + write_skill(output_path, &name, &md)?; + index.push(SkillIndexEntry { + name: name.clone(), + description: truncate_desc(&persona.description), + category: "persona".to_string(), + }); + } + } + } else { + eprintln!("WARNING: Failed to parse personas.yaml"); + } + } + + // Generate Recipes + if filter + .as_ref() + .is_none_or(|f| "recipe".contains(f.as_str()) || "recipes".contains(f.as_str())) + { + if let Ok(registry) = serde_yaml::from_str::(RECIPES_YAML) { + eprintln!( + "Generating skills for {} recipes...", + registry.recipes.len() + ); + for recipe in registry.recipes { + let name = format!("recipe-{}", recipe.name); + let emit = match &filter { + Some(f) => name.contains(f.as_str()), + None => true, + }; + if emit { + let md = render_recipe_skill(&recipe); + write_skill(output_path, &name, &md)?; + index.push(SkillIndexEntry { + name: name.clone(), + description: truncate_desc(&recipe.description), + category: "recipe".to_string(), + }); + } + } + } else { + eprintln!("WARNING: Failed to parse recipes.yaml"); + } + } + + // Write skills index + if filter.is_none() { + write_skills_index(&index)?; + } + eprintln!("\nDone. Skills written to {output_dir}/"); Ok(()) } @@ -139,6 +290,57 @@ fn write_skill(base: &Path, name: &str, content: &str) -> Result<(), GwsError> { Ok(()) } +fn write_skills_index(entries: &[SkillIndexEntry]) -> Result<(), GwsError> { + let mut out = String::new(); + out.push_str("# Skills Index\n\n"); + out.push_str("> Auto-generated by `gws generate-skills`. Do not edit manually.\n\n"); + + let sections = [ + ( + "service", + "## Services", + "Core Google Workspace API skills.", + ), + ( + "helper", + "## Helpers", + "Shortcut commands for common operations.", + ), + ("persona", "## Personas", "Role-based skill bundles."), + ( + "recipe", + "## Recipes", + "Multi-step task sequences with real commands.", + ), + ]; + + for (cat, heading, subtitle) in §ions { + let items: Vec<&SkillIndexEntry> = entries.iter().filter(|e| e.category == *cat).collect(); + if items.is_empty() { + continue; + } + out.push_str(&format!("{heading}\n\n{subtitle}\n\n")); + out.push_str("| Skill | Description |\n|-------|-------------|\n"); + for item in &items { + out.push_str(&format!( + "| [{}](../skills/{}/SKILL.md) | {} |\n", + item.name, item.name, item.description + )); + } + out.push('\n'); + } + + let path = Path::new("docs/skills.md"); + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent) + .map_err(|e| GwsError::Validation(format!("Failed to create docs dir: {e}")))?; + } + std::fs::write(path, &out) + .map_err(|e| GwsError::Validation(format!("Failed to write skills index: {e}")))?; + eprintln!("Skills index written to docs/skills.md"); + Ok(()) +} + // --------------------------------------------------------------------------- // Renderers // --------------------------------------------------------------------------- @@ -148,15 +350,18 @@ fn render_service_skill( entry: &services::ServiceEntry, helpers: &[&Command], resources: &[&Command], + product_name: &str, ) -> String { let mut out = String::new(); + let trigger_desc = service_description(product_name, entry.description); + // Frontmatter out.push_str(&format!( r#"--- name: gws-{alias} version: 1.0.0 -description: "USE WHEN the user wants to {description} via the `gws` CLI." +description: "{trigger_desc}" metadata: openclaw: category: "productivity" @@ -166,7 +371,6 @@ metadata: --- "#, - description = entry.description.to_lowercase(), )); // Title @@ -253,6 +457,7 @@ fn render_helper_skill( cmd_name: &str, cmd: &Command, entry: &services::ServiceEntry, + product_name: &str, ) -> String { let mut out = String::new(); @@ -260,6 +465,8 @@ fn render_helper_skill( let about = about_raw.strip_prefix("[Helper] ").unwrap_or(&about_raw); let short = cmd_name.trim_start_matches('+'); + let capitalized_about = capitalize_first(about); + let trigger_desc = truncate_desc(&format!("{}: {}", product_name, capitalized_about)); // Determine if write command let is_write = matches!( @@ -284,7 +491,7 @@ fn render_helper_skill( r#"--- name: gws-{alias}-{short} version: 1.0.0 -description: "{about}" +description: "{trigger_desc}" metadata: openclaw: category: "{category}" @@ -431,7 +638,7 @@ fn generate_shared_skill(base: &Path) -> Result<(), GwsError> { let content = r#"--- name: gws-shared version: 1.0.0 -description: "Shared patterns, authentication, and global flags for all gws commands." +description: "gws CLI: Shared patterns for authentication, global flags, and output formatting." metadata: openclaw: category: "productivity" @@ -491,3 +698,265 @@ gws [sub-resource] [flags] write_skill(base, "gws-shared", content) } + +fn render_persona_skill(persona: &PersonaEntry) -> String { + let mut out = String::new(); + + // metadata JSON string for skills array + let required_skills = persona + .services + .iter() + .map(|s| format!("\"gws-{s}\"")) + .collect::>() + .join(", "); + + let trigger_desc = truncate_desc(&persona.description); + + out.push_str(&format!( + r#"--- +name: persona-{name} +version: 1.0.0 +description: "{trigger_desc}" +metadata: + openclaw: + category: "persona" + requires: + bins: ["gws"] + skills: [{skills}] +--- + +# {title} + +> **PREREQUISITE:** Load the following utility skills to operate as this persona: {skills_list} + +{description} + +## Relevant Workflows +{workflows} + +## Instructions +"#, + name = persona.name, + description = persona.description, + title = persona.title, + skills = required_skills, + skills_list = persona + .services + .iter() + .map(|s| format!("`gws-{s}`")) + .collect::>() + .join(", "), + workflows = persona + .workflows + .iter() + .map(|w| format!("- `gws workflow {w}`")) + .collect::>() + .join("\n") + )); + + for inst in &persona.instructions { + out.push_str(&format!("- {inst}\n")); + } + out.push('\n'); + + if !persona.tips.is_empty() { + out.push_str("## Tips\n"); + for tip in &persona.tips { + out.push_str(&format!("- {tip}\n")); + } + out.push('\n'); + } + + out +} + +fn render_recipe_skill(recipe: &RecipeEntry) -> String { + let mut out = String::new(); + + let required_skills = recipe + .services + .iter() + .map(|s| format!("\"gws-{s}\"")) + .collect::>() + .join(", "); + + let trigger_desc = truncate_desc(&recipe.description); + + out.push_str(&format!( + r#"--- +name: recipe-{name} +version: 1.0.0 +description: "{trigger_desc}" +metadata: + openclaw: + category: "recipe" + domain: "{category}" + requires: + bins: ["gws"] + skills: [{skills}] +--- + +# {title} + +> **PREREQUISITE:** Load the following skills to execute this recipe: {skills_list} + +{description} + +"#, + name = recipe.name, + description = recipe.description, + title = recipe.title, + category = recipe.category, + skills = required_skills, + skills_list = recipe + .services + .iter() + .map(|s| format!("`gws-{s}`")) + .collect::>() + .join(", "), + )); + + if let Some(caution) = &recipe.caution { + out.push_str(&format!("> [!CAUTION]\n> {caution}\n\n")); + } + + out.push_str("## Steps\n\n"); + for (i, step) in recipe.steps.iter().enumerate() { + out.push_str(&format!("{}. {}\n", i + 1, step)); + } + out.push('\n'); + + out +} + +fn truncate_desc(desc: &str) -> String { + let mut s = desc.replace('"', "'").trim().to_string(); + // Capitalize first letter + if let Some(first) = s.get(0..1) { + s = format!("{}{}", first.to_uppercase(), &s[1..]); + } + if s.len() > 120 { + if let Some(idx) = s[..120].rfind(' ') { + s = format!("{}...", &s[..idx]); + } else { + s = format!("{}...", &s[..117]); + } + } + // Ensure trailing period + if !s.ends_with('.') && !s.ends_with("...") { + s.push('.'); + } + s +} + +fn capitalize_first(s: &str) -> String { + let mut chars = s.chars(); + match chars.next() { + None => String::new(), + Some(c) => format!("{}{}", c.to_uppercase(), chars.as_str()), + } +} + +fn product_name_from_title(title: &str) -> String { + // Discovery titles are like "Google Drive API", "Gmail API", "Model Armor API" + // Strip " API" suffix to get the product name + let name = title.strip_suffix(" API").unwrap_or(title).trim(); + if name.is_empty() { + return "Unknown".to_string(); + } + // Prepend "Google" if not already present (most Workspace products are "Google X") + // Skip for standalone brands like "Gmail" + if !name.starts_with("Google") && !name.starts_with("Gmail") { + // Workspace management tools get "Google Workspace" prefix + let is_workspace_mgmt = + name.contains("Admin") || name.contains("Enterprise") || name.contains("Reseller"); + if is_workspace_mgmt { + return format!("Google Workspace {name}"); + } + return format!("Google {name}"); + } + name.to_string() +} + +fn service_description(product_name: &str, discovery_desc: &str) -> String { + // If the description already mentions the product name, use it as-is + let desc_lower = discovery_desc.to_lowercase(); + let name_lower = product_name.to_lowercase(); + if desc_lower.contains(&name_lower) { + return truncate_desc(discovery_desc); + } + + // Prepend the product name + truncate_desc(&format!("{product_name}: {discovery_desc}")) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::helpers; + use crate::services; + use clap::Command; + use std::collections::HashSet; + + #[test] + fn test_registry_references() { + let personas: PersonaRegistry = + serde_yaml::from_str(PERSONAS_YAML).expect("valid personas yaml"); + let recipes: RecipeRegistry = + serde_yaml::from_str(RECIPES_YAML).expect("valid recipes yaml"); + + // Valid services mapped by api_name or alias + let all_services = services::SERVICES; + let mut valid_services = HashSet::new(); + for s in all_services { + valid_services.insert(s.api_name); + for alias in s.aliases { + valid_services.insert(*alias); + } + } + // Workflows are synthetic and technically a service, so add it + valid_services.insert("workflow"); + + // Valid workflows + let wf_helper = helpers::get_helper("workflow").expect("workflow helper missing"); + let mut cli = Command::new("test"); + let doc = crate::discovery::RestDescription::default(); + cli = wf_helper.inject_commands(cli, &doc); + let valid_workflows: HashSet<_> = cli + .get_subcommands() + .map(|s| s.get_name().to_string()) + .collect(); + + // Validate personas + for p in personas.personas { + for s in &p.services { + assert!( + valid_services.contains(s.as_str()), + "Persona '{}' refs invalid service '{}'", + p.name, + s + ); + } + for w in &p.workflows { + assert!( + valid_workflows.contains(w.as_str()), + "Persona '{}' refs invalid workflow '{}'", + p.name, + w + ); + } + } + + // Validate recipes + for r in recipes.recipes { + for s in &r.services { + assert!( + valid_services.contains(s.as_str()), + "Recipe '{}' refs invalid service '{}'", + r.name, + s + ); + } + } + } +} diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs index 3d696af1..378f1ea5 100644 --- a/src/helpers/mod.rs +++ b/src/helpers/mod.rs @@ -25,6 +25,7 @@ pub mod gmail; pub mod modelarmor; pub mod script; pub mod sheets; +pub mod workflows; /// A trait for service-specific CLI helpers that inject custom commands. pub trait Helper: Send + Sync { @@ -58,6 +59,7 @@ pub fn get_helper(service: &str) -> Option> { "script" | "apps-script" => Some(Box::new(script::ScriptHelper)), "workspaceevents" => Some(Box::new(events::EventsHelper)), "modelarmor" => Some(Box::new(modelarmor::ModelArmorHelper)), + "workflow" => Some(Box::new(workflows::WorkflowHelper)), _ => None, } } diff --git a/src/helpers/workflows.rs b/src/helpers/workflows.rs new file mode 100644 index 00000000..749b2ec0 --- /dev/null +++ b/src/helpers/workflows.rs @@ -0,0 +1,665 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Cross-service workflow helpers that compose multiple Google Workspace API +//! calls into high-level productivity actions. + +use super::Helper; +use crate::auth; +use crate::error::GwsError; +use clap::{Arg, ArgMatches, Command}; +use serde_json::{json, Value}; +use std::future::Future; +use std::pin::Pin; + +pub struct WorkflowHelper; + +impl Helper for WorkflowHelper { + fn inject_commands( + &self, + mut cmd: Command, + _doc: &crate::discovery::RestDescription, + ) -> Command { + cmd = cmd.subcommand(build_standup_report_cmd()); + cmd = cmd.subcommand(build_meeting_prep_cmd()); + cmd = cmd.subcommand(build_email_to_task_cmd()); + cmd = cmd.subcommand(build_weekly_digest_cmd()); + cmd = cmd.subcommand(build_file_announce_cmd()); + cmd + } + + fn handle<'a>( + &'a self, + _doc: &'a crate::discovery::RestDescription, + matches: &'a ArgMatches, + _sanitize_config: &'a crate::helpers::modelarmor::SanitizeConfig, + ) -> Pin> + Send + 'a>> { + Box::pin(async move { + if let Some(m) = matches.subcommand_matches("+standup-report") { + handle_standup_report(m).await?; + return Ok(true); + } + if let Some(m) = matches.subcommand_matches("+meeting-prep") { + handle_meeting_prep(m).await?; + return Ok(true); + } + if let Some(m) = matches.subcommand_matches("+email-to-task") { + handle_email_to_task(m).await?; + return Ok(true); + } + if let Some(m) = matches.subcommand_matches("+weekly-digest") { + handle_weekly_digest(m).await?; + return Ok(true); + } + if let Some(m) = matches.subcommand_matches("+file-announce") { + handle_file_announce(m).await?; + return Ok(true); + } + Ok(false) + }) + } + + fn helper_only(&self) -> bool { + true + } +} + +// --------------------------------------------------------------------------- +// Command definitions +// --------------------------------------------------------------------------- + +fn build_standup_report_cmd() -> Command { + Command::new("+standup-report") + .about("[Helper] Today's meetings + open tasks as a standup summary") + .arg( + Arg::new("format") + .long("format") + .help("Output format: json (default), table, yaml, csv") + .value_name("FORMAT") + .global(true), + ) + .after_help( + "\ +EXAMPLES: + gws workflow +standup-report + gws workflow +standup-report --format table + +TIPS: + Read-only — never modifies data. + Combines calendar agenda (today) with tasks list.", + ) +} + +fn build_meeting_prep_cmd() -> Command { + Command::new("+meeting-prep") + .about("[Helper] Prepare for your next meeting: agenda, attendees, and linked docs") + .arg( + Arg::new("calendar") + .long("calendar") + .help("Calendar ID (default: primary)") + .default_value("primary") + .value_name("ID"), + ) + .arg( + Arg::new("format") + .long("format") + .help("Output format: json (default), table, yaml, csv") + .value_name("FORMAT") + .global(true), + ) + .after_help( + "\ +EXAMPLES: + gws workflow +meeting-prep + gws workflow +meeting-prep --calendar Work + +TIPS: + Read-only — never modifies data. + Shows the next upcoming event with attendees and description.", + ) +} + +fn build_email_to_task_cmd() -> Command { + Command::new("+email-to-task") + .about("[Helper] Convert a Gmail message into a Google Tasks entry") + .arg( + Arg::new("message-id") + .long("message-id") + .help("Gmail message ID to convert") + .required(true) + .value_name("ID"), + ) + .arg( + Arg::new("tasklist") + .long("tasklist") + .help("Task list ID (default: @default)") + .default_value("@default") + .value_name("ID"), + ) + .after_help( + "\ +EXAMPLES: + gws workflow +email-to-task --message-id MSG_ID + gws workflow +email-to-task --message-id MSG_ID --tasklist LIST_ID + +TIPS: + Reads the email subject as the task title and snippet as notes. + Creates a new task — confirm with the user before executing.", + ) +} + +fn build_weekly_digest_cmd() -> Command { + Command::new("+weekly-digest") + .about("[Helper] Weekly summary: this week's meetings + unread email count") + .arg( + Arg::new("format") + .long("format") + .help("Output format: json (default), table, yaml, csv") + .value_name("FORMAT") + .global(true), + ) + .after_help( + "\ +EXAMPLES: + gws workflow +weekly-digest + gws workflow +weekly-digest --format table + +TIPS: + Read-only — never modifies data. + Combines calendar agenda (week) with gmail triage summary.", + ) +} + +fn build_file_announce_cmd() -> Command { + Command::new("+file-announce") + .about("[Helper] Announce a Drive file in a Chat space") + .arg( + Arg::new("file-id") + .long("file-id") + .help("Drive file ID to announce") + .required(true) + .value_name("ID"), + ) + .arg( + Arg::new("space") + .long("space") + .help("Chat space name (e.g. spaces/SPACE_ID)") + .required(true) + .value_name("SPACE"), + ) + .arg( + Arg::new("message") + .long("message") + .help("Custom announcement message") + .value_name("TEXT"), + ) + .arg( + Arg::new("format") + .long("format") + .help("Output format: json (default), table, yaml, csv") + .value_name("FORMAT") + .global(true), + ) + .after_help( + "\ +EXAMPLES: + gws workflow +file-announce --file-id FILE_ID --space spaces/ABC123 + gws workflow +file-announce --file-id FILE_ID --space spaces/ABC123 --message 'Check this out!' + +TIPS: + This is a write command — sends a Chat message. + Use `gws drive +upload` first to upload the file, then announce it here. + Fetches the file name from Drive to build the announcement.", + ) +} + +// --------------------------------------------------------------------------- +// Handlers +// --------------------------------------------------------------------------- + +async fn get_json(client: &reqwest::Client, url: &str, token: &str) -> Result { + let resp = client + .get(url) + .bearer_auth(token) + .send() + .await + .map_err(|e| GwsError::Other(anyhow::anyhow!("HTTP request failed: {e}")))?; + + if !resp.status().is_success() { + let status = resp.status(); + let body = resp.text().await.unwrap_or_default(); + return Err(GwsError::Api { + code: status.as_u16(), + message: body, + reason: "workflow_request_failed".to_string(), + }); + } + + resp.json::() + .await + .map_err(|e| GwsError::Other(anyhow::anyhow!("JSON parse failed: {e}"))) +} + +fn format_and_print(value: &Value, matches: &ArgMatches) { + let fmt = matches + .get_one::("format") + .map(|s| crate::formatter::OutputFormat::from_str(s)) + .unwrap_or_default(); + println!("{}", crate::formatter::format_value(value, &fmt)); +} + +async fn handle_standup_report(matches: &ArgMatches) -> Result<(), GwsError> { + let cal_scope = "https://www.googleapis.com/auth/calendar.readonly"; + let tasks_scope = "https://www.googleapis.com/auth/tasks.readonly"; + let token = auth::get_token(&[cal_scope, tasks_scope]) + .await + .map_err(|e| GwsError::Auth(format!("Auth failed: {e}")))?; + + let client = crate::client::build_client()?; + + // Today's time range + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs(); + let day_start = (now / 86400) * 86400; + let day_end = day_start + 86400; + let time_min = epoch_to_rfc3339(day_start); + let time_max = epoch_to_rfc3339(day_end); + + // Fetch today's events + let events_url = format!( + "https://www.googleapis.com/calendar/v3/calendars/primary/events?timeMin={}&timeMax={}&singleEvents=true&orderBy=startTime&maxResults=25", + urlencoded(&time_min), + urlencoded(&time_max), + ); + let events_json = get_json(&client, &events_url, &token) + .await + .unwrap_or(json!({})); + let events = events_json + .get("items") + .and_then(|i| i.as_array()) + .cloned() + .unwrap_or_default(); + + let meetings: Vec = events + .iter() + .map(|e| { + json!({ + "summary": e.get("summary").and_then(|v| v.as_str()).unwrap_or("(No title)"), + "start": e.get("start").and_then(|s| s.get("dateTime").or(s.get("date"))).and_then(|v| v.as_str()).unwrap_or(""), + "end": e.get("end").and_then(|s| s.get("dateTime").or(s.get("date"))).and_then(|v| v.as_str()).unwrap_or(""), + }) + }) + .collect(); + + // Fetch open tasks + let tasks_url = "https://tasks.googleapis.com/tasks/v1/lists/@default/tasks?showCompleted=false&maxResults=20"; + let tasks_json = get_json(&client, tasks_url, &token) + .await + .unwrap_or(json!({})); + let tasks = tasks_json + .get("items") + .and_then(|i| i.as_array()) + .cloned() + .unwrap_or_default(); + + let open_tasks: Vec = tasks + .iter() + .map(|t| { + json!({ + "title": t.get("title").and_then(|v| v.as_str()).unwrap_or(""), + "due": t.get("due").and_then(|v| v.as_str()).unwrap_or(""), + }) + }) + .collect(); + + let output = json!({ + "meetings": meetings, + "meetingCount": meetings.len(), + "tasks": open_tasks, + "taskCount": open_tasks.len(), + "date": time_min.split('T').next().unwrap_or(""), + }); + + format_and_print(&output, matches); + Ok(()) +} + +async fn handle_meeting_prep(matches: &ArgMatches) -> Result<(), GwsError> { + let cal_scope = "https://www.googleapis.com/auth/calendar.readonly"; + let token = auth::get_token(&[cal_scope]) + .await + .map_err(|e| GwsError::Auth(format!("Auth failed: {e}")))?; + + let client = crate::client::build_client()?; + let calendar_id = matches + .get_one::("calendar") + .map(|s| s.as_str()) + .unwrap_or("primary"); + + // Fetch next upcoming event + let now_rfc = epoch_to_rfc3339( + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs(), + ); + + let events_url = format!( + "https://www.googleapis.com/calendar/v3/calendars/{}/events?timeMin={}&singleEvents=true&orderBy=startTime&maxResults=1", + urlencoded(calendar_id), + urlencoded(&now_rfc), + ); + let events_json = get_json(&client, &events_url, &token).await?; + let items = events_json + .get("items") + .and_then(|i| i.as_array()) + .cloned() + .unwrap_or_default(); + + if items.is_empty() { + let output = json!({ "message": "No upcoming meetings found." }); + format_and_print(&output, matches); + return Ok(()); + } + + let event = &items[0]; + let attendees = event + .get("attendees") + .and_then(|a| a.as_array()) + .cloned() + .unwrap_or_default(); + + let attendee_list: Vec = attendees + .iter() + .map(|a| { + json!({ + "email": a.get("email").and_then(|v| v.as_str()).unwrap_or(""), + "responseStatus": a.get("responseStatus").and_then(|v| v.as_str()).unwrap_or(""), + }) + }) + .collect(); + + let output = json!({ + "summary": event.get("summary").and_then(|v| v.as_str()).unwrap_or("(No title)"), + "start": event.get("start").and_then(|s| s.get("dateTime").or(s.get("date"))).and_then(|v| v.as_str()).unwrap_or(""), + "end": event.get("end").and_then(|s| s.get("dateTime").or(s.get("date"))).and_then(|v| v.as_str()).unwrap_or(""), + "description": event.get("description").and_then(|v| v.as_str()).unwrap_or(""), + "location": event.get("location").and_then(|v| v.as_str()).unwrap_or(""), + "hangoutLink": event.get("hangoutLink").and_then(|v| v.as_str()).unwrap_or(""), + "htmlLink": event.get("htmlLink").and_then(|v| v.as_str()).unwrap_or(""), + "attendees": attendee_list, + "attendeeCount": attendee_list.len(), + }); + + format_and_print(&output, matches); + Ok(()) +} + +async fn handle_email_to_task(matches: &ArgMatches) -> Result<(), GwsError> { + let gmail_scope = "https://www.googleapis.com/auth/gmail.readonly"; + let tasks_scope = "https://www.googleapis.com/auth/tasks"; + let token = auth::get_token(&[gmail_scope, tasks_scope]) + .await + .map_err(|e| GwsError::Auth(format!("Auth failed: {e}")))?; + + let client = crate::client::build_client()?; + let message_id = matches.get_one::("message-id").unwrap(); + let tasklist = matches + .get_one::("tasklist") + .map(|s| s.as_str()) + .unwrap_or("@default"); + + // 1. Fetch the email + let msg_url = format!( + "https://gmail.googleapis.com/gmail/v1/users/me/messages/{}?format=metadata&metadataHeaders=Subject", + message_id, + ); + let msg_json = get_json(&client, &msg_url, &token).await?; + + let subject = msg_json + .get("payload") + .and_then(|p| p.get("headers")) + .and_then(|h| h.as_array()) + .and_then(|headers| { + headers.iter().find(|h| { + h.get("name") + .and_then(|n| n.as_str()) + .is_some_and(|n| n.eq_ignore_ascii_case("Subject")) + }) + }) + .and_then(|h| h.get("value")) + .and_then(|v| v.as_str()) + .unwrap_or("(No subject)"); + + let snippet = msg_json + .get("snippet") + .and_then(|v| v.as_str()) + .unwrap_or(""); + + // 2. Create the task + let task_body = json!({ + "title": subject, + "notes": format!("From email: {}\n\n{}", message_id, snippet), + }); + + let task_url = format!( + "https://tasks.googleapis.com/tasks/v1/lists/{}/tasks", + tasklist, + ); + + let resp = client + .post(&task_url) + .bearer_auth(&token) + .json(&task_body) + .send() + .await + .map_err(|e| GwsError::Other(anyhow::anyhow!("Failed to create task: {e}")))?; + + if !resp.status().is_success() { + let status = resp.status(); + let body = resp.text().await.unwrap_or_default(); + return Err(GwsError::Api { + code: status.as_u16(), + message: body, + reason: "task_create_failed".to_string(), + }); + } + + let task_result: Value = resp.json().await.unwrap_or(json!({})); + let output = json!({ + "created": true, + "taskId": task_result.get("id").and_then(|v| v.as_str()).unwrap_or(""), + "title": subject, + "sourceMessageId": message_id, + }); + + format_and_print(&output, matches); + Ok(()) +} + +async fn handle_weekly_digest(matches: &ArgMatches) -> Result<(), GwsError> { + let cal_scope = "https://www.googleapis.com/auth/calendar.readonly"; + let gmail_scope = "https://www.googleapis.com/auth/gmail.readonly"; + let token = auth::get_token(&[cal_scope, gmail_scope]) + .await + .map_err(|e| GwsError::Auth(format!("Auth failed: {e}")))?; + + let client = crate::client::build_client()?; + + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs(); + let week_end = now + 7 * 86400; + let time_min = epoch_to_rfc3339(now); + let time_max = epoch_to_rfc3339(week_end); + + // Fetch this week's events + let events_url = format!( + "https://www.googleapis.com/calendar/v3/calendars/primary/events?timeMin={}&timeMax={}&singleEvents=true&orderBy=startTime&maxResults=50", + urlencoded(&time_min), + urlencoded(&time_max), + ); + let events_json = get_json(&client, &events_url, &token) + .await + .unwrap_or(json!({})); + let events = events_json + .get("items") + .and_then(|i| i.as_array()) + .cloned() + .unwrap_or_default(); + + let meetings: Vec = events + .iter() + .map(|e| { + json!({ + "summary": e.get("summary").and_then(|v| v.as_str()).unwrap_or("(No title)"), + "start": e.get("start").and_then(|s| s.get("dateTime").or(s.get("date"))).and_then(|v| v.as_str()).unwrap_or(""), + }) + }) + .collect(); + + // Fetch unread email count + let gmail_url = + "https://gmail.googleapis.com/gmail/v1/users/me/messages?q=is%3Aunread&maxResults=1"; + let gmail_json = get_json(&client, gmail_url, &token) + .await + .unwrap_or(json!({})); + let unread_estimate = gmail_json + .get("resultSizeEstimate") + .and_then(|v| v.as_u64()) + .unwrap_or(0); + + let output = json!({ + "meetings": meetings, + "meetingCount": meetings.len(), + "unreadEmails": unread_estimate, + "periodStart": time_min, + "periodEnd": time_max, + }); + + format_and_print(&output, matches); + Ok(()) +} + +async fn handle_file_announce(matches: &ArgMatches) -> Result<(), GwsError> { + let drive_scope = "https://www.googleapis.com/auth/drive.readonly"; + let chat_scope = "https://www.googleapis.com/auth/chat.messages.create"; + let token = auth::get_token(&[drive_scope, chat_scope]) + .await + .map_err(|e| GwsError::Auth(format!("Auth failed: {e}")))?; + + let client = crate::client::build_client()?; + let file_id = matches.get_one::("file-id").unwrap(); + let space = matches.get_one::("space").unwrap(); + let custom_msg = matches.get_one::("message"); + + // 1. Fetch file metadata from Drive + let file_url = format!( + "https://www.googleapis.com/drive/v3/files/{}?fields=id,name,webViewLink", + file_id, + ); + let file_json = get_json(&client, &file_url, &token).await?; + let file_name = file_json + .get("name") + .and_then(|v| v.as_str()) + .unwrap_or("file"); + let default_link = format!("https://drive.google.com/file/d/{}/view", file_id); + let file_link = file_json + .get("webViewLink") + .and_then(|v| v.as_str()) + .unwrap_or(&default_link); + + // 2. Send Chat message + let msg_text = custom_msg + .map(|m| format!("{m}\n{file_link}")) + .unwrap_or_else(|| format!("📎 {file_name}\n{file_link}")); + + let chat_body = json!({ "text": msg_text }); + let chat_url = format!("https://chat.googleapis.com/v1/{}/messages", space); + + let chat_resp = client + .post(&chat_url) + .bearer_auth(&token) + .json(&chat_body) + .send() + .await + .map_err(|e| GwsError::Other(anyhow::anyhow!("Chat send failed: {e}")))?; + + if !chat_resp.status().is_success() { + let status = chat_resp.status(); + let body = chat_resp.text().await.unwrap_or_default(); + return Err(GwsError::Api { + code: status.as_u16(), + message: body, + reason: "chat_send_failed".to_string(), + }); + } + + let output = json!({ + "announced": true, + "fileName": file_name, + "fileLink": file_link, + "space": space, + }); + + format_and_print(&output, matches); + Ok(()) +} + +// --------------------------------------------------------------------------- +// Utilities +// --------------------------------------------------------------------------- + +fn epoch_to_rfc3339(epoch: u64) -> String { + use chrono::{TimeZone, Utc}; + Utc.timestamp_opt(epoch as i64, 0).unwrap().to_rfc3339() +} + +fn urlencoded(s: &str) -> String { + s.replace('%', "%25") + .replace(' ', "%20") + .replace('@', "%40") + .replace('+', "%2B") + .replace(':', "%3A") +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_inject_commands() { + let helper = WorkflowHelper; + let cmd = Command::new("test"); + let doc = crate::discovery::RestDescription::default(); + let cmd = helper.inject_commands(cmd, &doc); + let names: Vec<_> = cmd + .get_subcommands() + .map(|s| s.get_name().to_string()) + .collect(); + assert!(names.contains(&"+standup-report".to_string())); + assert!(names.contains(&"+meeting-prep".to_string())); + assert!(names.contains(&"+email-to-task".to_string())); + assert!(names.contains(&"+weekly-digest".to_string())); + assert!(names.contains(&"+file-announce".to_string())); + } + + #[test] + fn test_helper_only() { + assert!(WorkflowHelper.helper_only()); + } +} diff --git a/src/main.rs b/src/main.rs index 2d6bb14a..54644f31 100644 --- a/src/main.rs +++ b/src/main.rs @@ -104,10 +104,19 @@ async fn run() -> Result<(), GwsError> { // Parse service name and optional version override let (api_name, version) = parse_service_and_version(&args, first_arg)?; - // Fetch the Discovery Document - let doc = discovery::fetch_discovery_document(&api_name, &version) - .await - .map_err(|e| GwsError::Discovery(format!("{e:#}")))?; + // For synthetic services (no Discovery doc), use an empty RestDescription + let doc = if api_name == "workflow" { + discovery::RestDescription { + name: "workflow".to_string(), + description: Some("Cross-service productivity workflows".to_string()), + ..Default::default() + } + } else { + // Fetch the Discovery Document + discovery::fetch_discovery_document(&api_name, &version) + .await + .map_err(|e| GwsError::Discovery(format!("{e:#}")))? + }; // Build the dynamic command tree (all commands shown regardless of auth state) let cli = commands::build_cli(&doc); diff --git a/src/services.rs b/src/services.rs index fc5b1aa7..067831c8 100644 --- a/src/services.rs +++ b/src/services.rs @@ -168,6 +168,12 @@ pub const SERVICES: &[ServiceEntry] = &[ version: "v1", description: "Filter user-generated content for safety", }, + ServiceEntry { + aliases: &["workflow", "wf"], + api_name: "workflow", + version: "v1", + description: "Cross-service productivity workflows", + }, ]; /// Resolves a service alias to (api_name, version). diff --git a/src/setup.rs b/src/setup.rs index ea78f9f7..7da40655 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -1442,7 +1442,10 @@ mod tests { fn test_workspace_api_ids_covers_services() { let api_ids = all_api_ids(); for entry in services::SERVICES { - if entry.api_name == "modelarmor" || entry.api_name == "workspaceevents" { + if entry.api_name == "modelarmor" + || entry.api_name == "workspaceevents" + || entry.api_name == "workflow" + { continue; } let expected_suffix = if entry.api_name == "calendar" {