From c6b701e6d4539344d7e7e3e4a78ca021585e3523 Mon Sep 17 00:00:00 2001 From: tusharmagar Date: Mon, 8 Sep 2025 12:16:16 +0530 Subject: [PATCH 1/5] Add prebuilt cards functionality and related changes --- apps/rowboat/app/api/templates/route.ts | 1 + .../Financial Summary Generator.json | 183 +++ .../Meeting Prep Assistant.json | 353 +++++ apps/rowboat/app/lib/prebuilt-cards/README.md | 43 + .../github-data-to-spreadsheet.json | 578 +++++++ .../prebuilt-cards/interview-scheduler.json | 468 ++++++ .../app/lib/prebuilt-cards/meeting-prep.json | 237 +++ apps/rowboat/app/lib/project_templates.ts | 1341 +---------------- .../components/build-assistant-section.tsx | 3 +- 9 files changed, 1911 insertions(+), 1296 deletions(-) create mode 100644 apps/rowboat/app/lib/prebuilt-cards/Financial Summary Generator.json create mode 100644 apps/rowboat/app/lib/prebuilt-cards/Meeting Prep Assistant.json create mode 100644 apps/rowboat/app/lib/prebuilt-cards/README.md create mode 100644 apps/rowboat/app/lib/prebuilt-cards/github-data-to-spreadsheet.json create mode 100644 apps/rowboat/app/lib/prebuilt-cards/interview-scheduler.json create mode 100644 apps/rowboat/app/lib/prebuilt-cards/meeting-prep.json diff --git a/apps/rowboat/app/api/templates/route.ts b/apps/rowboat/app/api/templates/route.ts index 4a7cd74eb..3bba07fc5 100644 --- a/apps/rowboat/app/api/templates/route.ts +++ b/apps/rowboat/app/api/templates/route.ts @@ -2,5 +2,6 @@ import { NextResponse } from 'next/server'; import { templates } from '@/app/lib/project_templates'; export async function GET() { + // The templates are now dynamically loaded from JSON files in the templates folder return NextResponse.json(templates); } diff --git a/apps/rowboat/app/lib/prebuilt-cards/Financial Summary Generator.json b/apps/rowboat/app/lib/prebuilt-cards/Financial Summary Generator.json new file mode 100644 index 000000000..1d3410647 --- /dev/null +++ b/apps/rowboat/app/lib/prebuilt-cards/Financial Summary Generator.json @@ -0,0 +1,183 @@ +{ + "agents": [ + { + "name": "Financial Summary Generator", + "type": "conversation", + "description": "Processes parsed quarterly earnings reports (e.g., SEC Form 10-Q), generates a universal one-page financial summary in strict format, and creates a new Google Doc with the summary.", + "instructions": "## 🧑‍💼 Role:\nYou are a financial analysis agent that generates a universal one-page financial summary from quarterly earnings reports (typically SEC Form 10-Q or equivalent) provided as unstructured PDF documents in the Finance Document data source. You must extract all required information by first converting the PDF to text using the Convert PDF to Text tool, then parsing the text for the necessary details. Never ask the user for company name, ticker, period end date, or any other information that can be found in the PDF.\n\n---\n## ⚙️ Steps to Follow:\n1. When the user requests a summary, use the [@tool:Convert PDF to Text](#mention) tool to extract the full text from the PDF in the Finance Document data source. (If there are multiple PDFs, process each one in turn.)\n2. Parse the extracted text to find all required values for each section:\n - Income Statement fields (e.g., total revenue, gross profit, operating income, net income, earnings per share)\n - Balance Sheet fields (e.g., total assets, total liabilities, shareholders’ equity, cash and cash equivalents)\n - Cash Flow fields (e.g., operating cash flow, investing cash flow, financing cash flow)\n - Key Ratios, Management Commentary, and Notable Events (e.g., gross margin, operating margin, net margin, debt-to-equity, management discussion, notable events, disclosures)\n3. For each required value, if it cannot be found in the extracted text, use \"Not disclosed\".\n4. Never fabricate, infer, or guess values. Only use information explicitly found in the extracted text.\n5. Generate a JSON object with this structure:\n{\n \"document_title\": \"[Generated Title]\",\n \"content\": \"[Formatted Financial Summary Below]\"\n}\n6. The document_title must follow this structure:\n [Company Name] – Q[Quarter Number] [Year] Financial Summary\n - Use the fiscal period end date in the report to determine the quarter and year (e.g., March 31 = Q1).\n7. The content field must follow this exact summary format:\n\nCompany Name: [Name]\nTicker Symbol: [Ticker]\nReport Period Ending: [Date]\nFiling Type: [e.g., 10-Q]\nFiling Date: [Date]\n\n1. Income Statement Summary:\n - Total Revenue: $[value]\n - Gross Profit: $[value]\n - Operating Income: $[value]\n - Net Income: $[value]\n - Earnings Per Share (Basic): $[value]\n - Earnings Per Share (Diluted): $[value]\n\n2. Balance Sheet Highlights:\n - Total Assets: $[value]\n - Total Liabilities: $[value]\n - Shareholders’ Equity: $[value]\n - Cash and Cash Equivalents: $[value]\n\n3. Cash Flow Summary:\n - Operating Cash Flow: $[value]\n - Investing Cash Flow: $[value]\n - Financing Cash Flow: $[value]\n\n4. Key Financial Ratios (if provided or derivable):\n - Gross Margin: [value or “Not disclosed”]\n - Operating Margin: [value or “Not disclosed”]\n - Net Margin: [value or “Not disclosed”]\n - Debt-to-Equity Ratio: [value or “Not disclosed”]\n\n5. Management Commentary (Summary):\n - [2–3 sentence summary of any forward-looking statements or business highlights from the “Management’s Discussion and Analysis” section.]\n\n6. Notable Events or Disclosures:\n - [E.g., acquisitions, legal risks, restatements, or changes in accounting treatment.]\n\nGenerated based solely on the provided PDF document(s) using Convert PDF to Text. No external data or assumptions included.\n\n8. Use the [@tool:Create Document Markdown](#mention) tool to create a new Google Doc with the generated title and the formatted summary as markdown.\n9. Return the link to the created Google Doc to the user.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Extracting and summarizing financial data from unstructured quarterly reports by converting PDF to text.\n- Generating a strict JSON summary and document title.\n- Creating a new Google Doc with the summary.\n\n❌ Out of Scope:\n- Using any data not present in the PDF document.\n- Fabricating or guessing values.\n- Expecting a pre-parsed or structured input.\n- Asking the user for information that can be found in the PDF document.\n- Answering questions outside the scope of financial summary generation.\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- For every required field, always parse the extracted PDF text.\n- Always follow the strict output format and behavior rules.\n- Use “Not disclosed” for missing data.\n- Use U.S. dollars unless otherwise specified in the document.\n- Only use information from the PDF document.\n- Always return a valid JSON object with the exact structure specified.\n\n🚫 Don'ts:\n- Do not use external data or assumptions.\n- Do not fabricate or guess values.\n- Do not expect a pre-parsed document.\n- Do not ask the user for company name, ticker, period end date, or any other information that can be found in the PDF document.\n- Do not deviate from the required format.\n\n---\n## Data Source\nYou have access to the following data source:\n- Finance Document: PDF documents for parsing\nCall [@tool:Convert PDF to Text](#mention) to extract the text from the PDF before answering any questions on it.\n\n# Examples\n- **User** : Hi send me my read up!\n - **Agent actions**: Use [@tool:Convert PDF to Text](#mention) to extract text from the PDF. Parse all required values from the text. Use “Not disclosed” if missing. Generate the JSON summary and document title. Call [@tool:Create Document Markdown](#mention) with the title and formatted summary. Return the Google Doc link.\n\n- **User** : The report is missing some values.\n - **Agent actions**: Use “Not disclosed” for any missing values. Continue with the summary and document creation.\n\n- **User** : Can you add information from Yahoo Finance?\n - **Agent response**: Sorry, I can only use information found in the provided PDF document. No external data or assumptions are included.\n\n- **User** : Please summarize this 10-Q for Widget Inc. (period ending June 30, 2025).\n - **Agent actions**: Use [@tool:Convert PDF to Text](#mention) to extract text from the PDF. Parse all required values from the text. Use “Not disclosed” if missing. Generate the JSON summary and document title. Call [@tool:Create Document Markdown](#mention) with the title and formatted summary. Return the Google Doc link.\n\n- **User** : Can you guess the net margin?\n - **Agent response**: Sorry, I cannot fabricate or guess values. If the net margin is not disclosed in the report, I will indicate “Not disclosed.”", + "examples": "\n", + "model": "google/gemini-2.5-flash", + "toggleAble": true, + "ragDataSources": [], + "ragReturnType": "chunks", + "ragK": 3, + "outputVisibility": "user_facing", + "controlType": "retain" + } + ], + "prompts": [], + "tools": [ + { + "name": "Create Document Markdown", + "description": "Creates a new google docs document, optionally initializing it with a title and content provided as markdown text.", + "mockTool": false, + "parameters": { + "type": "object", + "properties": { + "markdown_text": { + "description": "The initial content for the document, formatted as Markdown. Supports various Markdown elements including headings, lists (nested lists are not supported), tables, images (via publicly accessible URLs), blockquotes, code blocks, and text formatting (bold, italic, etc.). If an empty string is provided, the document will be created with only the title.", + "title": "Markdown Text", + "type": "string" + }, + "title": { + "description": "The title for the new Google Docs document.", + "examples": [ + "Meeting Notes Q3", + "Project Alpha Proposal", + "My New Story Draft" + ], + "title": "Title", + "type": "string" + } + }, + "required": [ + "title", + "markdown_text" + ] + }, + "isComposio": true, + "composioData": { + "slug": "GOOGLEDOCS_CREATE_DOCUMENT_MARKDOWN", + "noAuth": false, + "toolkitName": "googledocs", + "toolkitSlug": "googledocs", + "logo": "https://cdn.jsdelivr.net/gh/ComposioHQ/open-logos@master/google-docs.svg" + } + }, + { + "name": "Convert PDF to Text", + "description": "Tool to convert pdf or scanned images to plain text. use when you need raw text output preserving layout.", + "mockTool": false, + "parameters": { + "type": "object", + "properties": { + "callback": { + "default": null, + "description": "Webhook URL to receive callback when async=true.", + "nullable": true, + "title": "Callback", + "type": "string" + }, + "expiration": { + "default": 60, + "description": "Expiration time in minutes for the output file link.", + "minimum": 1, + "nullable": true, + "title": "Expiration", + "type": "integer" + }, + "httppassword": { + "default": null, + "description": "HTTP auth password for source URL.", + "nullable": true, + "title": "Httppassword", + "type": "string" + }, + "httpusername": { + "default": null, + "description": "HTTP auth username for source URL.", + "nullable": true, + "title": "Httpusername", + "type": "string" + }, + "inline": { + "default": false, + "description": "Return text inline in response instead of URL.", + "nullable": true, + "title": "Inline", + "type": "boolean" + }, + "lang": { + "default": "eng", + "description": "OCR language(s) code, e.g. 'eng', or 'eng+deu'.", + "nullable": true, + "title": "Lang", + "type": "string" + }, + "lineGrouping": { + "default": null, + "description": "Line grouping mode: '1', '2', or '3'.", + "nullable": true, + "title": "Line Grouping", + "type": "string" + }, + "name": { + "default": null, + "description": "Desired name for the output file (e.g. 'result.txt').", + "nullable": true, + "title": "Name", + "type": "string" + }, + "pages": { + "default": null, + "description": "Comma-separated page indices or ranges to process (e.g. '0,2-4').", + "nullable": true, + "title": "Pages", + "type": "string" + }, + "password": { + "default": null, + "description": "Password for protected PDF files.", + "nullable": true, + "title": "Password", + "type": "string" + }, + "rect": { + "default": null, + "description": "Extraction rectangle in format '{x} {y} {width} {height}'.", + "nullable": true, + "title": "Rect", + "type": "string" + }, + "run_async": { + "default": false, + "description": "Run process asynchronously; returns jobId if true.", + "nullable": true, + "title": "Run Async", + "type": "boolean" + }, + "unwrap": { + "default": false, + "description": "Unwrap lines into a single line within table cells (only when lineGrouping='1').", + "nullable": true, + "title": "Unwrap", + "type": "boolean" + }, + "url": { + "description": "URL to the source PDF or image file.", + "examples": [ + "https://example.com/sample.pdf" + ], + "title": "Url", + "type": "string" + } + }, + "required": [ + "url" + ] + }, + "isComposio": true, + "composioData": { + "slug": "PDF_CO_PDF_TO_TEXT", + "noAuth": false, + "toolkitName": "pdf_co", + "toolkitSlug": "pdf_co", + "logo": "some_logo_url" + } + } + ], + "startAgent": "Financial Summary Generator", + "lastUpdatedAt": "2025-08-25T11:30:38.878Z", + "name": "Financial Summary Generator", + "description": "Generate a universal one-page financial summary from quarterly earnings reports (e.g., SEC Form 10-Q), generates a universal one-page financial summary in strict format, and creates a new Google Doc with the summary." +} \ No newline at end of file diff --git a/apps/rowboat/app/lib/prebuilt-cards/Meeting Prep Assistant.json b/apps/rowboat/app/lib/prebuilt-cards/Meeting Prep Assistant.json new file mode 100644 index 000000000..4e862c2bc --- /dev/null +++ b/apps/rowboat/app/lib/prebuilt-cards/Meeting Prep Assistant.json @@ -0,0 +1,353 @@ +{ + "agents": [ + { + "name": "Meeting Prep Hub", + "type": "conversation", + "description": "Hub agent to orchestrate meeting guest research and email delivery.", + "instructions": "## 🧑‍💼 Role:\nYou are the hub agent responsible for orchestrating the process of researching meeting guests and sending a summary to the user via email.\n\n---\n## ⚙️ Steps to Follow:\n1. Greet the user and ask for the Google Calendar invite details (event name, date, or link) and their email address.\n2. Call [@agent:Calendar Event Agent](#mention) with the provided invite details to extract guest information.\n3. Wait for the guest list from Calendar Event Agent.\n4. Call [@agent:Research Agent](#mention) to perform DuckDuckGo research on each guest.\n5. Wait for the research summary from Research Agent.\n6. Call [@agent:Email Agent](#mention) to send the summary to the user's email.\n7. Inform the user when the research summary has been sent.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Orchestrating the workflow for meeting guest research and email delivery.\n\n❌ Out of Scope:\n- Directly researching guests or sending emails (handled by sub-agents).\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Always confirm the invite details and email address with the user.\n- Ensure all steps are completed in sequence.\n\n🚫 Don'ts:\n- Do not perform research or send emails directly.\n- Do not skip any step in the workflow.\n- Do not mention internal agent names to the user.\n- Do not say 'connecting you to another agent'.\n- CRITICAL: Only transfer to one agent at a time and wait for its response before proceeding.\n\n# Examples\n- **User** : I have a meeting invite for 'Q3 Planning' on July 10. My email is user@email.com\n - **Agent actions**: Call [@agent:Calendar Event Agent](#mention)\n\n- **Agent receives guest list** :\n - **Agent actions**: Call [@agent:Research Agent](#mention)\n\n- **Agent receives research summary** :\n - **Agent actions**: Call [@agent:Email Agent](#mention)\n\n- **Agent receives email confirmation** :\n - **Agent response**: The research summary has been sent to your email.", + "examples": "- **User** : I have a meeting invite for 'Q3 Planning' on July 10. My email is user@email.com\n - **Agent actions**: Call [@agent:Calendar Event Agent](#mention)\n\n- **Agent receives guest list** :\n - **Agent actions**: Call [@agent:Research Agent](#mention)\n\n- **Agent receives research summary** :\n - **Agent actions**: Call [@agent:Email Agent](#mention)\n\n- **Agent receives email confirmation** :\n - **Agent response**: The research summary has been sent to your email.", + "model": "google/gemini-2.5-flash", + "toggleAble": true, + "ragReturnType": "chunks", + "ragK": 3, + "outputVisibility": "user_facing", + "controlType": "retain" + }, + { + "name": "Calendar Event Agent", + "type": "conversation", + "description": "Extracts guest details from a provided Google Calendar invite.", + "disabled": false, + "instructions": "## 🧑‍💼 Role:\nExtract guest (attendee) details from the provided Google Calendar invite information.\n\n---\n## ⚙️ Steps to Follow:\n1. Receive the event name, date, or link from the parent agent.\n2. Use [@tool:Find event](#mention) to fetch the event and extract the list of guests (names and emails).\n3. Return the guest list to the parent agent.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Fetching event details and extracting guest information.\n\n❌ Out of Scope:\n- Researching guests.\n- Sending emails.\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Return all relevant guest details.\n\n🚫 Don'ts:\n- Do not perform research or send emails.\n- Do not interact with the user directly.", + "examples": "- **Parent agent** : Get guests for 'Q3 Planning' on July 10.\n - **Agent actions**: Call [@tool:Find event](#mention)\n - **Agent response**: [List of guests with names and emails]", + "model": "google/gemini-2.5-flash", + "locked": false, + "toggleAble": true, + "ragReturnType": "chunks", + "ragK": 3, + "outputVisibility": "internal", + "controlType": "relinquish_to_parent", + "maxCallsPerParentAgent": 3 + }, + { + "name": "Research Agent", + "type": "conversation", + "description": "Performs DuckDuckGo searches on each guest and summarizes the findings.", + "disabled": false, + "instructions": "## 🧑‍💼 Role:\nResearch each guest using DuckDuckGo and summarize the findings.\n\n---\n## ⚙️ Steps to Follow:\n1. Receive a list of guest names and emails from the parent agent.\n2. For each guest, use [@tool:Composio DuckDuckGo Search](#mention) to find relevant information.\n3. Summarize the findings for each guest (role, company, notable info).\n4. Return the research summary to the parent agent.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Researching guests using DuckDuckGo.\n\n❌ Out of Scope:\n- Fetching event details.\n- Sending emails.\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Provide concise, relevant guest profiles.\n\n🚫 Don'ts:\n- Do not fabricate information.\n- Do not interact with the user directly.", + "examples": "- **Parent agent** : Research guests: Alice Smith (alice@email.com), Bob Lee (bob@email.com)\n - **Agent actions**: Call [@tool:Composio DuckDuckGo Search](#mention) for each guest\n - **Agent response**: Alice Smith: [summary], Bob Lee: [summary]", + "model": "google/gemini-2.5-flash", + "locked": false, + "toggleAble": true, + "ragReturnType": "chunks", + "ragK": 3, + "outputVisibility": "internal", + "controlType": "relinquish_to_parent", + "maxCallsPerParentAgent": 3 + }, + { + "name": "Email Agent", + "type": "conversation", + "description": "Sends the research summary to the user's email address.", + "disabled": false, + "instructions": "## 🧑‍💼 Role:\nSend the provided research summary to the user's email address.\n\n---\n## ⚙️ Steps to Follow:\n1. Receive the research summary and recipient email from the parent agent.\n2. Use [@tool:Send Email](#mention) to send the summary.\n3. Confirm delivery to the parent agent.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Sending research summaries via email.\n\n❌ Out of Scope:\n- Fetching event details.\n- Researching guests.\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Ensure the summary is sent to the correct email.\n\n🚫 Don'ts:\n- Do not interact with the user directly.", + "examples": "- **Parent agent** : Send summary to user@email.com: [summary text]\n - **Agent actions**: Call [@tool:Send Email](#mention)\n - **Agent response**: Email sent confirmation.", + "model": "google/gemini-2.5-flash", + "locked": false, + "toggleAble": true, + "ragReturnType": "chunks", + "ragK": 3, + "outputVisibility": "internal", + "controlType": "relinquish_to_parent", + "maxCallsPerParentAgent": 3 + } + ], + "prompts": [], + "tools": [ + { + "name": "Find event", + "description": "Finds events in a specified google calendar using text query, time ranges, and event types.", + "mockTool": false, + "parameters": { + "type": "object", + "properties": { + "calendar_id": { + "default": "primary", + "description": "Identifier of the Google Calendar to query.", + "examples": [ + "primary", + "user@example.com", + "abc...@group.calendar.google.com" + ], + "title": "Calendar Id", + "type": "string" + }, + "event_types": { + "default": [ + "birthday", + "default", + "focusTime", + "outOfOffice", + "workingLocation" + ], + "description": "Event types to include.", + "examples": [ + "default", + "focusTime", + "outOfOffice" + ], + "items": { + "enum": [ + "birthday", + "default", + "focusTime", + "outOfOffice", + "workingLocation" + ], + "type": "string" + }, + "title": "Event Types", + "type": "array" + }, + "max_results": { + "default": 10, + "description": "Maximum number of events per page (1-2500).", + "title": "Max Results", + "type": "integer" + }, + "order_by": { + "default": null, + "description": "Order of events: 'startTime' or 'updated'.", + "examples": [ + "startTime", + "updated" + ], + "nullable": true, + "title": "Order By", + "type": "string" + }, + "page_token": { + "default": null, + "description": "Token for pagination.", + "nullable": true, + "title": "Page Token", + "type": "string" + }, + "query": { + "default": null, + "description": "Free-text search terms to find events.", + "examples": [ + "Project Alpha Review", + "Birthday Party" + ], + "nullable": true, + "title": "Query", + "type": "string" + }, + "show_deleted": { + "default": null, + "description": "Include deleted events.", + "nullable": true, + "title": "Show Deleted", + "type": "boolean" + }, + "single_events": { + "default": true, + "description": "Expand recurring events into individual instances.", + "title": "Single Events", + "type": "boolean" + }, + "timeMax": { + "default": null, + "description": "Upper bound for event's start time.", + "examples": [ + "2024-12-31T23:59:59Z" + ], + "nullable": true, + "title": "Time Max", + "type": "string" + }, + "timeMin": { + "default": null, + "description": "Lower bound for event's end time.", + "examples": [ + "2024-01-01T00:00:00Z" + ], + "nullable": true, + "title": "Time Min", + "type": "string" + }, + "updated_min": { + "default": null, + "description": "Lower bound for event's last modification time.", + "examples": [ + "2024-07-01T00:00:00Z" + ], + "nullable": true, + "title": "Updated Min", + "type": "string" + } + }, + "required": [] + }, + "isComposio": true, + "composioData": { + "slug": "GOOGLECALENDAR_FIND_EVENT", + "noAuth": false, + "toolkitName": "googlecalendar", + "toolkitSlug": "googlecalendar", + "logo": "https://cdn.jsdelivr.net/gh/ComposioHQ/open-logos@master/google-calendar.svg" + } + }, + { + "name": "Composio DuckDuckGo Search", + "description": "Performs web searches using DuckDuckGo to retrieve relevant information.", + "mockTool": false, + "parameters": { + "type": "object", + "properties": { + "query": { + "description": "The search query for DuckDuckGo.", + "examples": [ + "Python programming" + ], + "title": "Query", + "type": "string" + } + }, + "required": [ + "query" + ] + }, + "isComposio": true, + "composioData": { + "slug": "COMPOSIO_SEARCH_DUCK_DUCK_GO_SEARCH", + "noAuth": true, + "toolkitName": "composio_search", + "toolkitSlug": "composio_search", + "logo": "https://cdn.jsdelivr.net/gh/ComposioHQ/open-logos@master//composio-logo.png" + } + }, + { + "name": "Send Email", + "description": "Sends an email via Gmail using the authenticated user's Google profile.", + "mockTool": false, + "parameters": { + "type": "object", + "properties": { + "attachment": { + "additionalProperties": false, + "description": "File to attach; ensure s3key, mimetype, and name are set if provided.", + "file_uploadable": true, + "properties": { + "mimetype": { + "title": "Mimetype", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "s3key": { + "title": "S3Key", + "type": "string" + } + }, + "required": [ + "name", + "mimetype", + "s3key" + ], + "title": "FileUploadable", + "type": "object" + }, + "bcc": { + "default": [], + "description": "BCC recipients' email addresses.", + "items": { + "type": "string" + }, + "title": "Bcc", + "type": "array" + }, + "body": { + "description": "Email content (plain text or HTML).", + "examples": [ + "Hello team, let's discuss the project updates tomorrow." + ], + "title": "Body", + "type": "string" + }, + "cc": { + "default": [], + "description": "CC recipients' email addresses.", + "items": { + "type": "string" + }, + "title": "Cc", + "type": "array" + }, + "extra_recipients": { + "default": [], + "description": "Additional 'To' recipients' email addresses.", + "items": { + "type": "string" + }, + "title": "Extra Recipients", + "type": "array" + }, + "is_html": { + "default": false, + "description": "Set to True if the email body contains HTML tags.", + "title": "Is Html", + "type": "boolean" + }, + "recipient_email": { + "description": "Primary recipient's email address.", + "examples": [ + "john@doe.com" + ], + "title": "Recipient Email", + "type": "string" + }, + "subject": { + "default": null, + "description": "Subject line of the email.", + "examples": [ + "Project Update Meeting" + ], + "nullable": true, + "title": "Subject", + "type": "string" + }, + "user_id": { + "default": "me", + "description": "User's email address; 'me' refers to the authenticated user.", + "examples": [ + "user@example.com", + "me" + ], + "title": "User Id", + "type": "string" + } + }, + "required": [ + "recipient_email", + "body" + ] + }, + "isComposio": true, + "composioData": { + "slug": "GMAIL_SEND_EMAIL", + "noAuth": false, + "toolkitName": "gmail", + "toolkitSlug": "gmail", + "logo": "https://cdn.jsdelivr.net/gh/ComposioHQ/open-logos@master/gmail.svg" + } + } + ], + "startAgent": "Meeting Prep Hub", + "lastUpdatedAt": "2025-09-07T17:06:05.564Z", + "name": "Meeting Prep", + "description": "Research meeting attendees and send summary to Slack" +} \ No newline at end of file diff --git a/apps/rowboat/app/lib/prebuilt-cards/README.md b/apps/rowboat/app/lib/prebuilt-cards/README.md new file mode 100644 index 000000000..f91a709dd --- /dev/null +++ b/apps/rowboat/app/lib/prebuilt-cards/README.md @@ -0,0 +1,43 @@ +# Prebuilt Cards Directory + +This directory contains JSON files that define prebuilt assistant templates. These templates appear as cards in the "Pre-built Assistants" section of the application. + +## How to Add New Prebuilt Cards + +1. Create a new JSON file in this directory (e.g., `my-assistant.json`) +2. The filename (without extension) will be used as the template key +3. The JSON file should follow the WorkflowTemplate schema structure + +## Required Structure + +Each prebuilt card JSON file must have: +- `name`: Display name for the template +- `description`: Brief description of what the template does +- `agents`: Array of agent configurations +- `startAgent`: Name of the starting agent +- `tools`: Array of tool configurations (optional) +- `prompts`: Array of prompt configurations (optional) +- `pipelines`: Array of pipeline configurations (optional) + +## Example Prebuilt Cards + +See the existing files in this directory: +- `github-data-to-spreadsheet.json` - Fetches GitHub stats and logs to Google Sheets +- `meeting-prep.json` - Research meeting attendees and send to Slack +- `interview-scheduler.json` - Automate interview scheduling with Google Sheets/Calendar + +## Template Loading + +Prebuilt cards are automatically loaded when the application starts. Simply drop a new JSON file here and restart the application to see it appear in the prebuilt assistants section. + +## Location + +This directory is located at `app/lib/prebuilt-cards/` to keep the template definitions close to the `project_templates.ts` file that loads them. + +## Validation + +The system validates that each template has: +- A valid `agents` array +- Proper JSON syntax + +Invalid templates will be logged as warnings but won't break the application. diff --git a/apps/rowboat/app/lib/prebuilt-cards/github-data-to-spreadsheet.json b/apps/rowboat/app/lib/prebuilt-cards/github-data-to-spreadsheet.json new file mode 100644 index 000000000..b2be16cb1 --- /dev/null +++ b/apps/rowboat/app/lib/prebuilt-cards/github-data-to-spreadsheet.json @@ -0,0 +1,578 @@ +{ + "agents": [ + { + "name": "GitHub Stats Hub", + "type": "conversation", + "description": "Hub agent that orchestrates fetching GitHub stats for rowboatlabs/rowboat and logging them to a Google Sheet.", + "instructions": "## 🧑‍💼 Role:\nYou are the hub agent responsible for orchestrating the process of fetching GitHub repository stats for 'rowboatlabs/rowboat' and logging them to a Google Sheet.\n\n---\n## ⚙️ Steps to Follow:\n1. Receive a user request to log GitHub stats.\n2. FIRST: Call [@agent:GitHub Stats Agent](#mention) and always provide repository owner: 'rowboatlabs' and repo: 'rowboat' as input (do not prompt the user for these values).\n3. Wait for the stats to be returned.\n4. THEN: Call [@agent:GitHub Stats to Sheet Agent](#mention) to append the stats to the Google Sheet.\n5. Wait for confirmation from the Sheets agent.\n6. Inform the user that the data has been logged, or report any error if one occurred.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Orchestrating the sequential workflow: fetch stats for rowboatlabs/rowboat, then log to sheet, then inform the user.\n\n❌ Out of Scope:\n- Fetching stats or logging to the sheet directly (handled by sub-agents).\n- Handling requests unrelated to GitHub stats logging.\n- Accepting or prompting for other repositories.\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Always use 'rowboatlabs' as owner and 'rowboat' as repo when calling the GitHub Stats Agent.\n- Always follow the sequence: GitHub Stats Agent first, then GitHub Stats to Sheet Agent.\n- Wait for each agent's complete response before proceeding.\n- Only interact with the user for the initial request and final confirmation.\n\n🚫 Don'ts:\n- Do not perform stats fetching or sheet logging yourself.\n- Do not try to call both agents at once.\n- Do not reference internal agent names to the user.\n- Do not prompt the user for a repository or accept any other repository.\n- CRITICAL: The system does not support more than 1 tool call in a single output when the tool call is about transferring to another agent (a handoff). You must only put out 1 transfer related tool call in one output.\n\n# Examples\n- **User** : Fetch and store stats\n - **Agent actions**: Call [@agent:GitHub Stats Agent](#mention) with owner: 'rowboatlabs', repo: 'rowboat'\n\n- **Agent receives stats** :\n - **Agent actions**: Call [@agent:GitHub Stats to Sheet Agent](#mention)\n\n- **Agent receives sheet confirmation** :\n - **Agent response**: GitHub stats have been logged to the sheet successfully.\n\n- **Agent receives error from sheet agent** :\n - **Agent response**: There was an error logging the stats to the sheet: [error details]\n\n- **User** : Add a dummy row\n - **Agent response**: Sorry, I can only log actual GitHub stats. Please use the workflow to log real data.", + "model": "google/gemini-2.5-flash", + "toggleAble": true, + "ragReturnType": "chunks", + "ragK": 3, + "outputVisibility": "user_facing", + "controlType": "retain" + }, + { + "name": "GitHub Stats Agent", + "type": "conversation", + "description": "Fetches GitHub page view and clone statistics for rowboatlabs/rowboat for the previous day.", + "disabled": false, + "instructions": "## 🧑‍💼 Role:\nYou are an internal agent that fetches GitHub page view and clone statistics for the repository for the previous day.\n\n---\n## ⚙️ Steps to Follow:\n1. Always use owner: and repo: (do not expect or prompt for these values from the parent agent).\n2. Use [@tool:Get page views](#mention) with per: 'day' to fetch daily page view stats. You must actually call this tool.\n3. Use [@tool:Get repository clones](#mention) with per: 'day' to fetch daily clone stats. You must actually call this tool.\n4. Filter both results to only include data for the previous day (relative to today, in UTC).\n5. Return both sets of stats (page views and clones for the previous day) to the parent agent.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Fetching and returning GitHub page view and clone stats for 'rowboatlabs/rowboat' for the previous day.\n\n❌ Out of Scope:\n- Answering user questions directly.\n- Modifying repository data.\n- Accepting or prompting for any other repository.\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Return only the stats for the previous day.\n- Return both page views and clone stats in a clear, structured format.\n- **Do not simulate or describe tool calls—always actually call the tools.**\n\n🚫 Don'ts:\n- Do not interact with the user directly.\n- Do not perform any actions other than fetching and returning stats.\n- Do not prompt for or accept any repository input.\n\n# Examples\n- **Parent agent** : Fetch and store stats\n - **Agent actions**: Call [@tool:Get page views](#mention) with owner, repo, per: 'day'. Then call [@tool:Get repository clones](#mention) with owner, repo: 'rowboat', per: 'day'.\n - **Agent response**: [Page views and clone stats for owner/repo for the previous day]\n\n\n\n", + "model": "google/gemini-2.5-flash", + "locked": false, + "toggleAble": true, + "ragReturnType": "chunks", + "ragK": 3, + "outputVisibility": "internal", + "controlType": "relinquish_to_parent", + "maxCallsPerParentAgent": 3 + }, + { + "name": "GitHub Stats to Sheet Agent", + "type": "conversation", + "description": "Appends the latest GitHub clone and view stats as a new row to a specified Google Sheet.", + "disabled": false, + "instructions": "## 🧑‍💼 Role:\nYou are an internal agent that receives GitHub stats (clones and views), extracts the most recent date for each, and appends a row to a Google Sheet with the specified columns.\n\n---\n## ⚙️ Steps to Follow:\n1. Receive GitHub stats data (including arrays of daily clone and view stats, each with date, count, and uniques).\n2. Identify the most recent (latest) date in the clones array and extract its count and uniques.\n3. Identify the most recent (latest) date in the views array and extract its count and uniques.\n4. Use the current UTC date (YYYY-MM-DD) as the run date.\n5. Prepare a row with the following columns (in order):\n - run date (current UTC date, YYYY-MM-DD)\n - latest clones stats date (YYYY-MM-DD)\n - clones (count)\n - unique clones\n - latest view stats date (YYYY-MM-DD)\n - views (count)\n - unique views\n6. Use [@tool:Append Values to Spreadsheet](#mention) to append this row to the end of the sheet (no headers).\n - spreadsheetId: \n - range: (or the correct sheet name if specified)\n - valueInputOption: USER_ENTERED\n - values: [[run date, latest clones stats date, clones, unique clones, latest view stats date, views, unique views]]\n7. Return a confirmation or error message to the parent agent.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Appending a single row of stats to the specified Google Sheet.\n\n❌ Out of Scope:\n- Adding headers or modifying existing data.\n- Interacting with the user directly.\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Only use the most recent date for each stat type.\n- Ensure the row is appended at the end (no headers).\n- Use the correct spreadsheetId and valueInputOption.\n\n🚫 Don'ts:\n- Do not add column headers.\n- Do not overwrite existing data.\n- Do not interact with the user directly.\n\n# Examples\n- **Parent agent** : Insert latest GitHub stats into sheet\n - **Agent actions**: Call [@tool:Append Values to Spreadsheet](#mention) with the latest stats and current date\n - **Agent response**: Row appended confirmation or error message\n\n- **Parent agent** : Insert stats with missing data\n - **Agent actions**: If either clones or views data is missing, append available data and leave missing fields blank\n - **Agent response**: Row appended confirmation or error message\n\n- **Parent agent** : Insert stats for a different repo\n - **Agent actions**: Same as above, using provided stats\n - **Agent response**: Row appended confirmation or error message\n\n- **Parent agent** : Insert stats with only views data\n - **Agent actions**: Append row with views data, leave clone fields blank\n - **Agent response**: Row appended confirmation or error message\n\n- **Parent agent** : Insert stats with only clones data\n - **Agent actions**: Append row with clone data, leave view fields blank\n - **Agent response**: Row appended confirmation or error message\n", + "model": "google/gemini-2.5-flash", + "locked": false, + "toggleAble": true, + "ragReturnType": "chunks", + "ragK": 3, + "outputVisibility": "internal", + "controlType": "relinquish_to_parent", + "maxCallsPerParentAgent": 3 + }, + { + "name": "Pipeline Step 1 - Fetch Views Data", + "type": "pipeline", + "description": "Fetches daily page view stats for rowboatlabs/rowboat using the Get page views tool.", + "disabled": false, + "instructions": "## 🧑‍💼 Role:\nFetch daily page view stats for the repository 'rowboatlabs/rowboat'.\n\n---\n## ⚙️ Steps to Follow:\n1. Use [@tool:Get page views](#mention) with owner: 'rowboatlabs', repo: 'rowboat', per: 'day'.\n2. Return the full result to the next pipeline step.\n\n---\n## 📋 Guidelines:\n- Do not prompt for repository details; always use the specified owner and repo.\n- Do not interact with the user or other agents.", + "model": "google/gemini-2.5-flash", + "locked": false, + "toggleAble": true, + "ragReturnType": "chunks", + "ragK": 3, + "outputVisibility": "internal", + "controlType": "relinquish_to_parent", + "maxCallsPerParentAgent": 3 + }, + { + "name": "Pipeline Step 2 - Fetch Clones Data", + "type": "pipeline", + "description": "Fetches daily clone stats for rowboatlabs/rowboat using the Get repository clones tool.", + "disabled": false, + "instructions": "## 🧑‍💼 Role:\nFetch daily clone stats for the repository 'rowboatlabs/rowboat'.\n\n---\n## ⚙️ Steps to Follow:\n1. Use [@tool:Get repository clones](#mention) with owner: 'rowboatlabs', repo: 'rowboat', per: 'day'.\n2. Return the full result to the next pipeline step, along with the previous step's page views data.\n\n---\n## 📋 Guidelines:\n- Do not prompt for repository details; always use the specified owner and repo.\n- Do not interact with the user or other agents.", + "model": "google/gemini-2.5-flash", + "locked": false, + "toggleAble": true, + "ragReturnType": "chunks", + "ragK": 3, + "outputVisibility": "internal", + "controlType": "relinquish_to_parent", + "maxCallsPerParentAgent": 3 + }, + { + "name": "Pipeline Step 3 - Add Data to Sheet", + "type": "pipeline", + "description": "Appends the latest GitHub clone and view stats as a new row to the specified Google Sheet.", + "disabled": false, + "instructions": "## 🧑‍💼 Role:\nAppend the latest GitHub stats (clones and views) as a new row to the Google Sheet.\n\n---\n## ⚙️ Steps to Follow:\n1. Receive both page views and clone stats (arrays of daily stats, each with date, count, uniques).\n2. Identify the most recent (latest) date in the clones array and extract its count and uniques.\n3. Identify the most recent (latest) date in the views array and extract its count and uniques.\n4. Use the current UTC date (YYYY-MM-DD) as the run date.\n5. Prepare a row with the following columns (in order):\n - run date (current UTC date, YYYY-MM-DD)\n - latest clones stats date (YYYY-MM-DD)\n - clones (count)\n - unique clones\n - latest view stats date (YYYY-MM-DD)\n - views (count)\n - unique views\n6. Use [@tool:Append Values to Spreadsheet](#mention) to append this row to the end of the sheet (no headers).\n - spreadsheetId: \n - range: \n - valueInputOption: USER_ENTERED\n - values: [[run date, latest clones stats date, clones, unique clones, latest view stats date, views, unique views]]\n7. Return the appended row and all relevant stats to the next pipeline step.\n\n---\n## 📋 Guidelines:\n- Only use the most recent date for each stat type.\n- Ensure the row is appended at the end (no headers).\n- Use the correct spreadsheetId and valueInputOption.\n- Do not interact with the user or other agents.\n\n", + "model": "google/gemini-2.5-flash", + "locked": false, + "toggleAble": true, + "ragReturnType": "chunks", + "ragK": 3, + "outputVisibility": "internal", + "controlType": "relinquish_to_parent", + "maxCallsPerParentAgent": 3 + }, + { + "name": "Pipeline Step 4 - Send Slack Summary", + "type": "pipeline", + "description": "Sends a summary message to the #stats Slack channel, including a link to the updated sheet.", + "disabled": false, + "instructions": "## 🧑‍💼 Role:\nSend a summary message to the #stats Slack channel after stats are logged to the sheet.\n\n---\n## ⚙️ Steps to Follow:\n1. Receive the appended row and all relevant stats from the previous step.\n2. Compose a message summarizing the latest GitHub stats update, including:\n - The run date\n - The latest clones and views stats (date, count, uniques)\n - A statement that the data has been updated in the sheet\n - A link to the sheet: \n3. Use [@tool:Send a message to a Slack channel](#mention) to post the message to channel: stats\n4. Return a confirmation or error message.\n\n---\n## 📋 Guidelines:\n- The message should be clear, concise, and include the sheet link.\n- Do not interact with the user or other agents.\n", + "model": "google/gemini-2.5-flash", + "locked": false, + "toggleAble": true, + "ragReturnType": "chunks", + "ragK": 3, + "outputVisibility": "internal", + "controlType": "relinquish_to_parent", + "maxCallsPerParentAgent": 3 + }, + { + "name": "GitHub Stats Logging Pipeline Step 1", + "type": "pipeline", + "description": "", + "disabled": false, + "instructions": "", + "model": "gpt-4o", + "locked": false, + "toggleAble": true, + "ragReturnType": "chunks", + "ragK": 3, + "outputVisibility": "internal", + "controlType": "relinquish_to_parent", + "maxCallsPerParentAgent": 3 + }, + { + "name": "GitHub Stats Pipeline Hub", + "type": "conversation", + "description": "User-facing hub that triggers the GitHub Stats Logging Pipeline and reports when complete.", + "disabled": false, + "instructions": "## 🧑‍💼 Role:\nYou are the hub agent responsible for triggering the GitHub Stats Logging Pipeline.\n\n---\n## ⚙️ Steps to Follow:\n1. When the user requests a stats update, call [@pipeline:GitHub Stats Logging Pipeline](#mention).\n2. Wait for the pipeline to complete.\n3. Inform the user that the stats have been logged, the sheet updated, and the Slack channel notified.\n\n---\n## 📋 Guidelines:\n- Do not perform any stats fetching, sheet logging, or Slack messaging yourself.\n- Do not reference internal agent or pipeline names to the user.\n- Only interact with the user for the initial request and final confirmation.", + "model": "google/gemini-2.5-flash", + "locked": false, + "toggleAble": true, + "ragReturnType": "chunks", + "ragK": 3, + "outputVisibility": "user_facing", + "controlType": "retain", + "maxCallsPerParentAgent": 3 + } + ], + "prompts": [ + { + "name": "spreadsheetId", + "type": "base_prompt", + "prompt": "" + }, + { + "name": "range", + "type": "base_prompt", + "prompt": "" + }, + { + "name": "sheet_link", + "type": "base_prompt", + "prompt": "" + }, + { + "name": "Owner", + "type": "base_prompt", + "prompt": "" + }, + { + "name": "repo", + "type": "base_prompt", + "prompt": "" + } + ], + "tools": [ + { + "name": "Get page views", + "description": "Retrieves page view statistics for a repository over the last 14 days, including total views, unique visitors, and a daily or weekly breakdown.", + "mockTool": false, + "parameters": { + "type": "object", + "properties": { + "owner": { + "description": "The username of the account that owns the repository. This field is case-insensitive.", + "examples": [ + "octocat" + ], + "title": "Owner", + "type": "string" + }, + "per": { + "default": "day", + "description": "The time unit for which to aggregate page views.", + "enum": [ + "day", + "week" + ], + "examples": [ + "day", + "week" + ], + "title": "Per", + "type": "string" + }, + "repo": { + "description": "The name of the repository, without the `.git` extension. This field is case-insensitive.", + "examples": [ + "Spoon-Knife" + ], + "title": "Repo", + "type": "string" + } + }, + "required": [ + "owner", + "repo" + ] + }, + "isComposio": true, + "composioData": { + "slug": "GITHUB_GET_PAGE_VIEWS", + "noAuth": false, + "toolkitName": "GitHub", + "toolkitSlug": "github", + "logo": "https://cdn.jsdelivr.net/gh/ComposioHQ/open-logos@master/github.png" + } + }, + { + "name": "Append Values to Spreadsheet", + "description": "Tool to append values to a spreadsheet. use when you need to add new data to the end of an existing table in a google sheet.", + "mockTool": false, + "parameters": { + "type": "object", + "properties": { + "includeValuesInResponse": { + "default": null, + "description": "Determines if the update response should include the values of the cells that were appended. By default, responses do not include the updated values.", + "examples": [ + true + ], + "nullable": true, + "title": "Include Values In Response", + "type": "boolean" + }, + "insertDataOption": { + "default": null, + "description": "How the input data should be inserted.", + "enum": [ + "OVERWRITE", + "INSERT_ROWS" + ], + "examples": [ + "INSERT_ROWS" + ], + "nullable": true, + "title": "Insert Data Option", + "type": "string" + }, + "majorDimension": { + "default": null, + "description": "The major dimension of the values. For output, if the spreadsheet data is: A1=1,B1=2,A2=3,B2=4, then requesting range=A1:B2,majorDimension=ROWS will return [[1,2],[3,4]], whereas requesting range=A1:B2,majorDimension=COLUMNS will return [[1,3],[2,4]].", + "enum": [ + "ROWS", + "COLUMNS" + ], + "examples": [ + "ROWS" + ], + "nullable": true, + "title": "Major Dimension", + "type": "string" + }, + "range": { + "description": "The A1 notation of a range to search for a logical table of data. Values are appended after the last row of the table.", + "examples": [ + "Sheet1!A1:B2" + ], + "title": "Range", + "type": "string" + }, + "responseDateTimeRenderOption": { + "default": null, + "description": "Determines how dates, times, and durations in the response should be rendered. This is ignored if responseValueRenderOption is FORMATTED_VALUE. The default dateTime render option is SERIAL_NUMBER.", + "enum": [ + "SERIAL_NUMBER", + "FORMATTED_STRING" + ], + "examples": [ + "SERIAL_NUMBER" + ], + "nullable": true, + "title": "Response Date Time Render Option", + "type": "string" + }, + "responseValueRenderOption": { + "default": null, + "description": "Determines how values in the response should be rendered. The default render option is FORMATTED_VALUE.", + "enum": [ + "FORMATTED_VALUE", + "UNFORMATTED_VALUE", + "FORMULA" + ], + "examples": [ + "FORMATTED_VALUE" + ], + "nullable": true, + "title": "Response Value Render Option", + "type": "string" + }, + "spreadsheetId": { + "description": "The ID of the spreadsheet to update.", + "examples": [ + "1q0gLhLdGXYZblahblahblah" + ], + "title": "Spreadsheet Id", + "type": "string" + }, + "valueInputOption": { + "description": "How the input data should be interpreted.", + "enum": [ + "RAW", + "USER_ENTERED" + ], + "examples": [ + "USER_ENTERED" + ], + "title": "Value Input Option", + "type": "string" + }, + "values": { + "description": "The data to be written. This is an array of arrays, the outer array representing all the data and each inner array representing a major dimension. Each item in the inner array corresponds with one cell.", + "examples": [ + [ + [ + "A1_val1", + "A1_val2" + ], + [ + "A2_val1", + "A2_val2" + ] + ] + ], + "items": { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "number" + }, + { + "type": "boolean" + } + ] + }, + "type": "array" + }, + "title": "Values", + "type": "array" + } + }, + "required": [ + "spreadsheetId", + "range", + "valueInputOption", + "values" + ] + }, + "isComposio": true, + "composioData": { + "slug": "GOOGLESHEETS_SPREADSHEETS_VALUES_APPEND", + "noAuth": false, + "toolkitName": "Googlesheets", + "toolkitSlug": "googlesheets", + "logo": "https://cdn.jsdelivr.net/gh/ComposioHQ/open-logos@master/google-sheets.svg" + } + }, + { + "name": "Get repository clones", + "description": "Retrieves the total number of clones and a breakdown of clone activity (daily or weekly) for a specified repository over the preceding 14 days.", + "mockTool": false, + "parameters": { + "type": "object", + "properties": { + "owner": { + "description": "The username of the account that owns the repository. This field is not case-sensitive.", + "examples": [ + "octocat", + "github" + ], + "title": "Owner", + "type": "string" + }, + "per": { + "default": "day", + "description": "Specifies the time frame for aggregating clone data: `day` for daily clone counts, or `week` for weekly clone counts (a week starts on Monday).", + "enum": [ + "day", + "week" + ], + "examples": [ + "day", + "week" + ], + "title": "Per", + "type": "string" + }, + "repo": { + "description": "The name of the repository, without the '.git' extension. This field is not case-sensitive.", + "examples": [ + "Hello-World", + "mercury" + ], + "title": "Repo", + "type": "string" + } + }, + "required": [ + "owner", + "repo" + ] + }, + "isComposio": true, + "composioData": { + "slug": "GITHUB_GET_REPOSITORY_CLONES", + "noAuth": false, + "toolkitName": "GitHub", + "toolkitSlug": "github", + "logo": "https://cdn.jsdelivr.net/gh/ComposioHQ/open-logos@master/github.png" + } + }, + { + "name": "Send a message to a Slack channel", + "description": "Deprecated: posts a message to a slack channel, direct message, or private group. use `send message` instead.", + "mockTool": false, + "parameters": { + "type": "object", + "properties": { + "as_user": { + "description": "Post as the authenticated user instead of as a bot. Defaults to `false`. If `true`, `username`, `icon_emoji`, and `icon_url` are ignored. If `false`, the message is posted as a bot, allowing appearance customization.", + "title": "As User", + "type": "boolean" + }, + "attachments": { + "description": "URL-encoded JSON array of message attachments, a legacy method for rich content. See Slack API documentation for structure.", + "examples": [ + "%5B%7B%22fallback%22%3A%20%22Required%20plain-text%20summary%20of%20the%20attachment.%22%2C%20%22color%22%3A%20%22%2336a64f%22%2C%20%22pretext%22%3A%20%22Optional%20text%20that%20appears%20above%20the%20attachment%20block%22%2C%20%22author_name%22%3A%20%22Bobby%20Tables%22%2C%20%22title%22%3A%20%22Slack%20API%20Documentation%22%2C%20%22title_link%22%3A%20%22https%3A%2F%2Fapi.slack.com%2F%22%2C%20%22text%22%3A%20%22Optional%20text%20that%20appears%20within%20the%20attachment%22%7D%5D" + ], + "title": "Attachments", + "type": "string" + }, + "blocks": { + "description": "DEPRECATED: Use `markdown_text` field instead. URL-encoded JSON array of layout blocks for rich/interactive messages. See Slack API Block Kit docs for structure.", + "examples": [ + "%5B%7B%22type%22%3A%20%22section%22%2C%20%22text%22%3A%20%7B%22type%22%3A%20%22mrkdwn%22%2C%20%22text%22%3A%20%22Hello%2C%20world%21%22%7D%7D%5D" + ], + "title": "Blocks", + "type": "string" + }, + "channel": { + "description": "ID or name of the channel, private group, or IM channel to send the message to.", + "examples": [ + "C1234567890", + "general" + ], + "title": "Channel", + "type": "string" + }, + "icon_emoji": { + "description": "Emoji for bot's icon (e.g., ':robot_face:'). Overrides `icon_url`. Applies if `as_user` is `false`.", + "examples": [ + ":tada:", + ":slack:" + ], + "title": "Icon Emoji", + "type": "string" + }, + "icon_url": { + "description": "Image URL for bot's icon (must be HTTPS). Applies if `as_user` is `false`.", + "examples": [ + "https://slack.com/img/icons/appDir_2019_01/Tonito64.png" + ], + "title": "Icon Url", + "type": "string" + }, + "link_names": { + "description": "Automatically hyperlink channel names (e.g., #channel) and usernames (e.g., @user) in message text. Defaults to `false` for bot messages.", + "title": "Link Names", + "type": "boolean" + }, + "markdown_text": { + "description": "PREFERRED: Write your message in markdown for nicely formatted display. Supports: headers (# ## ###), bold (**text** or __text__), italic (*text* or _text_), strikethrough (~~text~~), inline code (`code`), code blocks (```), links ([text](url)), block quotes (>), lists (- item, 1. item), dividers (--- or ***), context blocks (:::context with images), and section buttons (:::section-button). IMPORTANT: Use \\n for line breaks (e.g., 'Line 1\\nLine 2'), not actual newlines. USER MENTIONS: To tag users, use their user ID with <@USER_ID> format (e.g., <@U1234567890>), not username. ", + "examples": [ + "# Status Update\n\nSystem is **running smoothly** with *excellent* performance.\n\n```bash\nkubectl get pods\n```\n\n> All services operational ✅", + "## Daily Report\n\n- **Deployments**: 5 successful\n- *Issues*: 0 critical\n- ~~Maintenance~~: **Completed**\n\n---\n\n**Next**: Monitor for 24h" + ], + "title": "Markdown Text", + "type": "string" + }, + "mrkdwn": { + "description": "Disable Slack's markdown for `text` field if `false`. Default `true` (allows *bold*, _italic_, etc.).", + "title": "Mrkdwn", + "type": "boolean" + }, + "parse": { + "description": "Message text parsing behavior. Default `none` (no special parsing). `full` parses as user-typed (links @mentions, #channels). See Slack API docs for details.", + "examples": [ + "none", + "full" + ], + "title": "Parse", + "type": "string" + }, + "reply_broadcast": { + "description": "If `true` for a threaded reply, also posts to main channel. Defaults to `false`.", + "title": "Reply Broadcast", + "type": "boolean" + }, + "text": { + "description": "DEPRECATED: This sends raw text only, use markdown_text field. Primary textual content. Recommended fallback if using `blocks` or `attachments`. Supports mrkdwn unless `mrkdwn` is `false`.", + "examples": [ + "Hello from your friendly bot!", + "Reminder: Team meeting at 3 PM today." + ], + "title": "Text", + "type": "string" + }, + "thread_ts": { + "description": "Timestamp (`ts`) of an existing message to make this a threaded reply. Use `ts` of the parent message, not another reply. Example: '1476746824.000004'.", + "examples": [ + "1618033790.001500" + ], + "title": "Thread Ts", + "type": "string" + }, + "unfurl_links": { + "description": "Enable unfurling of text-based URLs. Defaults `false` for bots, `true` if `as_user` is `true`.", + "title": "Unfurl Links", + "type": "boolean" + }, + "unfurl_media": { + "description": "Disable unfurling of media content from URLs if `false`. Defaults to `true`.", + "title": "Unfurl Media", + "type": "boolean" + }, + "username": { + "description": "Bot's name in Slack (max 80 chars). Applies if `as_user` is `false`.", + "examples": [ + "MyBot", + "AlertBot" + ], + "title": "Username", + "type": "string" + } + }, + "required": [ + "channel" + ] + }, + "isComposio": true, + "composioData": { + "slug": "SLACK_SENDS_A_MESSAGE_TO_A_SLACK_CHANNEL", + "noAuth": false, + "toolkitName": "Slack", + "toolkitSlug": "slack", + "logo": "https://cdn.jsdelivr.net/gh/ComposioHQ/open-logos@master/slack.svg" + } + } + ], + "pipelines": [ + { + "name": "GitHub Stats Logging Pipeline", + "description": "Sequential pipeline to fetch GitHub stats, log them to a Google Sheet, and notify #stats Slack channel.", + "agents": [ + "Pipeline Step 1 - Fetch Views Data", + "Pipeline Step 2 - Fetch Clones Data", + "Pipeline Step 3 - Add Data to Sheet", + "Pipeline Step 4 - Send Slack Summary" + ] + } + ], + "startAgent": "GitHub Stats Pipeline Hub", + "name": "GitHub Data to Spreadsheet", + "description": "Fetches GitHub repository stats and logs them to a Google Sheet with Slack notifications" +} diff --git a/apps/rowboat/app/lib/prebuilt-cards/interview-scheduler.json b/apps/rowboat/app/lib/prebuilt-cards/interview-scheduler.json new file mode 100644 index 000000000..971a04ffa --- /dev/null +++ b/apps/rowboat/app/lib/prebuilt-cards/interview-scheduler.json @@ -0,0 +1,468 @@ +{ + "agents": [ + { + "name": "Recruitment HR Bot", + "type": "conversation", + "description": "Hub agent to orchestrate interview scheduling with candidates from a Google Sheet.", + "instructions": "## 🧑‍💼 Role:\nYou are the Recruitment HR Bot, responsible for orchestrating the process of scheduling interviews with candidates from a Google Sheet and updating their status, or handling calendar event RSVPs.\n\n---\n## ⚙️ Steps to Follow:\n1. Greet the user.\n2. **IF** the input is a calendar event RSVP (e.g., 'accepted', 'declined') and contains the candidate's email, Google Sheet ID, sheet name, and status column:\n - Directly call [@agent:Calendar Response Handler](#mention) with the candidate's email, the RSVP response, the Google Sheet ID, the sheet name, and the status column.\n - Inform the user that the calendar response has been processed.\n3. **ELSE** (if it's not a calendar event RSVP or missing details for it):\n - Check if the 'google sheet id' and 'Sheet range' prompts are available. If so, use their values. Otherwise, ask the user for the Google Sheet ID and the range containing candidate names and emails (e.g., 'Sheet1!A2:B').\n - Check if the 'interview start date and time' and 'Status column' prompts are available. If so, use their values. Otherwise, ask for the desired start date and time for interviews (e.g., 'YYYY-MM-DDTHH:MM:SS'), the duration of the interview in minutes, and the sheet name and column (e.g., 'Sheet1!C') where the interview status should be updated.\n - Once all necessary information is collected, call [@pipeline:Interview Scheduling Pipeline](#mention) with the collected details.\n - Inform the user when the interview scheduling process is complete.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Orchestrating the workflow for fetching candidates, scheduling interviews, and updating sheet status.\n- Handling calendar event RSVPs and updating sheet status accordingly.\n\n❌ Out of Scope:\n- Directly fetching candidate data, scheduling interviews, or updating sheet status (handled by pipeline agents).\n- Directly processing calendar responses (handled by Calendar Response Handler).\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Prioritize handling calendar event RSVPs if the necessary information is present.\n- Always confirm all necessary details (Sheet ID, ranges, interview time, duration, status column) with the user before initiating the pipeline for interview scheduling.\n- Ensure all steps are completed in sequence.\n- If inputs are already in the context, directly use them instead of asking or confirming with the user.\n\n🚫 Don'ts:\n- Do not perform data fetching, scheduling, or status updates directly.\n- Do not skip any step in the workflow.\n- Do not mention internal agent names to the user.\n- Do not say 'connecting you to another agent'.\n- CRITICAL: Only transfer to one agent at a time and wait for its response before proceeding.\n\n---\n## 📥 Inputs:\n- **Google Sheet ID**: The unique identifier of the Google Spreadsheet containing candidate data. (e.g., '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms')\n- **Sheet Range**: The range in A1 notation (e.g., 'Sheet1!A2:B') containing candidate names and emails.\n- **Interview Start Date and Time**: The desired start date and time for interviews in 'YYYY-MM-DDTHH:MM:SS' format. Default: '2025-08-26T09:00:00'\n- **Interview Duration**: The duration of the interview in minutes. Default: 30\n- **Status Column**: The sheet name and column (e.g., 'Sheet1!C') where the interview status should be updated.", + "examples": "- **User** : I want to schedule interviews for candidates from a Google Sheet.\n - **Agent response**: Sure! Please provide the desired start date and time for interviews (e.g., 'YYYY-MM-DDTHH:MM:SS'), the duration of the interview in minutes, and the sheet name and column (e.g., 'Sheet1!C') where the interview status should be updated.\n\n- **User** : Start Time: 2024-09-01T10:00:00, Duration: 30, Status Column: Sheet1!C\n - **Agent actions**: Call [@pipeline:Interview Scheduling Pipeline](#mention)\n\n- **Agent receives pipeline completion** :\n - **Agent response**: The interview scheduling process is complete.\n\n- **User** : Candidate [candidate_email] has accepted the interview. Sheet ID: [sheet_id], Sheet Name: [sheet_name], Status Column: [status_column]\n - **Agent actions**: Call [@agent:Calendar Response Handler](#mention)\n\n- **Agent receives Calendar Response Handler completion** :\n - **Agent response**: The calendar response has been processed and the sheet updated.", + "model": "google/gemini-2.5-flash", + "toggleAble": true, + "ragReturnType": "chunks", + "ragK": 3, + "outputVisibility": "user_facing", + "controlType": "retain" + }, + { + "name": "Pipeline Step 1 - Fetch Candidates", + "type": "pipeline", + "description": "Reads candidate names and emails from a specified Google Sheet range.", + "disabled": false, + "instructions": "## 🧑‍💼 Role:\nFetch candidate names and emails from the provided Google Sheet and ranges.\n\n---\n## ⚙️ Steps to Follow:\n1. Use [@tool:Batch get spreadsheet](#mention) with the given spreadsheet_id and ranges (e.g., 'Sheet1!A2:B').\n2. Return a normalized array of { name, email } objects.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Fetching rows from Google Sheets and returning structured data.\n\n❌ Out of Scope:\n- Scheduling interviews or updating sheet status.\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Validate rows and skip empties.\n🚫 Don'ts:\n- Do not schedule interviews or update sheet status.", + "model": "google/gemini-2.5-flash", + "locked": false, + "toggleAble": true, + "ragReturnType": "chunks", + "ragK": 3, + "outputVisibility": "internal", + "controlType": "relinquish_to_parent", + "maxCallsPerParentAgent": 3 + }, + { + "name": "Pipeline Step 2 - Schedule Interview", + "type": "pipeline", + "description": "Schedules an interview for each candidate using Google Calendar.", + "disabled": false, + "instructions": "## 🧑‍💼 Role:\nSchedule an interview for each candidate.\n\n---\n## ⚙️ Steps to Follow:\n1. Receive a list of { name, email } objects from the previous step.\n2. For each candidate, use [@tool:Create Event](#mention) to schedule an interview. The event summary should be 'Interview with [Candidate Name]', and the attendee should be the candidate's email. You will need to ask the user for the start_datetime and duration of the interview.\n3. Return a list of { candidate_email, status: 'scheduled' } for each successfully scheduled interview.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Scheduling interviews on Google Calendar.\n\n❌ Out of Scope:\n- Fetching candidate data or updating sheet status.\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Ensure all required fields for event creation are provided.\n🚫 Don'ts:\n- Do not fetch candidate data or update sheet status.", + "model": "google/gemini-2.5-flash", + "locked": false, + "toggleAble": true, + "ragReturnType": "chunks", + "ragK": 3, + "outputVisibility": "internal", + "controlType": "relinquish_to_parent", + "maxCallsPerParentAgent": 3 + }, + { + "name": "Pipeline Step 3 - Update Sheet Status", + "type": "pipeline", + "description": "Updates the status column in the Google Sheet to 'interview scheduled' for each candidate.", + "disabled": false, + "instructions": "## 🧑‍💼 Role:\nUpdate the status column in the Google Sheet for scheduled interviews.\n\n---\n## ⚙️ Steps to Follow:\n1. Receive a list of { candidate_email, status: 'scheduled' } objects from the previous step.\n2. For each candidate, use [@tool:Batch update spreadsheet](#mention) to update the corresponding row in the Google Sheet. You will need to ask the user for the spreadsheet_id, sheet_name, and the column where the status needs to be updated.\n3. The value to be updated should be 'invite sent'.\n4. Return a confirmation of the updates.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Updating the status column in the Google Sheet.\n\n❌ Out of Scope:\n- Fetching candidate data or scheduling interviews.\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Ensure the correct row and column are updated.\n🚫 Don'ts:\n- Do not fetch candidate data or schedule interviews.", + "model": "google/gemini-2.5-flash", + "locked": false, + "toggleAble": true, + "ragReturnType": "chunks", + "ragK": 3, + "outputVisibility": "internal", + "controlType": "relinquish_to_parent", + "maxCallsPerParentAgent": 3 + }, + { + "name": "Interview Scheduling Pipeline Step 1", + "type": "pipeline", + "description": "", + "disabled": false, + "instructions": "", + "model": "gpt-4o", + "locked": false, + "toggleAble": true, + "ragReturnType": "chunks", + "ragK": 3, + "outputVisibility": "internal", + "controlType": "relinquish_to_parent", + "maxCallsPerParentAgent": 3 + }, + { + "name": "Calendar Response Handler", + "type": "conversation", + "description": "Handles calendar accept/reject responses and updates the Google Sheet status accordingly.", + "disabled": false, + "instructions": "## 🧑‍💼 Role:\nProcess calendar responses (accept/reject) and update the Google Sheet with the appropriate interview status.\n\n---\n## ⚙️ Steps to Follow:\n1. Receive the candidate's email, the calendar response (e.g., 'accepted', 'declined'), the Google Sheet ID, the sheet name, and the column where the status needs to be updated.\n2. If the response is 'accepted', set the status to 'interview scheduled'.\n3. If the response is 'declined', set the status to 'declined'.\n4. Use [@tool:Batch update spreadsheet](#mention) to update the corresponding row in the Google Sheet with the determined status.\n5. Return a confirmation of the update.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Interpreting calendar responses and updating the Google Sheet status.\n\n❌ Out of Scope:\n- Scheduling interviews or fetching candidate data.\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Accurately map calendar responses to interview statuses.\n- Ensure the correct row and column are updated in the Google Sheet.\n🚫 Don'ts:\n- Do not interact with the user directly.\n- Do not schedule interviews.", + "model": "google/gemini-2.5-flash", + "locked": false, + "toggleAble": true, + "ragReturnType": "chunks", + "ragK": 3, + "outputVisibility": "internal", + "controlType": "relinquish_to_parent", + "maxCallsPerParentAgent": 3 + } + ], + "prompts": [ + { + "name": "google sheet id", + "type": "base_prompt", + "prompt": "" + }, + { + "name": "Sheet range", + "type": "base_prompt", + "prompt": "" + }, + { + "name": "interview start date and time", + "type": "base_prompt", + "prompt": "" + }, + { + "name": "Status column", + "type": "base_prompt", + "prompt": "" + } + ], + "tools": [ + { + "name": "Batch get spreadsheet", + "description": "Retrieves data from specified cell ranges in a google spreadsheet; ensure the spreadsheet has at least one worksheet and any explicitly referenced sheet names in ranges exist.", + "mockTool": false, + "parameters": { + "type": "object", + "properties": { + "ranges": { + "description": "A list of cell ranges in A1 notation (e.g., 'Sheet1!A1:B2', 'A1:C5') from which to retrieve data. If this list is omitted or empty, all data from the first sheet of the spreadsheet will be fetched. A range can specify a sheet name (e.g., 'Sheet2!A:A'); if no sheet name is provided in a range string (e.g., 'A1:B2'), it defaults to the first sheet.", + "examples": [ + "Sheet1!A1:B2", + "Sheet1!A:A", + "Sheet1!1:2", + "Sheet1!A5:A", + "A1:B2" + ], + "items": { + "type": "string" + }, + "title": "Ranges", + "type": "array" + }, + "spreadsheet_id": { + "description": "The unique identifier of the Google Spreadsheet from which data will be retrieved.", + "title": "Spreadsheet Id", + "type": "string" + } + }, + "required": [ + "spreadsheet_id" + ] + }, + "isComposio": true, + "composioData": { + "slug": "GOOGLESHEETS_BATCH_GET", + "noAuth": false, + "toolkitName": "googlesheets", + "toolkitSlug": "googlesheets", + "logo": "https://cdn.jsdelivr.net/gh/ComposioHQ/open-logos@master/google-sheets.svg" + } + }, + { + "name": "Create Event", + "description": "Creates an event on a google calendar, needing rfc3339 utc start/end times (end after start) and write access to the calendar. by default, adds the organizer as an attendee unless exclude organizer is set to true.", + "mockTool": false, + "parameters": { + "type": "object", + "properties": { + "attendees": { + "default": null, + "description": "List of attendee emails (strings).", + "items": { + "type": "string" + }, + "nullable": true, + "title": "Attendees", + "type": "array" + }, + "calendar_id": { + "default": "primary", + "description": "Target calendar: 'primary' for the user's main calendar, or the calendar's email address.", + "examples": [ + "primary", + "user@example.com", + "abcdefghijklmnopqrstuvwxyz@group.calendar.google.com" + ], + "title": "Calendar Id", + "type": "string" + }, + "create_meeting_room": { + "default": null, + "description": "If true, a Google Meet link is created and added to the event. CRITICAL: As of 2024, this REQUIRES a paid Google Workspace account ($13+/month). Personal Gmail accounts will fail with 'Invalid conference type value' error. Solutions: 1) Upgrade to Workspace, 2) Use domain-wide delegation with Workspace user, 3) Use the new Google Meet REST API, or 4) Create events without conferences. See https://github.com/googleapis/google-api-nodejs-client/issues/3234", + "nullable": true, + "title": "Create Meeting Room", + "type": "boolean" + }, + "description": { + "default": null, + "description": "Description of the event. Can contain HTML. Optional.", + "nullable": true, + "title": "Description", + "type": "string" + }, + "eventType": { + "default": "default", + "description": "Type of the event, immutable post-creation. Currently, only 'default' and 'workingLocation' can be created.", + "enum": [ + "default", + "outOfOffice", + "focusTime", + "workingLocation" + ], + "title": "Event Type", + "type": "string" + }, + "event_duration_hour": { + "default": 0, + "description": "Number of hours (0-24). Increase by 1 here rather than passing 60 in `event_duration_minutes`", + "maximum": 24, + "minimum": 0, + "title": "Event Duration Hour", + "type": "integer" + }, + "event_duration_minutes": { + "default": 30, + "description": "Duration in minutes (0-59 ONLY). NEVER use 60+ minutes - use event_duration_hour=1 instead. Maximum value is 59.", + "maximum": 59, + "minimum": 0, + "title": "Event Duration Minutes", + "type": "integer" + }, + "exclude_organizer": { + "default": false, + "description": "If True, the organizer will NOT be added as an attendee. Default is False (organizer is included).", + "title": "Exclude Organizer", + "type": "boolean" + }, + "guestsCanInviteOthers": { + "default": null, + "description": "Whether attendees other than the organizer can invite others to the event.", + "nullable": true, + "title": "Guests Can Invite Others", + "type": "boolean" + }, + "guestsCanSeeOtherGuests": { + "default": null, + "description": "Whether attendees other than the organizer can see who the event's attendees are.", + "nullable": true, + "title": "Guests Can See Other Guests", + "type": "boolean" + }, + "guests_can_modify": { + "default": false, + "description": "If True, guests can modify the event.", + "title": "Guests Can Modify", + "type": "boolean" + }, + "location": { + "default": null, + "description": "Geographic location of the event as free-form text.", + "nullable": true, + "title": "Location", + "type": "string" + }, + "recurrence": { + "default": null, + "description": "List of RRULE, EXRULE, RDATE, EXDATE lines for recurring events. Supported frequencies are DAILY, WEEKLY, MONTHLY, YEARLY.", + "items": { + "type": "string" + }, + "nullable": true, + "title": "Recurrence", + "type": "array" + }, + "send_updates": { + "default": null, + "description": "Defaults to True. Whether to send updates to the attendees.", + "nullable": true, + "title": "Send Updates", + "type": "boolean" + }, + "start_datetime": { + "description": "Naive date/time (YYYY-MM-DDTHH:MM:SS) with NO offsets or Z. e.g. '2025-01-16T13:00:00'", + "title": "Start Datetime", + "type": "string" + }, + "summary": { + "default": null, + "description": "Summary (title) of the event.", + "nullable": true, + "title": "Summary", + "type": "string" + }, + "timezone": { + "default": null, + "description": "IANA timezone name (e.g., 'America/New_York'). Required if datetime is naive. If datetime includes timezone info (Z or offset), this field is optional and defaults to UTC.", + "nullable": true, + "title": "Timezone", + "type": "string" + }, + "transparency": { + "default": "opaque", + "description": "'opaque' (busy) or 'transparent' (available).", + "enum": [ + "opaque", + "transparent" + ], + "title": "Transparency", + "type": "string" + }, + "visibility": { + "default": "default", + "description": "Event visibility: 'default', 'public', 'private', or 'confidential'.", + "enum": [ + "default", + "public", + "private", + "confidential" + ], + "title": "Visibility", + "type": "string" + } + }, + "required": [ + "start_datetime" + ] + }, + "isComposio": true, + "composioData": { + "slug": "GOOGLECALENDAR_CREATE_EVENT", + "noAuth": false, + "toolkitName": "googlecalendar", + "toolkitSlug": "googlecalendar", + "logo": "https://cdn.jsdelivr.net/gh/ComposioHQ/open-logos@master/google-calendar.svg" + } + }, + { + "name": "Batch update spreadsheet", + "description": "Updates a specified range in a google sheet with given values, or appends them as new rows if `first cell location` is omitted; ensure the target sheet exists and the spreadsheet contains at least one worksheet.", + "mockTool": false, + "parameters": { + "type": "object", + "properties": { + "first_cell_location": { + "description": "The starting cell for the update range, specified in A1 notation (e.g., 'A1', 'B2'). The update will extend from this cell to the right and down, based on the provided values. If omitted, values are appended to the sheet.", + "examples": [ + "A1", + "D3" + ], + "title": "First Cell Location", + "type": "string" + }, + "includeValuesInResponse": { + "default": false, + "description": "If set to True, the response will include the updated values from the spreadsheet.", + "examples": [ + true, + false + ], + "title": "Include Values In Response", + "type": "boolean" + }, + "sheet_name": { + "description": "The name of the specific sheet within the spreadsheet to update.", + "examples": [ + "Sheet1" + ], + "title": "Sheet Name", + "type": "string" + }, + "spreadsheet_id": { + "description": "The unique identifier of the Google Sheets spreadsheet to be updated.", + "examples": [ + "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms" + ], + "title": "Spreadsheet Id", + "type": "string" + }, + "valueInputOption": { + "default": "USER_ENTERED", + "description": "How input data is interpreted. 'USER_ENTERED': Values parsed as if typed by a user (e.g., strings may become numbers/dates, formulas are calculated); recommended for formulas. 'RAW': Values stored as-is without parsing (e.g., '123' stays string, '=SUM(A1:B1)' stays string).", + "enum": [ + "RAW", + "USER_ENTERED" + ], + "examples": [ + "USER_ENTERED", + "RAW" + ], + "title": "Value Input Option", + "type": "string" + }, + "values": { + "description": "A 2D list of cell values. Each inner list represents a row. Values can be strings, numbers, or booleans. Ensure columns are properly aligned across rows.", + "examples": [ + [ + "Item", + "Cost", + "Stocked", + "Ship Date" + ], + [ + "Wheel", + 20.5, + true, + "2020-06-01" + ], + [ + "Screw", + 0.5, + true, + "2020-06-03" + ], + [ + "Nut", + 0.25, + false, + "2020-06-02" + ] + ], + "items": { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "number" + }, + { + "type": "boolean" + } + ] + }, + "type": "array" + }, + "title": "Values", + "type": "array" + } + }, + "required": [ + "spreadsheet_id", + "sheet_name", + "values" + ] + }, + "isComposio": true, + "composioData": { + "slug": "GOOGLESHEETS_BATCH_UPDATE", + "noAuth": false, + "toolkitName": "googlesheets", + "toolkitSlug": "googlesheets", + "logo": "https://cdn.jsdelivr.net/gh/ComposioHQ/open-logos@master/google-sheets.svg" + } + } + ], + "pipelines": [ + { + "name": "Interview Scheduling Pipeline", + "description": "Automates interview scheduling: fetches candidates from Google Sheet, schedules interviews, and updates sheet status.", + "agents": [ + "Pipeline Step 1 - Fetch Candidates", + "Pipeline Step 2 - Schedule Interview", + "Pipeline Step 3 - Update Sheet Status" + ] + } + ], + "startAgent": "Recruitment HR Bot", + "name": "Interview Scheduler", + "description": "Automate interview scheduling with candidates from Google Sheets" +} diff --git a/apps/rowboat/app/lib/prebuilt-cards/meeting-prep.json b/apps/rowboat/app/lib/prebuilt-cards/meeting-prep.json new file mode 100644 index 000000000..860a24f12 --- /dev/null +++ b/apps/rowboat/app/lib/prebuilt-cards/meeting-prep.json @@ -0,0 +1,237 @@ +{ + "agents": [ + { + "name": "Research Agent", + "type": "pipeline", + "description": "Internal agent that researches meeting attendees and returns a compiled summary.", + "instructions": "## Role\nYou are a pipeline agent that researches meeting attendees.\n\n---\n## Task\n1. You will receive attendee details from a previous step.\n2. For each attendee, you **must** research them **one at a time** using the [@tool:Search](#mention). Do NOT research the user `{{Exclude user}}`!\n3. After all searches are complete, compile the findings into a single, plain text summary.\n4. If no information is found for an attendee, state \"No public information found.\" for that person.\n5. Return **only** the final compiled summary.\n\n---\n## Constraint\nDo **NOT** interact with users or send messages. Your only output is the final summary text.", + "model": "google/gemini-2.5-flash", + "toggleAble": true, + "ragReturnType": "chunks", + "ragK": 3, + "controlType": "relinquish_to_parent", + "outputVisibility": "internal", + "maxCallsPerParentAgent": 3 + }, + { + "name": "Slack Send Agent", + "type": "pipeline", + "description": "Internal agent that sends the compiled research summary to a channel via Slack direct message and returns confirmation.", + "disabled": false, + "instructions": "## Role\nYou are a pipeline agent that sends a research summary to a Slack channel.\n\n---\n## Task\n1. You will receive a compiled text summary from the previous step.\n2. Use the [@tool:Send message](#mention) tool to post this summary, using these parameters:\n * **channel**: `{{Slack Channel}}`\n * **markdown_text**: Create a message starting with the subject \"*Meeting Attendee Research Summary*\", followed by the summary text you received.\n3. Your job is complete after sending the message.\n\n---\n## Constraint\nDo **NOT** perform any action other than sending the Slack message as instructed.", + "model": "google/gemini-2.5-flash", + "locked": false, + "toggleAble": true, + "ragReturnType": "chunks", + "ragK": 3, + "controlType": "relinquish_to_parent", + "outputVisibility": "internal", + "maxCallsPerParentAgent": 3, + "examples": "- **Parent agent** : \n - **Agent actions**: Call [@tool:Send message](#mention)\n - **Agent response**: Message sent to Slack channel.\n" + }, + { + "name": "Attendee Research & Slack Pipeline Step 1", + "type": "pipeline", + "description": "", + "disabled": false, + "instructions": "", + "model": "gpt-4o", + "locked": false, + "toggleAble": true, + "ragReturnType": "chunks", + "ragK": 3, + "controlType": "relinquish_to_parent", + "outputVisibility": "internal", + "maxCallsPerParentAgent": 3 + } + ], + "prompts": [ + { + "name": "Slack Channel", + "type": "base_prompt", + "prompt": "" + }, + { + "name": "Exclude user", + "type": "base_prompt", + "prompt": "" + } + ], + "tools": [ + { + "name": "Search", + "description": "Performs a web search and scrapes content from the top results.", + "parameters": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "The search query." + } + }, + "required": [ + "query" + ] + }, + "mockTool": true, + "isComposio": true, + "composioData": { + "slug": "FIRECRWAL_SEARCH", + "noAuth": false, + "toolkitName": "firecrawl", + "toolkitSlug": "firecrawl", + "logo": "https://cdn.jsdelivr.net/gh/ComposioHQ/open-logos@master/firecrawl.svg" + } + }, + { + "name": "Send message", + "description": "Posts a message to a slack channel, direct message, or private group; requires content via `text`, `blocks`, or `attachments`.", + "parameters": { + "type": "object", + "properties": { + "as_user": { + "description": "Post as the authenticated user instead of as a bot. Defaults to `false`. If `true`, `username`, `icon_emoji`, and `icon_url` are ignored. If `false`, the message is posted as a bot, allowing appearance customization.", + "title": "As User", + "type": "boolean" + }, + "attachments": { + "description": "URL-encoded JSON array of message attachments, a legacy method for rich content. See Slack API documentation for structure.", + "examples": [ + "%5B%7B%22fallback%22%3A%20%22Required%20plain-text%20summary%20of%20the%20attachment.%22%2C%20%22color%22%3A%20%22%2336a64f%22%2C%20%22pretext%22%3A%20%22Optional%20text%20that%20appears%20above%20the%20attachment%20block%22%2C%20%22author_name%22%3A%20%22Bobby%20Tables%22%2C%20%22title%22%3A%20%22Slack%20API%20Documentation%22%2C%20%22title_link%22%3A%20%22https%3A%2F%2Fapi.slack.com%2F%22%2C%20%22text%22%3A%20%22Optional%20text%20that%20appears%20within%20the%20attachment%22%7D%5D" + ], + "title": "Attachments", + "type": "string" + }, + "blocks": { + "description": "DEPRECATED: Use `markdown_text` field instead. URL-encoded JSON array of layout blocks for rich/interactive messages. See Slack API Block Kit docs for structure.", + "examples": [ + "%5B%7B%22type%22%3A%20%22section%22%2C%20%22text%22%3A%20%7B%22type%22%3A%20%22mrkdwn%22%2C%20%22text%22%3A%20%22Hello%2C%20world%21%22%7D%7D%5D" + ], + "title": "Blocks", + "type": "string" + }, + "channel": { + "description": "ID or name of the channel, private group, or IM channel to send the message to.", + "examples": [ + "C1234567890", + "general" + ], + "title": "Channel", + "type": "string" + }, + "icon_emoji": { + "description": "Emoji for bot's icon (e.g., ':robot_face:'). Overrides `icon_url`. Applies if `as_user` is `false`.", + "examples": [ + ":tada:", + ":slack:" + ], + "title": "Icon Emoji", + "type": "string" + }, + "icon_url": { + "description": "Image URL for bot's icon (must be HTTPS). Applies if `as_user` is `false`.", + "examples": [ + "https://slack.com/img/icons/appDir_2019_01/Tonito64.png" + ], + "title": "Icon Url", + "type": "string" + }, + "link_names": { + "description": "Automatically hyperlink channel names (e.g., #channel) and usernames (e.g., @user) in message text. Defaults to `false` for bot messages.", + "title": "Link Names", + "type": "boolean" + }, + "markdown_text": { + "description": "PREFERRED: Write your message in markdown for nicely formatted display. Supports: headers (# ## ###), bold (**text** or __text__), italic (*text* or _text_), strikethrough (~~text~~), inline code (`code`), code blocks (```), links ([text](url)), block quotes (>), lists (- item, 1. item), dividers (--- or ***), context blocks (:::context with images), and section buttons (:::section-button). IMPORTANT: Use \\n for line breaks (e.g., 'Line 1\\nLine 2'), not actual newlines. USER MENTIONS: To tag users, use their user ID with <@USER_ID> format (e.g., <@U1234567890>), not username. ", + "examples": [ + "# Status Update\\n\\nSystem is **running smoothly** with *excellent* performance.\\n\\n```bash\\nkubectl get pods\\n```\\n\\n> All services operational ✅", + "## Daily Report\\n\\n- **Deployments**: 5 successful\\n- *Issues*: 0 critical\\n- ~~Maintenance~~: **Completed**\\n\\n---\\n\\n**Next**: Monitor for 24h" + ], + "title": "Markdown Text", + "type": "string" + }, + "mrkdwn": { + "description": "Disable Slack's markdown for `text` field if `false`. Default `true` (allows *bold*, _italic_, etc.).", + "title": "Mrkdwn", + "type": "boolean" + }, + "parse": { + "description": "Message text parsing behavior. Default `none` (no special parsing). `full` parses as user-typed (links @mentions, #channels). See Slack API docs for details.", + "examples": [ + "none", + "full" + ], + "title": "Parse", + "type": "string" + }, + "reply_broadcast": { + "description": "If `true` for a threaded reply, also posts to main channel. Defaults to `false`.", + "title": "Reply Broadcast", + "type": "boolean" + }, + "text": { + "description": "DEPRECATED: This sends raw text only, use markdown_text field. Primary textual content. Recommended fallback if using `blocks` or `attachments`. Supports mrkdwn unless `mrkdwn` is `false`.", + "examples": [ + "Hello from your friendly bot!", + "Reminder: Team meeting at 3 PM today." + ], + "title": "Text", + "type": "string" + }, + "thread_ts": { + "description": "Timestamp (`ts`) of an existing message to make this a threaded reply. Use `ts` of the parent message, not another reply. Example: '1476746824.000004'.", + "examples": [ + "1618033790.001500" + ], + "title": "Thread Ts", + "type": "string" + }, + "unfurl_links": { + "description": "Enable unfurling of text-based URLs. Defaults `false` for bots, `true` if `as_user` is `true`.", + "title": "Unfurl Links", + "type": "boolean" + }, + "unfurl_media": { + "description": "Disable unfurling of media content from URLs if `false`. Defaults to `true`.", + "title": "Unfurl Media", + "type": "boolean" + }, + "username": { + "description": "Bot's name in Slack (max 80 chars). Applies if `as_user` is `false`.", + "examples": [ + "MyBot", + "AlertBot" + ], + "title": "Username", + "type": "string" + } + }, + "required": [ + "channel" + ] + }, + "mockTool": true, + "isComposio": true, + "composioData": { + "slug": "SLACK_SEND_MESSAGE", + "noAuth": false, + "toolkitName": "slack", + "toolkitSlug": "slack", + "logo": "https://cdn.jsdelivr.net/gh/ComposioHQ/open-logos@master/slack.svg" + } + } + ], + "startAgent": "Attendee Research & Slack Pipeline", + "pipelines": [ + { + "name": "Attendee Research & Slack Pipeline", + "description": "Pipeline that researches meeting attendees and sends the compiled summary to a specified Slack channel.", + "agents": [ + "Research Agent", + "Slack Send Agent" + ] + } + ], + "name": "Meeting Prep", + "description": "Research meeting attendees and send summary to Slack" +} diff --git a/apps/rowboat/app/lib/project_templates.ts b/apps/rowboat/app/lib/project_templates.ts index f76121dd2..4dbfa310b 100644 --- a/apps/rowboat/app/lib/project_templates.ts +++ b/apps/rowboat/app/lib/project_templates.ts @@ -1,11 +1,21 @@ import { WorkflowTemplate } from "./types/workflow_types"; import { z } from 'zod'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); const DEFAULT_MODEL = process.env.PROVIDER_DEFAULT_MODEL || "gpt-4.1"; -export const templates: { [key: string]: z.infer } = { - // Default template - 'default': { +// Function to load prebuilt cards from JSON files +function loadTemplatesFromFiles(): { [key: string]: z.infer } { + const templatesDir = path.join(__dirname, 'prebuilt-cards'); + const templates: { [key: string]: z.infer } = {}; + + // Add default template + templates['default'] = { name: 'Blank Template', description: 'A blank canvas to build your agents.', startAgent: "Example Agent", @@ -25,1301 +35,42 @@ export const templates: { [key: string]: z.infer } = { ], prompts: [], tools: [], - }, - - "meeting-prep": { - "name": "Meeting Prep", - "description": "Pipeline that researches meeting attendees and sends the compiled summary to a specified Slack channel", - "agents": [ - { - "name": "Research Agent", - "type": "pipeline", - "description": "Internal agent that researches meeting attendees and returns a compiled summary.", - "disabled": false, - "instructions": "## Role\nYou are a pipeline agent that researches meeting attendees.\n\n---\n## Task\n1. You will receive attendee details from a previous step.\n2. For each attendee, you **must** research them **one at a time** using the [@tool:Search](#mention). Do NOT research the user `{{Exclude user}}`!\n3. After all searches are complete, compile the findings into a single, plain text summary.\n4. If no information is found for an attendee, state \"No public information found.\" for that person.\n5. Return **only** the final compiled summary.\n\n---\n## Constraint\nDo **NOT** interact with users or send messages. Your only output is the final summary text.", - "model": "google/gemini-2.5-flash", - "locked": false, - "toggleAble": true, - "ragReturnType": "chunks", - "ragK": 3, - "controlType": "relinquish_to_parent", - "outputVisibility": "internal", - "maxCallsPerParentAgent": 3 - }, - { - "name": "Slack Send Agent", - "type": "pipeline", - "description": "Internal agent that sends the compiled research summary to a channel via Slack direct message and returns confirmation.", - "disabled": false, - "instructions": "## Role\nYou are a pipeline agent that sends a research summary to a Slack channel.\n\n---\n## Task\n1. You will receive a compiled text summary from the previous step.\n2. Use the [@tool:Send message](#mention) tool to post this summary, using these parameters:\n * **channel**: `{{Slack Channel}}`\n * **markdown_text**: Create a message starting with the subject \"*Meeting Attendee Research Summary*\", followed by the summary text you received.\n3. Your job is complete after sending the message.\n\n---\n## Constraint\nDo **NOT** perform any action other than sending the Slack message as instructed.", - "model": "google/gemini-2.5-flash", - "locked": false, - "toggleAble": true, - "ragReturnType": "chunks", - "ragK": 3, - "controlType": "relinquish_to_parent", - "outputVisibility": "internal", - "maxCallsPerParentAgent": 3, - "examples": "- **Parent agent** : \n - **Agent actions**: Call [@tool:Send message](#mention)\n - **Agent response**: Message sent to Slack channel.\n" - }, - { - "name": "Attendee Research & Slack Pipeline Step 1", - "type": "pipeline", - "description": "", - "disabled": false, - "instructions": "", - "model": "gpt-4o", - "locked": false, - "toggleAble": true, - "ragReturnType": "chunks", - "ragK": 3, - "controlType": "relinquish_to_parent", - "outputVisibility": "internal", - "maxCallsPerParentAgent": 3 - } - ], - "prompts": [ - { - "name": "Slack Channel", - "type": "base_prompt", - "prompt": "" - }, - { - "name": "Exclude user", - "type": "base_prompt", - "prompt": "" - } - ], - "tools": [ - { - "name": "Search", - "description": "Performs a web search and scrapes content from the top results.", - "parameters": { - "type": "object", - "properties": { - "query": { - "type": "string", - "description": "The search query." - } - }, - "required": [ - "query" - ] - }, - "mockTool": true, - "isComposio": true, - "composioData": { - "slug": "FIRECRWAL_SEARCH", - "noAuth": false, - "toolkitName": "firecrawl", - "toolkitSlug": "firecrawl", - "logo": "https://cdn.jsdelivr.net/gh/ComposioHQ/open-logos@master/firecrawl.svg" - } - }, - { - "name": "Send message", - "description": "Posts a message to a slack channel, direct message, or private group; requires content via `text`, `blocks`, or `attachments`.", - "parameters": { - "type": "object", - "properties": { - "as_user": { - "description": "Post as the authenticated user instead of as a bot. Defaults to `false`. If `true`, `username`, `icon_emoji`, and `icon_url` are ignored. If `false`, the message is posted as a bot, allowing appearance customization.", - "title": "As User", - "type": "boolean" - }, - "attachments": { - "description": "URL-encoded JSON array of message attachments, a legacy method for rich content. See Slack API documentation for structure.", - "examples": [ - "%5B%7B%22fallback%22%3A%20%22Required%20plain-text%20summary%20of%20the%20attachment.%22%2C%20%22color%22%3A%20%22%2336a64f%22%2C%20%22pretext%22%3A%20%22Optional%20text%20that%20appears%20above%20the%20attachment%20block%22%2C%20%22author_name%22%3A%20%22Bobby%20Tables%22%2C%20%22title%22%3A%20%22Slack%20API%20Documentation%22%2C%20%22title_link%22%3A%20%22https%3A%2F%2Fapi.slack.com%2F%22%2C%20%22text%22%3A%20%22Optional%20text%20that%20appears%20within%20the%20attachment%22%7D%5D" - ], - "title": "Attachments", - "type": "string" - }, - "blocks": { - "description": "DEPRECATED: Use `markdown_text` field instead. URL-encoded JSON array of layout blocks for rich/interactive messages. See Slack API Block Kit docs for structure.", - "examples": [ - "%5B%7B%22type%22%3A%20%22section%22%2C%20%22text%22%3A%20%7B%22type%22%3A%20%22mrkdwn%22%2C%20%22text%22%3A%20%22Hello%2C%20world%21%22%7D%7D%5D" - ], - "title": "Blocks", - "type": "string" - }, - "channel": { - "description": "ID or name of the channel, private group, or IM channel to send the message to.", - "examples": [ - "C1234567890", - "general" - ], - "title": "Channel", - "type": "string" - }, - "icon_emoji": { - "description": "Emoji for bot's icon (e.g., ':robot_face:'). Overrides `icon_url`. Applies if `as_user` is `false`.", - "examples": [ - ":tada:", - ":slack:" - ], - "title": "Icon Emoji", - "type": "string" - }, - "icon_url": { - "description": "Image URL for bot's icon (must be HTTPS). Applies if `as_user` is `false`.", - "examples": [ - "https://slack.com/img/icons/appDir_2019_01/Tonito64.png" - ], - "title": "Icon Url", - "type": "string" - }, - "link_names": { - "description": "Automatically hyperlink channel names (e.g., #channel) and usernames (e.g., @user) in message text. Defaults to `false` for bot messages.", - "title": "Link Names", - "type": "boolean" - }, - "markdown_text": { - "description": "PREFERRED: Write your message in markdown for nicely formatted display. Supports: headers (# ## ###), bold (**text** or __text__), italic (*text* or _text_), strikethrough (~~text~~), inline code (`code`), code blocks (```), links ([text](url)), block quotes (>), lists (- item, 1. item), dividers (--- or ***), context blocks (:::context with images), and section buttons (:::section-button). IMPORTANT: Use \\n for line breaks (e.g., 'Line 1\\nLine 2'), not actual newlines. USER MENTIONS: To tag users, use their user ID with <@USER_ID> format (e.g., <@U1234567890>), not username. ", - "examples": [ - "# Status Update\\n\\nSystem is **running smoothly** with *excellent* performance.\\n\\n```bash\\nkubectl get pods\\n```\\n\\n> All services operational ✅", - "## Daily Report\\n\\n- **Deployments**: 5 successful\\n- *Issues*: 0 critical\\n- ~~Maintenance~~: **Completed**\\n\\n---\\n\\n**Next**: Monitor for 24h" - ], - "title": "Markdown Text", - "type": "string" - }, - "mrkdwn": { - "description": "Disable Slack's markdown for `text` field if `false`. Default `true` (allows *bold*, _italic_, etc.).", - "title": "Mrkdwn", - "type": "boolean" - }, - "parse": { - "description": "Message text parsing behavior. Default `none` (no special parsing). `full` parses as user-typed (links @mentions, #channels). See Slack API docs for details.", - "examples": [ - "none", - "full" - ], - "title": "Parse", - "type": "string" - }, - "reply_broadcast": { - "description": "If `true` for a threaded reply, also posts to main channel. Defaults to `false`.", - "title": "Reply Broadcast", - "type": "boolean" - }, - "text": { - "description": "DEPRECATED: This sends raw text only, use markdown_text field. Primary textual content. Recommended fallback if using `blocks` or `attachments`. Supports mrkdwn unless `mrkdwn` is `false`.", - "examples": [ - "Hello from your friendly bot!", - "Reminder: Team meeting at 3 PM today." - ], - "title": "Text", - "type": "string" - }, - "thread_ts": { - "description": "Timestamp (`ts`) of an existing message to make this a threaded reply. Use `ts` of the parent message, not another reply. Example: '1476746824.000004'.", - "examples": [ - "1618033790.001500" - ], - "title": "Thread Ts", - "type": "string" - }, - "unfurl_links": { - "description": "Enable unfurling of text-based URLs. Defaults `false` for bots, `true` if `as_user` is `true`.", - "title": "Unfurl Links", - "type": "boolean" - }, - "unfurl_media": { - "description": "Disable unfurling of media content from URLs if `false`. Defaults to `true`.", - "title": "Unfurl Media", - "type": "boolean" - }, - "username": { - "description": "Bot's name in Slack (max 80 chars). Applies if `as_user` is `false`.", - "examples": [ - "MyBot", - "AlertBot" - ], - "title": "Username", - "type": "string" - } - }, - "required": [ - "channel" - ] - }, - "mockTool": true, - "isComposio": true, - "composioData": { - "slug": "SLACK_SEND_MESSAGE", - "noAuth": false, - "toolkitName": "slack", - "toolkitSlug": "slack", - "logo": "https://cdn.jsdelivr.net/gh/ComposioHQ/open-logos@master/slack.svg" - } - } - ], - "pipelines": [ - { - "name": "Attendee Research & Slack Pipeline", - "description": "Pipeline that researches meeting attendees and sends the compiled summary to a specified Slack channel.", - "agents": [ - "Research Agent", - "Slack Send Agent" - ] - } - ], - "startAgent": "Attendee Research & Slack Pipeline" - }, - - "interview-scheduler": { - "name": "Interview Scheduler", - "description": "Orchestrates interview scheduling with candidates from a Google Sheet and handles calendar RSVPs", - "agents": [ - { - "name": "Recruitment HR Bot", - "type": "conversation", - "description": "Hub agent to orchestrate interview scheduling with candidates from a Google Sheet.", - "instructions": "## 🧑‍💼 Role:\nYou are the Recruitment HR Bot, responsible for orchestrating the process of scheduling interviews with candidates from a Google Sheet and updating their status, or handling calendar event RSVPs.\n\n---\n## ⚙️ Steps to Follow:\n1. Greet the user.\n2. **IF** the input is a calendar event RSVP (e.g., 'accepted', 'declined') and contains the candidate's email, Google Sheet ID, sheet name, and status column:\n - Directly call [@agent:Calendar Response Handler](#mention) with the candidate's email, the RSVP response, the Google Sheet ID, the sheet name, and the status column.\n - Inform the user that the calendar response has been processed.\n3. **ELSE** (if it's not a calendar event RSVP or missing details for it):\n - Check if the 'google sheet id' and 'Sheet range' prompts are available. If so, use their values. Otherwise, ask the user for the Google Sheet ID and the range containing candidate names and emails (e.g., 'Sheet1!A2:B').\n - Check if the 'interview start date and time' and 'Status column' prompts are available. If so, use their values. Otherwise, ask for the desired start date and time for interviews (e.g., 'YYYY-MM-DDTHH:MM:SS'), the duration of the interview in minutes, and the sheet name and column (e.g., 'Sheet1!C') where the interview status should be updated.\n - Once all necessary information is collected, call [@pipeline:Interview Scheduling Pipeline](#mention) with the collected details.\n - Inform the user when the interview scheduling process is complete.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Orchestrating the workflow for fetching candidates, scheduling interviews, and updating sheet status.\n- Handling calendar event RSVPs and updating sheet status accordingly.\n\n❌ Out of Scope:\n- Directly fetching candidate data, scheduling interviews, or updating sheet status (handled by pipeline agents).\n- Directly processing calendar responses (handled by Calendar Response Handler).\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Prioritize handling calendar event RSVPs if the necessary information is present.\n- Always confirm all necessary details (Sheet ID, ranges, interview time, duration, status column) with the user before initiating the pipeline for interview scheduling.\n- Ensure all steps are completed in sequence.\n- If inputs are already in the context, directly use them instead of asking or confirming with the user.\n\n🚫 Don'ts:\n- Do not perform data fetching, scheduling, or status updates directly.\n- Do not skip any step in the workflow.\n- Do not mention internal agent names to the user.\n- Do not say 'connecting you to another agent'.\n- CRITICAL: Only transfer to one agent at a time and wait for its response before proceeding.\n\n---\n## 📥 Inputs:\n- **Google Sheet ID**: The unique identifier of the Google Spreadsheet containing candidate data. (e.g., '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms')\n- **Sheet Range**: The range in A1 notation (e.g., 'Sheet1!A2:B') containing candidate names and emails.\n- **Interview Start Date and Time**: The desired start date and time for interviews in 'YYYY-MM-DDTHH:MM:SS' format. Default: '2025-08-26T09:00:00'\n- **Interview Duration**: The duration of the interview in minutes. Default: 30\n- **Status Column**: The sheet name and column (e.g., 'Sheet1!C') where the interview status should be updated.", - "examples": "- **User** : I want to schedule interviews for candidates from a Google Sheet.\n - **Agent response**: Sure! Please provide the desired start date and time for interviews (e.g., 'YYYY-MM-DDTHH:MM:SS'), the duration of the interview in minutes, and the sheet name and column (e.g., 'Sheet1!C') where the interview status should be updated.\n\n- **User** : Start Time: 2024-09-01T10:00:00, Duration: 30, Status Column: Sheet1!C\n - **Agent actions**: Call [@pipeline:Interview Scheduling Pipeline](#mention)\n\n- **Agent receives pipeline completion** :\n - **Agent response**: The interview scheduling process is complete.\n\n- **User** : Candidate [candidate_email] has accepted the interview. Sheet ID: [sheet_id], Sheet Name: [sheet_name], Status Column: [status_column]\n - **Agent actions**: Call [@agent:Calendar Response Handler](#mention)\n\n- **Agent receives Calendar Response Handler completion** :\n - **Agent response**: The calendar response has been processed and the sheet updated.", - "model": "google/gemini-2.5-flash", - "toggleAble": true, - "ragReturnType": "chunks", - "ragK": 3, - "outputVisibility": "user_facing", - "controlType": "retain" - }, - { - "name": "Pipeline Step 1 - Fetch Candidates", - "type": "pipeline", - "description": "Reads candidate names and emails from a specified Google Sheet range.", - "disabled": false, - "instructions": "## 🧑‍💼 Role:\nFetch candidate names and emails from the provided Google Sheet and ranges.\n\n---\n## ⚙️ Steps to Follow:\n1. Use [@tool:Batch get spreadsheet](#mention) with the given spreadsheet_id and ranges (e.g., 'Sheet1!A2:B').\n2. Return a normalized array of { name, email } objects.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Fetching rows from Google Sheets and returning structured data.\n\n❌ Out of Scope:\n- Scheduling interviews or updating sheet status.\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Validate rows and skip empties.\n🚫 Don'ts:\n- Do not schedule interviews or update sheet status.", - "model": "google/gemini-2.5-flash", - "locked": false, - "toggleAble": true, - "ragReturnType": "chunks", - "ragK": 3, - "outputVisibility": "internal", - "controlType": "relinquish_to_parent", - "maxCallsPerParentAgent": 3 - }, - { - "name": "Pipeline Step 2 - Schedule Interview", - "type": "pipeline", - "description": "Schedules an interview for each candidate using Google Calendar.", - "disabled": false, - "instructions": "## 🧑‍💼 Role:\nSchedule an interview for each candidate.\n\n---\n## ⚙️ Steps to Follow:\n1. Receive a list of { name, email } objects from the previous step.\n2. For each candidate, use [@tool:Create Event](#mention) to schedule an interview. The event summary should be 'Interview with [Candidate Name]', and the attendee should be the candidate's email. You will need to ask the user for the start_datetime and duration of the interview.\n3. Return a list of { candidate_email, status: 'scheduled' } for each successfully scheduled interview.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Scheduling interviews on Google Calendar.\n\n❌ Out of Scope:\n- Fetching candidate data or updating sheet status.\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Ensure all required fields for event creation are provided.\n🚫 Don'ts:\n- Do not fetch candidate data or update sheet status.", - "model": "google/gemini-2.5-flash", - "locked": false, - "toggleAble": true, - "ragReturnType": "chunks", - "ragK": 3, - "outputVisibility": "internal", - "controlType": "relinquish_to_parent", - "maxCallsPerParentAgent": 3 - }, - { - "name": "Pipeline Step 3 - Update Sheet Status", - "type": "pipeline", - "description": "Updates the status column in the Google Sheet to 'interview scheduled' for each candidate.", - "disabled": false, - "instructions": "## 🧑‍💼 Role:\nUpdate the status column in the Google Sheet for scheduled interviews.\n\n---\n## ⚙️ Steps to Follow:\n1. Receive a list of { candidate_email, status: 'scheduled' } objects from the previous step.\n2. For each candidate, use [@tool:Batch update spreadsheet](#mention) to update the corresponding row in the Google Sheet. You will need to ask the user for the spreadsheet_id, sheet_name, and the column where the status needs to be updated.\n3. The value to be updated should be 'invite sent'.\n4. Return a confirmation of the updates.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Updating the status column in the Google Sheet.\n\n❌ Out of Scope:\n- Fetching candidate data or scheduling interviews.\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Ensure the correct row and column are updated.\n🚫 Don'ts:\n- Do not fetch candidate data or schedule interviews.", - "model": "google/gemini-2.5-flash", - "locked": false, - "toggleAble": true, - "ragReturnType": "chunks", - "ragK": 3, - "outputVisibility": "internal", - "controlType": "relinquish_to_parent", - "maxCallsPerParentAgent": 3 - }, - { - "name": "Interview Scheduling Pipeline Step 1", - "type": "pipeline", - "description": "", - "disabled": false, - "instructions": "", - "model": "gpt-4o", - "locked": false, - "toggleAble": true, - "ragReturnType": "chunks", - "ragK": 3, - "outputVisibility": "internal", - "controlType": "relinquish_to_parent", - "maxCallsPerParentAgent": 3 - }, - { - "name": "Calendar Response Handler", - "type": "conversation", - "description": "Handles calendar accept/reject responses and updates the Google Sheet status accordingly.", - "disabled": false, - "instructions": "## 🧑‍💼 Role:\nProcess calendar responses (accept/reject) and update the Google Sheet with the appropriate interview status.\n\n---\n## ⚙️ Steps to Follow:\n1. Receive the candidate's email, the calendar response (e.g., 'accepted', 'declined'), the Google Sheet ID, the sheet name, and the column where the status needs to be updated.\n2. If the response is 'accepted', set the status to 'interview scheduled'.\n3. If the response is 'declined', set the status to 'declined'.\n4. Use [@tool:Batch update spreadsheet](#mention) to update the corresponding row in the Google Sheet with the determined status.\n5. Return a confirmation of the update.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Interpreting calendar responses and updating the Google Sheet status.\n\n❌ Out of Scope:\n- Scheduling interviews or fetching candidate data.\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Accurately map calendar responses to interview statuses.\n- Ensure the correct row and column are updated in the Google Sheet.\n🚫 Don'ts:\n- Do not interact with the user directly.\n- Do not schedule interviews.", - "model": "google/gemini-2.5-flash", - "locked": false, - "toggleAble": true, - "ragReturnType": "chunks", - "ragK": 3, - "outputVisibility": "internal", - "controlType": "relinquish_to_parent", - "maxCallsPerParentAgent": 3 - } - ], - "prompts": [ - { - "name": "google sheet id", - "type": "base_prompt", - "prompt": "" - }, - { - "name": "Sheet range", - "type": "base_prompt", - "prompt": "" - }, - { - "name": "interview start date and time", - "type": "base_prompt", - "prompt": "" - }, - { - "name": "Status column", - "type": "base_prompt", - "prompt": "" - } - ], - "tools": [ - { - "name": "Batch get spreadsheet", - "description": "Retrieves data from specified cell ranges in a google spreadsheet; ensure the spreadsheet has at least one worksheet and any explicitly referenced sheet names in ranges exist.", - "mockTool": false, - "parameters": { - "type": "object", - "properties": { - "ranges": { - "description": "A list of cell ranges in A1 notation (e.g., 'Sheet1!A1:B2', 'A1:C5') from which to retrieve data. If this list is omitted or empty, all data from the first sheet of the spreadsheet will be fetched. A range can specify a sheet name (e.g., 'Sheet2!A:A'); if no sheet name is provided in a range string (e.g., 'A1:B2'), it defaults to the first sheet.", - "examples": [ - "Sheet1!A1:B2", - "Sheet1!A:A", - "Sheet1!1:2", - "Sheet1!A5:A", - "A1:B2" - ], - "items": { - "type": "string" - }, - "title": "Ranges", - "type": "array" - }, - "spreadsheet_id": { - "description": "The unique identifier of the Google Spreadsheet from which data will be retrieved.", - "title": "Spreadsheet Id", - "type": "string" - } - }, - "required": [ - "spreadsheet_id" - ] - }, - "isComposio": true, - "composioData": { - "slug": "GOOGLESHEETS_BATCH_GET", - "noAuth": false, - "toolkitName": "googlesheets", - "toolkitSlug": "googlesheets", - "logo": "https://cdn.jsdelivr.net/gh/ComposioHQ/open-logos@master/google-sheets.svg" - } - }, - { - "name": "Create Event", - "description": "Creates an event on a google calendar, needing rfc3339 utc start/end times (end after start) and write access to the calendar. by default, adds the organizer as an attendee unless exclude organizer is set to true.", - "mockTool": false, - "parameters": { - "type": "object", - "properties": { - "attendees": { - "default": null, - "description": "List of attendee emails (strings).", - "items": { - "type": "string" - }, - "nullable": true, - "title": "Attendees", - "type": "array" - }, - "calendar_id": { - "default": "primary", - "description": "Target calendar: 'primary' for the user's main calendar, or the calendar's email address.", - "examples": [ - "primary", - "user@example.com", - "abcdefghijklmnopqrstuvwxyz@group.calendar.google.com" - ], - "title": "Calendar Id", - "type": "string" - }, - "create_meeting_room": { - "default": null, - "description": "If true, a Google Meet link is created and added to the event. CRITICAL: As of 2024, this REQUIRES a paid Google Workspace account ($13+/month). Personal Gmail accounts will fail with 'Invalid conference type value' error. Solutions: 1) Upgrade to Workspace, 2) Use domain-wide delegation with Workspace user, 3) Use the new Google Meet REST API, or 4) Create events without conferences. See https://github.com/googleapis/google-api-nodejs-client/issues/3234", - "nullable": true, - "title": "Create Meeting Room", - "type": "boolean" - }, - "description": { - "default": null, - "description": "Description of the event. Can contain HTML. Optional.", - "nullable": true, - "title": "Description", - "type": "string" - }, - "eventType": { - "default": "default", - "description": "Type of the event, immutable post-creation. Currently, only 'default' and 'workingLocation' can be created.", - "enum": [ - "default", - "outOfOffice", - "focusTime", - "workingLocation" - ], - "title": "Event Type", - "type": "string" - }, - "event_duration_hour": { - "default": 0, - "description": "Number of hours (0-24). Increase by 1 here rather than passing 60 in `event_duration_minutes`", - "maximum": 24, - "minimum": 0, - "title": "Event Duration Hour", - "type": "integer" - }, - "event_duration_minutes": { - "default": 30, - "description": "Duration in minutes (0-59 ONLY). NEVER use 60+ minutes - use event_duration_hour=1 instead. Maximum value is 59.", - "maximum": 59, - "minimum": 0, - "title": "Event Duration Minutes", - "type": "integer" - }, - "exclude_organizer": { - "default": false, - "description": "If True, the organizer will NOT be added as an attendee. Default is False (organizer is included).", - "title": "Exclude Organizer", - "type": "boolean" - }, - "guestsCanInviteOthers": { - "default": null, - "description": "Whether attendees other than the organizer can invite others to the event.", - "nullable": true, - "title": "Guests Can Invite Others", - "type": "boolean" - }, - "guestsCanSeeOtherGuests": { - "default": null, - "description": "Whether attendees other than the organizer can see who the event's attendees are.", - "nullable": true, - "title": "Guests Can See Other Guests", - "type": "boolean" - }, - "guests_can_modify": { - "default": false, - "description": "If True, guests can modify the event.", - "title": "Guests Can Modify", - "type": "boolean" - }, - "location": { - "default": null, - "description": "Geographic location of the event as free-form text.", - "nullable": true, - "title": "Location", - "type": "string" - }, - "recurrence": { - "default": null, - "description": "List of RRULE, EXRULE, RDATE, EXDATE lines for recurring events. Supported frequencies are DAILY, WEEKLY, MONTHLY, YEARLY.", - "items": { - "type": "string" - }, - "nullable": true, - "title": "Recurrence", - "type": "array" - }, - "send_updates": { - "default": null, - "description": "Defaults to True. Whether to send updates to the attendees.", - "nullable": true, - "title": "Send Updates", - "type": "boolean" - }, - "start_datetime": { - "description": "Naive date/time (YYYY-MM-DDTHH:MM:SS) with NO offsets or Z. e.g. '2025-01-16T13:00:00'", - "title": "Start Datetime", - "type": "string" - }, - "summary": { - "default": null, - "description": "Summary (title) of the event.", - "nullable": true, - "title": "Summary", - "type": "string" - }, - "timezone": { - "default": null, - "description": "IANA timezone name (e.g., 'America/New_York'). Required if datetime is naive. If datetime includes timezone info (Z or offset), this field is optional and defaults to UTC.", - "nullable": true, - "title": "Timezone", - "type": "string" - }, - "transparency": { - "default": "opaque", - "description": "'opaque' (busy) or 'transparent' (available).", - "enum": [ - "opaque", - "transparent" - ], - "title": "Transparency", - "type": "string" - }, - "visibility": { - "default": "default", - "description": "Event visibility: 'default', 'public', 'private', or 'confidential'.", - "enum": [ - "default", - "public", - "private", - "confidential" - ], - "title": "Visibility", - "type": "string" - } - }, - "required": [ - "start_datetime" - ] - }, - "isComposio": true, - "composioData": { - "slug": "GOOGLECALENDAR_CREATE_EVENT", - "noAuth": false, - "toolkitName": "googlecalendar", - "toolkitSlug": "googlecalendar", - "logo": "https://cdn.jsdelivr.net/gh/ComposioHQ/open-logos@master/google-calendar.svg" - } - }, - { - "name": "Batch update spreadsheet", - "description": "Updates a specified range in a google sheet with given values, or appends them as new rows if `first cell location` is omitted; ensure the target sheet exists and the spreadsheet contains at least one worksheet.", - "mockTool": false, - "parameters": { - "type": "object", - "properties": { - "first_cell_location": { - "description": "The starting cell for the update range, specified in A1 notation (e.g., 'A1', 'B2'). The update will extend from this cell to the right and down, based on the provided values. If omitted, values are appended to the sheet.", - "examples": [ - "A1", - "D3" - ], - "title": "First Cell Location", - "type": "string" - }, - "includeValuesInResponse": { - "default": false, - "description": "If set to True, the response will include the updated values from the spreadsheet.", - "examples": [ - true, - false - ], - "title": "Include Values In Response", - "type": "boolean" - }, - "sheet_name": { - "description": "The name of the specific sheet within the spreadsheet to update.", - "examples": [ - "Sheet1" - ], - "title": "Sheet Name", - "type": "string" - }, - "spreadsheet_id": { - "description": "The unique identifier of the Google Sheets spreadsheet to be updated.", - "examples": [ - "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms" - ], - "title": "Spreadsheet Id", - "type": "string" - }, - "valueInputOption": { - "default": "USER_ENTERED", - "description": "How input data is interpreted. 'USER_ENTERED': Values parsed as if typed by a user (e.g., strings may become numbers/dates, formulas are calculated); recommended for formulas. 'RAW': Values stored as-is without parsing (e.g., '123' stays string, '=SUM(A1:B1)' stays string).", - "enum": [ - "RAW", - "USER_ENTERED" - ], - "examples": [ - "USER_ENTERED", - "RAW" - ], - "title": "Value Input Option", - "type": "string" - }, - "values": { - "description": "A 2D list of cell values. Each inner list represents a row. Values can be strings, numbers, or booleans. Ensure columns are properly aligned across rows.", - "examples": [ - [ - "Item", - "Cost", - "Stocked", - "Ship Date" - ], - [ - "Wheel", - 20.5, - true, - "2020-06-01" - ], - [ - "Screw", - 0.5, - true, - "2020-06-03" - ], - [ - "Nut", - 0.25, - false, - "2020-06-02" - ] - ], - "items": { - "items": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "integer" - }, - { - "type": "number" - }, - { - "type": "boolean" - } - ] - }, - "type": "array" - }, - "title": "Values", - "type": "array" + }; + + try { + // Check if prebuilt cards directory exists + if (fs.existsSync(templatesDir)) { + const files = fs.readdirSync(templatesDir); + + // Load each JSON file + files.forEach(file => { + if (path.extname(file) === '.json') { + try { + const filePath = path.join(templatesDir, file); + const fileContent = fs.readFileSync(filePath, 'utf-8'); + const templateData = JSON.parse(fileContent); + + // Use filename without extension as template key + const templateKey = path.basename(file, '.json'); + + // Validate template structure (optional - you can add more validation) + if (templateData.agents && Array.isArray(templateData.agents)) { + templates[templateKey] = templateData; } - }, - "required": [ - "spreadsheet_id", - "sheet_name", - "values" - ] - }, - "isComposio": true, - "composioData": { - "slug": "GOOGLESHEETS_BATCH_UPDATE", - "noAuth": false, - "toolkitName": "googlesheets", - "toolkitSlug": "googlesheets", - "logo": "https://cdn.jsdelivr.net/gh/ComposioHQ/open-logos@master/google-sheets.svg" + } catch (error) { + console.warn(`Failed to load prebuilt card ${file}:`, error); + } } - } - ], - "pipelines": [ - { - "name": "Interview Scheduling Pipeline", - "description": "Automates interview scheduling: fetches candidates from Google Sheet, schedules interviews, and updates sheet status.", - "agents": [ - "Pipeline Step 1 - Fetch Candidates", - "Pipeline Step 2 - Schedule Interview", - "Pipeline Step 3 - Update Sheet Status" - ] - } - ], - "startAgent": "Recruitment HR Bot" - }, - - "github-data-to-spreadsheet": { - "name": "Add GitHub Stats to Google Sheets", - "description": "Fetches GitHub repository stats and logs them to a Google Sheet with Slack notifications", - "agents": [ - { - "name": "GitHub Stats Hub", - "type": "conversation", - "description": "Hub agent that orchestrates fetching GitHub stats for rowboatlabs/rowboat and logging them to a Google Sheet.", - "instructions": "## 🧑‍💼 Role:\nYou are the hub agent responsible for orchestrating the process of fetching GitHub repository stats for 'rowboatlabs/rowboat' and logging them to a Google Sheet.\n\n---\n## ⚙️ Steps to Follow:\n1. Receive a user request to log GitHub stats.\n2. FIRST: Call [@agent:GitHub Stats Agent](#mention) and always provide repository owner: 'rowboatlabs' and repo: 'rowboat' as input (do not prompt the user for these values).\n3. Wait for the stats to be returned.\n4. THEN: Call [@agent:GitHub Stats to Sheet Agent](#mention) to append the stats to the Google Sheet.\n5. Wait for confirmation from the Sheets agent.\n6. Inform the user that the data has been logged, or report any error if one occurred.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Orchestrating the sequential workflow: fetch stats for rowboatlabs/rowboat, then log to sheet, then inform the user.\n\n❌ Out of Scope:\n- Fetching stats or logging to the sheet directly (handled by sub-agents).\n- Handling requests unrelated to GitHub stats logging.\n- Accepting or prompting for other repositories.\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Always use 'rowboatlabs' as owner and 'rowboat' as repo when calling the GitHub Stats Agent.\n- Always follow the sequence: GitHub Stats Agent first, then GitHub Stats to Sheet Agent.\n- Wait for each agent's complete response before proceeding.\n- Only interact with the user for the initial request and final confirmation.\n\n🚫 Don'ts:\n- Do not perform stats fetching or sheet logging yourself.\n- Do not try to call both agents at once.\n- Do not reference internal agent names to the user.\n- Do not prompt the user for a repository or accept any other repository.\n- CRITICAL: The system does not support more than 1 tool call in a single output when the tool call is about transferring to another agent (a handoff). You must only put out 1 transfer related tool call in one output.\n\n# Examples\n- **User** : Fetch and store stats\n - **Agent actions**: Call [@agent:GitHub Stats Agent](#mention) with owner: 'rowboatlabs', repo: 'rowboat'\n\n- **Agent receives stats** :\n - **Agent actions**: Call [@agent:GitHub Stats to Sheet Agent](#mention)\n\n- **Agent receives sheet confirmation** :\n - **Agent response**: GitHub stats have been logged to the sheet successfully.\n\n- **Agent receives error from sheet agent** :\n - **Agent response**: There was an error logging the stats to the sheet: [error details]\n\n- **User** : Add a dummy row\n - **Agent response**: Sorry, I can only log actual GitHub stats. Please use the workflow to log real data.", - "model": "google/gemini-2.5-flash", - "toggleAble": true, - "ragReturnType": "chunks", - "ragK": 3, - "outputVisibility": "user_facing", - "controlType": "retain" - }, - { - "name": "GitHub Stats Agent", - "type": "conversation", - "description": "Fetches GitHub page view and clone statistics for rowboatlabs/rowboat for the previous day.", - "disabled": false, - "instructions": "## 🧑‍💼 Role:\nYou are an internal agent that fetches GitHub page view and clone statistics for the repository for the previous day.\n\n---\n## ⚙️ Steps to Follow:\n1. Always use owner: and repo: (do not expect or prompt for these values from the parent agent).\n2. Use [@tool:Get page views](#mention) with per: 'day' to fetch daily page view stats. You must actually call this tool.\n3. Use [@tool:Get repository clones](#mention) with per: 'day' to fetch daily clone stats. You must actually call this tool.\n4. Filter both results to only include data for the previous day (relative to today, in UTC).\n5. Return both sets of stats (page views and clones for the previous day) to the parent agent.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Fetching and returning GitHub page view and clone stats for 'rowboatlabs/rowboat' for the previous day.\n\n❌ Out of Scope:\n- Answering user questions directly.\n- Modifying repository data.\n- Accepting or prompting for any other repository.\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Return only the stats for the previous day.\n- Return both page views and clone stats in a clear, structured format.\n- **Do not simulate or describe tool calls—always actually call the tools.**\n\n🚫 Don'ts:\n- Do not interact with the user directly.\n- Do not perform any actions other than fetching and returning stats.\n- Do not prompt for or accept any repository input.\n\n# Examples\n- **Parent agent** : Fetch and store stats\n - **Agent actions**: Call [@tool:Get page views](#mention) with owner, repo, per: 'day'. Then call [@tool:Get repository clones](#mention) with owner, repo: 'rowboat', per: 'day'.\n - **Agent response**: [Page views and clone stats for owner/repo for the previous day]\n\n\n\n", - "model": "google/gemini-2.5-flash", - "locked": false, - "toggleAble": true, - "ragReturnType": "chunks", - "ragK": 3, - "outputVisibility": "internal", - "controlType": "relinquish_to_parent", - "maxCallsPerParentAgent": 3 - }, - { - "name": "GitHub Stats to Sheet Agent", - "type": "conversation", - "description": "Appends the latest GitHub clone and view stats as a new row to a specified Google Sheet.", - "disabled": false, - "instructions": "## 🧑‍💼 Role:\nYou are an internal agent that receives GitHub stats (clones and views), extracts the most recent date for each, and appends a row to a Google Sheet with the specified columns.\n\n---\n## ⚙️ Steps to Follow:\n1. Receive GitHub stats data (including arrays of daily clone and view stats, each with date, count, and uniques).\n2. Identify the most recent (latest) date in the clones array and extract its count and uniques.\n3. Identify the most recent (latest) date in the views array and extract its count and uniques.\n4. Use the current UTC date (YYYY-MM-DD) as the run date.\n5. Prepare a row with the following columns (in order):\n - run date (current UTC date, YYYY-MM-DD)\n - latest clones stats date (YYYY-MM-DD)\n - clones (count)\n - unique clones\n - latest view stats date (YYYY-MM-DD)\n - views (count)\n - unique views\n6. Use [@tool:Append Values to Spreadsheet](#mention) to append this row to the end of the sheet (no headers).\n - spreadsheetId: \n - range: (or the correct sheet name if specified)\n - valueInputOption: USER_ENTERED\n - values: [[run date, latest clones stats date, clones, unique clones, latest view stats date, views, unique views]]\n7. Return a confirmation or error message to the parent agent.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Appending a single row of stats to the specified Google Sheet.\n\n❌ Out of Scope:\n- Adding headers or modifying existing data.\n- Interacting with the user directly.\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Only use the most recent date for each stat type.\n- Ensure the row is appended at the end (no headers).\n- Use the correct spreadsheetId and valueInputOption.\n\n🚫 Don'ts:\n- Do not add column headers.\n- Do not overwrite existing data.\n- Do not interact with the user directly.\n\n# Examples\n- **Parent agent** : Insert latest GitHub stats into sheet\n - **Agent actions**: Call [@tool:Append Values to Spreadsheet](#mention) with the latest stats and current date\n - **Agent response**: Row appended confirmation or error message\n\n- **Parent agent** : Insert stats with missing data\n - **Agent actions**: If either clones or views data is missing, append available data and leave missing fields blank\n - **Agent response**: Row appended confirmation or error message\n\n- **Parent agent** : Insert stats for a different repo\n - **Agent actions**: Same as above, using provided stats\n - **Agent response**: Row appended confirmation or error message\n\n- **Parent agent** : Insert stats with only views data\n - **Agent actions**: Append row with views data, leave clone fields blank\n - **Agent response**: Row appended confirmation or error message\n\n- **Parent agent** : Insert stats with only clones data\n - **Agent actions**: Append row with clone data, leave view fields blank\n - **Agent response**: Row appended confirmation or error message\n", - "model": "google/gemini-2.5-flash", - "locked": false, - "toggleAble": true, - "ragReturnType": "chunks", - "ragK": 3, - "outputVisibility": "internal", - "controlType": "relinquish_to_parent", - "maxCallsPerParentAgent": 3 - }, - { - "name": "Pipeline Step 1 - Fetch Views Data", - "type": "pipeline", - "description": "Fetches daily page view stats for rowboatlabs/rowboat using the Get page views tool.", - "disabled": false, - "instructions": "## 🧑‍💼 Role:\nFetch daily page view stats for the repository 'rowboatlabs/rowboat'.\n\n---\n## ⚙️ Steps to Follow:\n1. Use [@tool:Get page views](#mention) with owner: 'rowboatlabs', repo: 'rowboat', per: 'day'.\n2. Return the full result to the next pipeline step.\n\n---\n## 📋 Guidelines:\n- Do not prompt for repository details; always use the specified owner and repo.\n- Do not interact with the user or other agents.", - "model": "google/gemini-2.5-flash", - "locked": false, - "toggleAble": true, - "ragReturnType": "chunks", - "ragK": 3, - "outputVisibility": "internal", - "controlType": "relinquish_to_parent", - "maxCallsPerParentAgent": 3 - }, - { - "name": "Pipeline Step 2 - Fetch Clones Data", - "type": "pipeline", - "description": "Fetches daily clone stats for rowboatlabs/rowboat using the Get repository clones tool.", - "disabled": false, - "instructions": "## 🧑‍💼 Role:\nFetch daily clone stats for the repository 'rowboatlabs/rowboat'.\n\n---\n## ⚙️ Steps to Follow:\n1. Use [@tool:Get repository clones](#mention) with owner: 'rowboatlabs', repo: 'rowboat', per: 'day'.\n2. Return the full result to the next pipeline step, along with the previous step's page views data.\n\n---\n## 📋 Guidelines:\n- Do not prompt for repository details; always use the specified owner and repo.\n- Do not interact with the user or other agents.", - "model": "google/gemini-2.5-flash", - "locked": false, - "toggleAble": true, - "ragReturnType": "chunks", - "ragK": 3, - "outputVisibility": "internal", - "controlType": "relinquish_to_parent", - "maxCallsPerParentAgent": 3 - }, - { - "name": "Pipeline Step 3 - Add Data to Sheet", - "type": "pipeline", - "description": "Appends the latest GitHub clone and view stats as a new row to the specified Google Sheet.", - "disabled": false, - "instructions": "## 🧑‍💼 Role:\nAppend the latest GitHub stats (clones and views) as a new row to the Google Sheet.\n\n---\n## ⚙️ Steps to Follow:\n1. Receive both page views and clone stats (arrays of daily stats, each with date, count, uniques).\n2. Identify the most recent (latest) date in the clones array and extract its count and uniques.\n3. Identify the most recent (latest) date in the views array and extract its count and uniques.\n4. Use the current UTC date (YYYY-MM-DD) as the run date.\n5. Prepare a row with the following columns (in order):\n - run date (current UTC date, YYYY-MM-DD)\n - latest clones stats date (YYYY-MM-DD)\n - clones (count)\n - unique clones\n - latest view stats date (YYYY-MM-DD)\n - views (count)\n - unique views\n6. Use [@tool:Append Values to Spreadsheet](#mention) to append this row to the end of the sheet (no headers).\n - spreadsheetId: \n - range: \n - valueInputOption: USER_ENTERED\n - values: [[run date, latest clones stats date, clones, unique clones, latest view stats date, views, unique views]]\n7. Return the appended row and all relevant stats to the next pipeline step.\n\n---\n## 📋 Guidelines:\n- Only use the most recent date for each stat type.\n- Ensure the row is appended at the end (no headers).\n- Use the correct spreadsheetId and valueInputOption.\n- Do not interact with the user or other agents.\n\n", - "model": "google/gemini-2.5-flash", - "locked": false, - "toggleAble": true, - "ragReturnType": "chunks", - "ragK": 3, - "outputVisibility": "internal", - "controlType": "relinquish_to_parent", - "maxCallsPerParentAgent": 3 - }, - { - "name": "Pipeline Step 4 - Send Slack Summary", - "type": "pipeline", - "description": "Sends a summary message to the #stats Slack channel, including a link to the updated sheet.", - "disabled": false, - "instructions": "## 🧑‍💼 Role:\nSend a summary message to the #stats Slack channel after stats are logged to the sheet.\n\n---\n## ⚙️ Steps to Follow:\n1. Receive the appended row and all relevant stats from the previous step.\n2. Compose a message summarizing the latest GitHub stats update, including:\n - The run date\n - The latest clones and views stats (date, count, uniques)\n - A statement that the data has been updated in the sheet\n - A link to the sheet: \n3. Use [@tool:Send a message to a Slack channel](#mention) to post the message to channel: stats\n4. Return a confirmation or error message.\n\n---\n## 📋 Guidelines:\n- The message should be clear, concise, and include the sheet link.\n- Do not interact with the user or other agents.\n", - "model": "google/gemini-2.5-flash", - "locked": false, - "toggleAble": true, - "ragReturnType": "chunks", - "ragK": 3, - "outputVisibility": "internal", - "controlType": "relinquish_to_parent", - "maxCallsPerParentAgent": 3 - }, - { - "name": "GitHub Stats Logging Pipeline Step 1", - "type": "pipeline", - "description": "", - "disabled": false, - "instructions": "", - "model": "gpt-4o", - "locked": false, - "toggleAble": true, - "ragReturnType": "chunks", - "ragK": 3, - "outputVisibility": "internal", - "controlType": "relinquish_to_parent", - "maxCallsPerParentAgent": 3 - }, - { - "name": "GitHub Stats Pipeline Hub", - "type": "conversation", - "description": "User-facing hub that triggers the GitHub Stats Logging Pipeline and reports when complete.", - "disabled": false, - "instructions": "## 🧑‍💼 Role:\nYou are the hub agent responsible for triggering the GitHub Stats Logging Pipeline.\n\n---\n## ⚙️ Steps to Follow:\n1. When the user requests a stats update, call [@pipeline:GitHub Stats Logging Pipeline](#mention).\n2. Wait for the pipeline to complete.\n3. Inform the user that the stats have been logged, the sheet updated, and the Slack channel notified.\n\n---\n## 📋 Guidelines:\n- Do not perform any stats fetching, sheet logging, or Slack messaging yourself.\n- Do not reference internal agent or pipeline names to the user.\n- Only interact with the user for the initial request and final confirmation.", - "model": "google/gemini-2.5-flash", - "locked": false, - "toggleAble": true, - "ragReturnType": "chunks", - "ragK": 3, - "outputVisibility": "user_facing", - "controlType": "retain", - "maxCallsPerParentAgent": 3 - } - ], - "prompts": [ - { - "name": "spreadsheetId", - "type": "base_prompt", - "prompt": "" - }, - { - "name": "range", - "type": "base_prompt", - "prompt": "" - }, - { - "name": "sheet_link", - "type": "base_prompt", - "prompt": "" - }, - { - "name": "Owner", - "type": "base_prompt", - "prompt": "" - }, - { - "name": "repo", - "type": "base_prompt", - "prompt": "" - } - ], - "tools": [ - { - "name": "Get page views", - "description": "Retrieves page view statistics for a repository over the last 14 days, including total views, unique visitors, and a daily or weekly breakdown.", - "mockTool": false, - "parameters": { - "type": "object", - "properties": { - "owner": { - "description": "The username of the account that owns the repository. This field is case-insensitive.", - "examples": [ - "octocat" - ], - "title": "Owner", - "type": "string" - }, - "per": { - "default": "day", - "description": "The time unit for which to aggregate page views.", - "enum": [ - "day", - "week" - ], - "examples": [ - "day", - "week" - ], - "title": "Per", - "type": "string" - }, - "repo": { - "description": "The name of the repository, without the `.git` extension. This field is case-insensitive.", - "examples": [ - "Spoon-Knife" - ], - "title": "Repo", - "type": "string" - } - }, - "required": [ - "owner", - "repo" - ] - }, - "isComposio": true, - "composioData": { - "slug": "GITHUB_GET_PAGE_VIEWS", - "noAuth": false, - "toolkitName": "GitHub", - "toolkitSlug": "github", - "logo": "https://cdn.jsdelivr.net/gh/ComposioHQ/open-logos@master/github.png" - } - }, - { - "name": "Append Values to Spreadsheet", - "description": "Tool to append values to a spreadsheet. use when you need to add new data to the end of an existing table in a google sheet.", - "mockTool": false, - "parameters": { - "type": "object", - "properties": { - "includeValuesInResponse": { - "default": null, - "description": "Determines if the update response should include the values of the cells that were appended. By default, responses do not include the updated values.", - "examples": [ - true - ], - "nullable": true, - "title": "Include Values In Response", - "type": "boolean" - }, - "insertDataOption": { - "default": null, - "description": "How the input data should be inserted.", - "enum": [ - "OVERWRITE", - "INSERT_ROWS" - ], - "examples": [ - "INSERT_ROWS" - ], - "nullable": true, - "title": "Insert Data Option", - "type": "string" - }, - "majorDimension": { - "default": null, - "description": "The major dimension of the values. For output, if the spreadsheet data is: A1=1,B1=2,A2=3,B2=4, then requesting range=A1:B2,majorDimension=ROWS will return [[1,2],[3,4]], whereas requesting range=A1:B2,majorDimension=COLUMNS will return [[1,3],[2,4]].", - "enum": [ - "ROWS", - "COLUMNS" - ], - "examples": [ - "ROWS" - ], - "nullable": true, - "title": "Major Dimension", - "type": "string" - }, - "range": { - "description": "The A1 notation of a range to search for a logical table of data. Values are appended after the last row of the table.", - "examples": [ - "Sheet1!A1:B2" - ], - "title": "Range", - "type": "string" - }, - "responseDateTimeRenderOption": { - "default": null, - "description": "Determines how dates, times, and durations in the response should be rendered. This is ignored if responseValueRenderOption is FORMATTED_VALUE. The default dateTime render option is SERIAL_NUMBER.", - "enum": [ - "SERIAL_NUMBER", - "FORMATTED_STRING" - ], - "examples": [ - "SERIAL_NUMBER" - ], - "nullable": true, - "title": "Response Date Time Render Option", - "type": "string" - }, - "responseValueRenderOption": { - "default": null, - "description": "Determines how values in the response should be rendered. The default render option is FORMATTED_VALUE.", - "enum": [ - "FORMATTED_VALUE", - "UNFORMATTED_VALUE", - "FORMULA" - ], - "examples": [ - "FORMATTED_VALUE" - ], - "nullable": true, - "title": "Response Value Render Option", - "type": "string" - }, - "spreadsheetId": { - "description": "The ID of the spreadsheet to update.", - "examples": [ - "1q0gLhLdGXYZblahblahblah" - ], - "title": "Spreadsheet Id", - "type": "string" - }, - "valueInputOption": { - "description": "How the input data should be interpreted.", - "enum": [ - "RAW", - "USER_ENTERED" - ], - "examples": [ - "USER_ENTERED" - ], - "title": "Value Input Option", - "type": "string" - }, - "values": { - "description": "The data to be written. This is an array of arrays, the outer array representing all the data and each inner array representing a major dimension. Each item in the inner array corresponds with one cell.", - "examples": [ - [ - [ - "A1_val1", - "A1_val2" - ], - [ - "A2_val1", - "A2_val2" - ] - ] - ], - "items": { - "items": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "integer" - }, - { - "type": "number" - }, - { - "type": "boolean" - } - ] - }, - "type": "array" - }, - "title": "Values", - "type": "array" - } - }, - "required": [ - "spreadsheetId", - "range", - "valueInputOption", - "values" - ] - }, - "isComposio": true, - "composioData": { - "slug": "GOOGLESHEETS_SPREADSHEETS_VALUES_APPEND", - "noAuth": false, - "toolkitName": "Googlesheets", - "toolkitSlug": "googlesheets", - "logo": "https://cdn.jsdelivr.net/gh/ComposioHQ/open-logos@master/google-sheets.svg" - } - }, - { - "name": "Get repository clones", - "description": "Retrieves the total number of clones and a breakdown of clone activity (daily or weekly) for a specified repository over the preceding 14 days.", - "mockTool": false, - "parameters": { - "type": "object", - "properties": { - "owner": { - "description": "The username of the account that owns the repository. This field is not case-sensitive.", - "examples": [ - "octocat", - "github" - ], - "title": "Owner", - "type": "string" - }, - "per": { - "default": "day", - "description": "Specifies the time frame for aggregating clone data: `day` for daily clone counts, or `week` for weekly clone counts (a week starts on Monday).", - "enum": [ - "day", - "week" - ], - "examples": [ - "day", - "week" - ], - "title": "Per", - "type": "string" - }, - "repo": { - "description": "The name of the repository, without the '.git' extension. This field is not case-sensitive.", - "examples": [ - "Hello-World", - "mercury" - ], - "title": "Repo", - "type": "string" - } - }, - "required": [ - "owner", - "repo" - ] - }, - "isComposio": true, - "composioData": { - "slug": "GITHUB_GET_REPOSITORY_CLONES", - "noAuth": false, - "toolkitName": "GitHub", - "toolkitSlug": "github", - "logo": "https://cdn.jsdelivr.net/gh/ComposioHQ/open-logos@master/github.png" - } - }, - { - "name": "Send a message to a Slack channel", - "description": "Deprecated: posts a message to a slack channel, direct message, or private group. use `send message` instead.", - "mockTool": false, - "parameters": { - "type": "object", - "properties": { - "as_user": { - "description": "Post as the authenticated user instead of as a bot. Defaults to `false`. If `true`, `username`, `icon_emoji`, and `icon_url` are ignored. If `false`, the message is posted as a bot, allowing appearance customization.", - "title": "As User", - "type": "boolean" - }, - "attachments": { - "description": "URL-encoded JSON array of message attachments, a legacy method for rich content. See Slack API documentation for structure.", - "examples": [ - "%5B%7B%22fallback%22%3A%20%22Required%20plain-text%20summary%20of%20the%20attachment.%22%2C%20%22color%22%3A%20%22%2336a64f%22%2C%20%22pretext%22%3A%20%22Optional%20text%20that%20appears%20above%20the%20attachment%20block%22%2C%20%22author_name%22%3A%20%22Bobby%20Tables%22%2C%20%22title%22%3A%20%22Slack%20API%20Documentation%22%2C%20%22title_link%22%3A%20%22https%3A%2F%2Fapi.slack.com%2F%22%2C%20%22text%22%3A%20%22Optional%20text%20that%20appears%20within%20the%20attachment%22%7D%5D" - ], - "title": "Attachments", - "type": "string" - }, - "blocks": { - "description": "DEPRECATED: Use `markdown_text` field instead. URL-encoded JSON array of layout blocks for rich/interactive messages. See Slack API Block Kit docs for structure.", - "examples": [ - "%5B%7B%22type%22%3A%20%22section%22%2C%20%22text%22%3A%20%7B%22type%22%3A%20%22mrkdwn%22%2C%20%22text%22%3A%20%22Hello%2C%20world%21%22%7D%7D%5D" - ], - "title": "Blocks", - "type": "string" - }, - "channel": { - "description": "ID or name of the channel, private group, or IM channel to send the message to.", - "examples": [ - "C1234567890", - "general" - ], - "title": "Channel", - "type": "string" - }, - "icon_emoji": { - "description": "Emoji for bot's icon (e.g., ':robot_face:'). Overrides `icon_url`. Applies if `as_user` is `false`.", - "examples": [ - ":tada:", - ":slack:" - ], - "title": "Icon Emoji", - "type": "string" - }, - "icon_url": { - "description": "Image URL for bot's icon (must be HTTPS). Applies if `as_user` is `false`.", - "examples": [ - "https://slack.com/img/icons/appDir_2019_01/Tonito64.png" - ], - "title": "Icon Url", - "type": "string" - }, - "link_names": { - "description": "Automatically hyperlink channel names (e.g., #channel) and usernames (e.g., @user) in message text. Defaults to `false` for bot messages.", - "title": "Link Names", - "type": "boolean" - }, - "markdown_text": { - "description": "PREFERRED: Write your message in markdown for nicely formatted display. Supports: headers (# ## ###), bold (**text** or __text__), italic (*text* or _text_), strikethrough (~~text~~), inline code (`code`), code blocks (```), links ([text](url)), block quotes (>), lists (- item, 1. item), dividers (--- or ***), context blocks (:::context with images), and section buttons (:::section-button). IMPORTANT: Use \\n for line breaks (e.g., 'Line 1\\nLine 2'), not actual newlines. USER MENTIONS: To tag users, use their user ID with <@USER_ID> format (e.g., <@U1234567890>), not username. ", - "examples": [ - "# Status Update\n\nSystem is **running smoothly** with *excellent* performance.\n\n```bash\nkubectl get pods\n```\n\n> All services operational ✅", - "## Daily Report\n\n- **Deployments**: 5 successful\n- *Issues*: 0 critical\n- ~~Maintenance~~: **Completed**\n\n---\n\n**Next**: Monitor for 24h" - ], - "title": "Markdown Text", - "type": "string" - }, - "mrkdwn": { - "description": "Disable Slack's markdown for `text` field if `false`. Default `true` (allows *bold*, _italic_, etc.).", - "title": "Mrkdwn", - "type": "boolean" - }, - "parse": { - "description": "Message text parsing behavior. Default `none` (no special parsing). `full` parses as user-typed (links @mentions, #channels). See Slack API docs for details.", - "examples": [ - "none", - "full" - ], - "title": "Parse", - "type": "string" - }, - "reply_broadcast": { - "description": "If `true` for a threaded reply, also posts to main channel. Defaults to `false`.", - "title": "Reply Broadcast", - "type": "boolean" - }, - "text": { - "description": "DEPRECATED: This sends raw text only, use markdown_text field. Primary textual content. Recommended fallback if using `blocks` or `attachments`. Supports mrkdwn unless `mrkdwn` is `false`.", - "examples": [ - "Hello from your friendly bot!", - "Reminder: Team meeting at 3 PM today." - ], - "title": "Text", - "type": "string" - }, - "thread_ts": { - "description": "Timestamp (`ts`) of an existing message to make this a threaded reply. Use `ts` of the parent message, not another reply. Example: '1476746824.000004'.", - "examples": [ - "1618033790.001500" - ], - "title": "Thread Ts", - "type": "string" - }, - "unfurl_links": { - "description": "Enable unfurling of text-based URLs. Defaults `false` for bots, `true` if `as_user` is `true`.", - "title": "Unfurl Links", - "type": "boolean" - }, - "unfurl_media": { - "description": "Disable unfurling of media content from URLs if `false`. Defaults to `true`.", - "title": "Unfurl Media", - "type": "boolean" - }, - "username": { - "description": "Bot's name in Slack (max 80 chars). Applies if `as_user` is `false`.", - "examples": [ - "MyBot", - "AlertBot" - ], - "title": "Username", - "type": "string" - } - }, - "required": [ - "channel" - ] - }, - "isComposio": true, - "composioData": { - "slug": "SLACK_SENDS_A_MESSAGE_TO_A_SLACK_CHANNEL", - "noAuth": false, - "toolkitName": "Slack", - "toolkitSlug": "slack", - "logo": "https://cdn.jsdelivr.net/gh/ComposioHQ/open-logos@master/slack.svg" - } - } - ], - "pipelines": [ - { - "name": "GitHub Stats Logging Pipeline", - "description": "Sequential pipeline to fetch GitHub stats, log them to a Google Sheet, and notify #stats Slack channel.", - "agents": [ - "Pipeline Step 1 - Fetch Views Data", - "Pipeline Step 2 - Fetch Clones Data", - "Pipeline Step 3 - Add Data to Sheet", - "Pipeline Step 4 - Send Slack Summary" - ] - } - ], - "startAgent": "GitHub Stats Pipeline Hub" + }); + } + } catch (error) { + console.warn('Failed to load prebuilt cards from directory:', error); } + + return templates; } -export const starting_copilot_prompts: { [key: string]: string } = { - "Credit Card Assistant": "Create a credit card assistant that helps users with credit card related queries like card recommendations, benefits, rewards, application process, and general credit card advice. Provide accurate and helpful information while maintaining a professional and friendly tone.", - - "Scheduling Assistant": "Create an appointment scheduling assistant that helps users schedule, modify, and manage their appointments efficiently. Help with finding available time slots, sending reminders, rescheduling appointments, and answering questions about scheduling policies and procedures. Maintain a professional and organized approach.", +export const templates: { [key: string]: z.infer } = loadTemplatesFromFiles(); - "Blog Assistant": "Create a blog writer assistant with agents for researching, compiling, outlining and writing the blog. The research agent will research the topic and compile the information. The outline agent will write bullet points for the blog post. The writing agent will expand upon the outline and write the blog post. The blog post should be 1000 words or more.", -} \ No newline at end of file +// Note: Prebuilt cards are now loaded from app/lib/prebuilt-cards/ directory +// starting_copilot_prompts has been removed as it was unused \ No newline at end of file diff --git a/apps/rowboat/app/projects/components/build-assistant-section.tsx b/apps/rowboat/app/projects/components/build-assistant-section.tsx index 5eddfb0e3..2861470cb 100644 --- a/apps/rowboat/app/projects/components/build-assistant-section.tsx +++ b/apps/rowboat/app/projects/components/build-assistant-section.tsx @@ -17,7 +17,8 @@ import { Project } from "@/src/entities/models/project"; import { z } from "zod"; import Link from 'next/link'; -const SHOW_PREBUILT_CARDS = process.env.NEXT_PUBLIC_SHOW_PREBUILT_CARDS === 'true'; +const SHOW_PREBUILT_CARDS = true; // Temporarily hardcoded for testing +// const SHOW_PREBUILT_CARDS = process.env.NEXT_PUBLIC_SHOW_PREBUILT_CARDS === 'true'; From 0472b828b3ab991d6d690303170337e08de5a546 Mon Sep 17 00:00:00 2001 From: tusharmagar Date: Mon, 8 Sep 2025 13:34:57 +0530 Subject: [PATCH 2/5] removed old jsons (still need to update the existing ones too) --- .../Financial Summary Generator.json | 183 -------------- .../app/lib/prebuilt-cards/meeting-prep.json | 237 ------------------ 2 files changed, 420 deletions(-) delete mode 100644 apps/rowboat/app/lib/prebuilt-cards/Financial Summary Generator.json delete mode 100644 apps/rowboat/app/lib/prebuilt-cards/meeting-prep.json diff --git a/apps/rowboat/app/lib/prebuilt-cards/Financial Summary Generator.json b/apps/rowboat/app/lib/prebuilt-cards/Financial Summary Generator.json deleted file mode 100644 index 1d3410647..000000000 --- a/apps/rowboat/app/lib/prebuilt-cards/Financial Summary Generator.json +++ /dev/null @@ -1,183 +0,0 @@ -{ - "agents": [ - { - "name": "Financial Summary Generator", - "type": "conversation", - "description": "Processes parsed quarterly earnings reports (e.g., SEC Form 10-Q), generates a universal one-page financial summary in strict format, and creates a new Google Doc with the summary.", - "instructions": "## 🧑‍💼 Role:\nYou are a financial analysis agent that generates a universal one-page financial summary from quarterly earnings reports (typically SEC Form 10-Q or equivalent) provided as unstructured PDF documents in the Finance Document data source. You must extract all required information by first converting the PDF to text using the Convert PDF to Text tool, then parsing the text for the necessary details. Never ask the user for company name, ticker, period end date, or any other information that can be found in the PDF.\n\n---\n## ⚙️ Steps to Follow:\n1. When the user requests a summary, use the [@tool:Convert PDF to Text](#mention) tool to extract the full text from the PDF in the Finance Document data source. (If there are multiple PDFs, process each one in turn.)\n2. Parse the extracted text to find all required values for each section:\n - Income Statement fields (e.g., total revenue, gross profit, operating income, net income, earnings per share)\n - Balance Sheet fields (e.g., total assets, total liabilities, shareholders’ equity, cash and cash equivalents)\n - Cash Flow fields (e.g., operating cash flow, investing cash flow, financing cash flow)\n - Key Ratios, Management Commentary, and Notable Events (e.g., gross margin, operating margin, net margin, debt-to-equity, management discussion, notable events, disclosures)\n3. For each required value, if it cannot be found in the extracted text, use \"Not disclosed\".\n4. Never fabricate, infer, or guess values. Only use information explicitly found in the extracted text.\n5. Generate a JSON object with this structure:\n{\n \"document_title\": \"[Generated Title]\",\n \"content\": \"[Formatted Financial Summary Below]\"\n}\n6. The document_title must follow this structure:\n [Company Name] – Q[Quarter Number] [Year] Financial Summary\n - Use the fiscal period end date in the report to determine the quarter and year (e.g., March 31 = Q1).\n7. The content field must follow this exact summary format:\n\nCompany Name: [Name]\nTicker Symbol: [Ticker]\nReport Period Ending: [Date]\nFiling Type: [e.g., 10-Q]\nFiling Date: [Date]\n\n1. Income Statement Summary:\n - Total Revenue: $[value]\n - Gross Profit: $[value]\n - Operating Income: $[value]\n - Net Income: $[value]\n - Earnings Per Share (Basic): $[value]\n - Earnings Per Share (Diluted): $[value]\n\n2. Balance Sheet Highlights:\n - Total Assets: $[value]\n - Total Liabilities: $[value]\n - Shareholders’ Equity: $[value]\n - Cash and Cash Equivalents: $[value]\n\n3. Cash Flow Summary:\n - Operating Cash Flow: $[value]\n - Investing Cash Flow: $[value]\n - Financing Cash Flow: $[value]\n\n4. Key Financial Ratios (if provided or derivable):\n - Gross Margin: [value or “Not disclosed”]\n - Operating Margin: [value or “Not disclosed”]\n - Net Margin: [value or “Not disclosed”]\n - Debt-to-Equity Ratio: [value or “Not disclosed”]\n\n5. Management Commentary (Summary):\n - [2–3 sentence summary of any forward-looking statements or business highlights from the “Management’s Discussion and Analysis” section.]\n\n6. Notable Events or Disclosures:\n - [E.g., acquisitions, legal risks, restatements, or changes in accounting treatment.]\n\nGenerated based solely on the provided PDF document(s) using Convert PDF to Text. No external data or assumptions included.\n\n8. Use the [@tool:Create Document Markdown](#mention) tool to create a new Google Doc with the generated title and the formatted summary as markdown.\n9. Return the link to the created Google Doc to the user.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Extracting and summarizing financial data from unstructured quarterly reports by converting PDF to text.\n- Generating a strict JSON summary and document title.\n- Creating a new Google Doc with the summary.\n\n❌ Out of Scope:\n- Using any data not present in the PDF document.\n- Fabricating or guessing values.\n- Expecting a pre-parsed or structured input.\n- Asking the user for information that can be found in the PDF document.\n- Answering questions outside the scope of financial summary generation.\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- For every required field, always parse the extracted PDF text.\n- Always follow the strict output format and behavior rules.\n- Use “Not disclosed” for missing data.\n- Use U.S. dollars unless otherwise specified in the document.\n- Only use information from the PDF document.\n- Always return a valid JSON object with the exact structure specified.\n\n🚫 Don'ts:\n- Do not use external data or assumptions.\n- Do not fabricate or guess values.\n- Do not expect a pre-parsed document.\n- Do not ask the user for company name, ticker, period end date, or any other information that can be found in the PDF document.\n- Do not deviate from the required format.\n\n---\n## Data Source\nYou have access to the following data source:\n- Finance Document: PDF documents for parsing\nCall [@tool:Convert PDF to Text](#mention) to extract the text from the PDF before answering any questions on it.\n\n# Examples\n- **User** : Hi send me my read up!\n - **Agent actions**: Use [@tool:Convert PDF to Text](#mention) to extract text from the PDF. Parse all required values from the text. Use “Not disclosed” if missing. Generate the JSON summary and document title. Call [@tool:Create Document Markdown](#mention) with the title and formatted summary. Return the Google Doc link.\n\n- **User** : The report is missing some values.\n - **Agent actions**: Use “Not disclosed” for any missing values. Continue with the summary and document creation.\n\n- **User** : Can you add information from Yahoo Finance?\n - **Agent response**: Sorry, I can only use information found in the provided PDF document. No external data or assumptions are included.\n\n- **User** : Please summarize this 10-Q for Widget Inc. (period ending June 30, 2025).\n - **Agent actions**: Use [@tool:Convert PDF to Text](#mention) to extract text from the PDF. Parse all required values from the text. Use “Not disclosed” if missing. Generate the JSON summary and document title. Call [@tool:Create Document Markdown](#mention) with the title and formatted summary. Return the Google Doc link.\n\n- **User** : Can you guess the net margin?\n - **Agent response**: Sorry, I cannot fabricate or guess values. If the net margin is not disclosed in the report, I will indicate “Not disclosed.”", - "examples": "\n", - "model": "google/gemini-2.5-flash", - "toggleAble": true, - "ragDataSources": [], - "ragReturnType": "chunks", - "ragK": 3, - "outputVisibility": "user_facing", - "controlType": "retain" - } - ], - "prompts": [], - "tools": [ - { - "name": "Create Document Markdown", - "description": "Creates a new google docs document, optionally initializing it with a title and content provided as markdown text.", - "mockTool": false, - "parameters": { - "type": "object", - "properties": { - "markdown_text": { - "description": "The initial content for the document, formatted as Markdown. Supports various Markdown elements including headings, lists (nested lists are not supported), tables, images (via publicly accessible URLs), blockquotes, code blocks, and text formatting (bold, italic, etc.). If an empty string is provided, the document will be created with only the title.", - "title": "Markdown Text", - "type": "string" - }, - "title": { - "description": "The title for the new Google Docs document.", - "examples": [ - "Meeting Notes Q3", - "Project Alpha Proposal", - "My New Story Draft" - ], - "title": "Title", - "type": "string" - } - }, - "required": [ - "title", - "markdown_text" - ] - }, - "isComposio": true, - "composioData": { - "slug": "GOOGLEDOCS_CREATE_DOCUMENT_MARKDOWN", - "noAuth": false, - "toolkitName": "googledocs", - "toolkitSlug": "googledocs", - "logo": "https://cdn.jsdelivr.net/gh/ComposioHQ/open-logos@master/google-docs.svg" - } - }, - { - "name": "Convert PDF to Text", - "description": "Tool to convert pdf or scanned images to plain text. use when you need raw text output preserving layout.", - "mockTool": false, - "parameters": { - "type": "object", - "properties": { - "callback": { - "default": null, - "description": "Webhook URL to receive callback when async=true.", - "nullable": true, - "title": "Callback", - "type": "string" - }, - "expiration": { - "default": 60, - "description": "Expiration time in minutes for the output file link.", - "minimum": 1, - "nullable": true, - "title": "Expiration", - "type": "integer" - }, - "httppassword": { - "default": null, - "description": "HTTP auth password for source URL.", - "nullable": true, - "title": "Httppassword", - "type": "string" - }, - "httpusername": { - "default": null, - "description": "HTTP auth username for source URL.", - "nullable": true, - "title": "Httpusername", - "type": "string" - }, - "inline": { - "default": false, - "description": "Return text inline in response instead of URL.", - "nullable": true, - "title": "Inline", - "type": "boolean" - }, - "lang": { - "default": "eng", - "description": "OCR language(s) code, e.g. 'eng', or 'eng+deu'.", - "nullable": true, - "title": "Lang", - "type": "string" - }, - "lineGrouping": { - "default": null, - "description": "Line grouping mode: '1', '2', or '3'.", - "nullable": true, - "title": "Line Grouping", - "type": "string" - }, - "name": { - "default": null, - "description": "Desired name for the output file (e.g. 'result.txt').", - "nullable": true, - "title": "Name", - "type": "string" - }, - "pages": { - "default": null, - "description": "Comma-separated page indices or ranges to process (e.g. '0,2-4').", - "nullable": true, - "title": "Pages", - "type": "string" - }, - "password": { - "default": null, - "description": "Password for protected PDF files.", - "nullable": true, - "title": "Password", - "type": "string" - }, - "rect": { - "default": null, - "description": "Extraction rectangle in format '{x} {y} {width} {height}'.", - "nullable": true, - "title": "Rect", - "type": "string" - }, - "run_async": { - "default": false, - "description": "Run process asynchronously; returns jobId if true.", - "nullable": true, - "title": "Run Async", - "type": "boolean" - }, - "unwrap": { - "default": false, - "description": "Unwrap lines into a single line within table cells (only when lineGrouping='1').", - "nullable": true, - "title": "Unwrap", - "type": "boolean" - }, - "url": { - "description": "URL to the source PDF or image file.", - "examples": [ - "https://example.com/sample.pdf" - ], - "title": "Url", - "type": "string" - } - }, - "required": [ - "url" - ] - }, - "isComposio": true, - "composioData": { - "slug": "PDF_CO_PDF_TO_TEXT", - "noAuth": false, - "toolkitName": "pdf_co", - "toolkitSlug": "pdf_co", - "logo": "some_logo_url" - } - } - ], - "startAgent": "Financial Summary Generator", - "lastUpdatedAt": "2025-08-25T11:30:38.878Z", - "name": "Financial Summary Generator", - "description": "Generate a universal one-page financial summary from quarterly earnings reports (e.g., SEC Form 10-Q), generates a universal one-page financial summary in strict format, and creates a new Google Doc with the summary." -} \ No newline at end of file diff --git a/apps/rowboat/app/lib/prebuilt-cards/meeting-prep.json b/apps/rowboat/app/lib/prebuilt-cards/meeting-prep.json deleted file mode 100644 index 860a24f12..000000000 --- a/apps/rowboat/app/lib/prebuilt-cards/meeting-prep.json +++ /dev/null @@ -1,237 +0,0 @@ -{ - "agents": [ - { - "name": "Research Agent", - "type": "pipeline", - "description": "Internal agent that researches meeting attendees and returns a compiled summary.", - "instructions": "## Role\nYou are a pipeline agent that researches meeting attendees.\n\n---\n## Task\n1. You will receive attendee details from a previous step.\n2. For each attendee, you **must** research them **one at a time** using the [@tool:Search](#mention). Do NOT research the user `{{Exclude user}}`!\n3. After all searches are complete, compile the findings into a single, plain text summary.\n4. If no information is found for an attendee, state \"No public information found.\" for that person.\n5. Return **only** the final compiled summary.\n\n---\n## Constraint\nDo **NOT** interact with users or send messages. Your only output is the final summary text.", - "model": "google/gemini-2.5-flash", - "toggleAble": true, - "ragReturnType": "chunks", - "ragK": 3, - "controlType": "relinquish_to_parent", - "outputVisibility": "internal", - "maxCallsPerParentAgent": 3 - }, - { - "name": "Slack Send Agent", - "type": "pipeline", - "description": "Internal agent that sends the compiled research summary to a channel via Slack direct message and returns confirmation.", - "disabled": false, - "instructions": "## Role\nYou are a pipeline agent that sends a research summary to a Slack channel.\n\n---\n## Task\n1. You will receive a compiled text summary from the previous step.\n2. Use the [@tool:Send message](#mention) tool to post this summary, using these parameters:\n * **channel**: `{{Slack Channel}}`\n * **markdown_text**: Create a message starting with the subject \"*Meeting Attendee Research Summary*\", followed by the summary text you received.\n3. Your job is complete after sending the message.\n\n---\n## Constraint\nDo **NOT** perform any action other than sending the Slack message as instructed.", - "model": "google/gemini-2.5-flash", - "locked": false, - "toggleAble": true, - "ragReturnType": "chunks", - "ragK": 3, - "controlType": "relinquish_to_parent", - "outputVisibility": "internal", - "maxCallsPerParentAgent": 3, - "examples": "- **Parent agent** : \n - **Agent actions**: Call [@tool:Send message](#mention)\n - **Agent response**: Message sent to Slack channel.\n" - }, - { - "name": "Attendee Research & Slack Pipeline Step 1", - "type": "pipeline", - "description": "", - "disabled": false, - "instructions": "", - "model": "gpt-4o", - "locked": false, - "toggleAble": true, - "ragReturnType": "chunks", - "ragK": 3, - "controlType": "relinquish_to_parent", - "outputVisibility": "internal", - "maxCallsPerParentAgent": 3 - } - ], - "prompts": [ - { - "name": "Slack Channel", - "type": "base_prompt", - "prompt": "" - }, - { - "name": "Exclude user", - "type": "base_prompt", - "prompt": "" - } - ], - "tools": [ - { - "name": "Search", - "description": "Performs a web search and scrapes content from the top results.", - "parameters": { - "type": "object", - "properties": { - "query": { - "type": "string", - "description": "The search query." - } - }, - "required": [ - "query" - ] - }, - "mockTool": true, - "isComposio": true, - "composioData": { - "slug": "FIRECRWAL_SEARCH", - "noAuth": false, - "toolkitName": "firecrawl", - "toolkitSlug": "firecrawl", - "logo": "https://cdn.jsdelivr.net/gh/ComposioHQ/open-logos@master/firecrawl.svg" - } - }, - { - "name": "Send message", - "description": "Posts a message to a slack channel, direct message, or private group; requires content via `text`, `blocks`, or `attachments`.", - "parameters": { - "type": "object", - "properties": { - "as_user": { - "description": "Post as the authenticated user instead of as a bot. Defaults to `false`. If `true`, `username`, `icon_emoji`, and `icon_url` are ignored. If `false`, the message is posted as a bot, allowing appearance customization.", - "title": "As User", - "type": "boolean" - }, - "attachments": { - "description": "URL-encoded JSON array of message attachments, a legacy method for rich content. See Slack API documentation for structure.", - "examples": [ - "%5B%7B%22fallback%22%3A%20%22Required%20plain-text%20summary%20of%20the%20attachment.%22%2C%20%22color%22%3A%20%22%2336a64f%22%2C%20%22pretext%22%3A%20%22Optional%20text%20that%20appears%20above%20the%20attachment%20block%22%2C%20%22author_name%22%3A%20%22Bobby%20Tables%22%2C%20%22title%22%3A%20%22Slack%20API%20Documentation%22%2C%20%22title_link%22%3A%20%22https%3A%2F%2Fapi.slack.com%2F%22%2C%20%22text%22%3A%20%22Optional%20text%20that%20appears%20within%20the%20attachment%22%7D%5D" - ], - "title": "Attachments", - "type": "string" - }, - "blocks": { - "description": "DEPRECATED: Use `markdown_text` field instead. URL-encoded JSON array of layout blocks for rich/interactive messages. See Slack API Block Kit docs for structure.", - "examples": [ - "%5B%7B%22type%22%3A%20%22section%22%2C%20%22text%22%3A%20%7B%22type%22%3A%20%22mrkdwn%22%2C%20%22text%22%3A%20%22Hello%2C%20world%21%22%7D%7D%5D" - ], - "title": "Blocks", - "type": "string" - }, - "channel": { - "description": "ID or name of the channel, private group, or IM channel to send the message to.", - "examples": [ - "C1234567890", - "general" - ], - "title": "Channel", - "type": "string" - }, - "icon_emoji": { - "description": "Emoji for bot's icon (e.g., ':robot_face:'). Overrides `icon_url`. Applies if `as_user` is `false`.", - "examples": [ - ":tada:", - ":slack:" - ], - "title": "Icon Emoji", - "type": "string" - }, - "icon_url": { - "description": "Image URL for bot's icon (must be HTTPS). Applies if `as_user` is `false`.", - "examples": [ - "https://slack.com/img/icons/appDir_2019_01/Tonito64.png" - ], - "title": "Icon Url", - "type": "string" - }, - "link_names": { - "description": "Automatically hyperlink channel names (e.g., #channel) and usernames (e.g., @user) in message text. Defaults to `false` for bot messages.", - "title": "Link Names", - "type": "boolean" - }, - "markdown_text": { - "description": "PREFERRED: Write your message in markdown for nicely formatted display. Supports: headers (# ## ###), bold (**text** or __text__), italic (*text* or _text_), strikethrough (~~text~~), inline code (`code`), code blocks (```), links ([text](url)), block quotes (>), lists (- item, 1. item), dividers (--- or ***), context blocks (:::context with images), and section buttons (:::section-button). IMPORTANT: Use \\n for line breaks (e.g., 'Line 1\\nLine 2'), not actual newlines. USER MENTIONS: To tag users, use their user ID with <@USER_ID> format (e.g., <@U1234567890>), not username. ", - "examples": [ - "# Status Update\\n\\nSystem is **running smoothly** with *excellent* performance.\\n\\n```bash\\nkubectl get pods\\n```\\n\\n> All services operational ✅", - "## Daily Report\\n\\n- **Deployments**: 5 successful\\n- *Issues*: 0 critical\\n- ~~Maintenance~~: **Completed**\\n\\n---\\n\\n**Next**: Monitor for 24h" - ], - "title": "Markdown Text", - "type": "string" - }, - "mrkdwn": { - "description": "Disable Slack's markdown for `text` field if `false`. Default `true` (allows *bold*, _italic_, etc.).", - "title": "Mrkdwn", - "type": "boolean" - }, - "parse": { - "description": "Message text parsing behavior. Default `none` (no special parsing). `full` parses as user-typed (links @mentions, #channels). See Slack API docs for details.", - "examples": [ - "none", - "full" - ], - "title": "Parse", - "type": "string" - }, - "reply_broadcast": { - "description": "If `true` for a threaded reply, also posts to main channel. Defaults to `false`.", - "title": "Reply Broadcast", - "type": "boolean" - }, - "text": { - "description": "DEPRECATED: This sends raw text only, use markdown_text field. Primary textual content. Recommended fallback if using `blocks` or `attachments`. Supports mrkdwn unless `mrkdwn` is `false`.", - "examples": [ - "Hello from your friendly bot!", - "Reminder: Team meeting at 3 PM today." - ], - "title": "Text", - "type": "string" - }, - "thread_ts": { - "description": "Timestamp (`ts`) of an existing message to make this a threaded reply. Use `ts` of the parent message, not another reply. Example: '1476746824.000004'.", - "examples": [ - "1618033790.001500" - ], - "title": "Thread Ts", - "type": "string" - }, - "unfurl_links": { - "description": "Enable unfurling of text-based URLs. Defaults `false` for bots, `true` if `as_user` is `true`.", - "title": "Unfurl Links", - "type": "boolean" - }, - "unfurl_media": { - "description": "Disable unfurling of media content from URLs if `false`. Defaults to `true`.", - "title": "Unfurl Media", - "type": "boolean" - }, - "username": { - "description": "Bot's name in Slack (max 80 chars). Applies if `as_user` is `false`.", - "examples": [ - "MyBot", - "AlertBot" - ], - "title": "Username", - "type": "string" - } - }, - "required": [ - "channel" - ] - }, - "mockTool": true, - "isComposio": true, - "composioData": { - "slug": "SLACK_SEND_MESSAGE", - "noAuth": false, - "toolkitName": "slack", - "toolkitSlug": "slack", - "logo": "https://cdn.jsdelivr.net/gh/ComposioHQ/open-logos@master/slack.svg" - } - } - ], - "startAgent": "Attendee Research & Slack Pipeline", - "pipelines": [ - { - "name": "Attendee Research & Slack Pipeline", - "description": "Pipeline that researches meeting attendees and sends the compiled summary to a specified Slack channel.", - "agents": [ - "Research Agent", - "Slack Send Agent" - ] - } - ], - "name": "Meeting Prep", - "description": "Research meeting attendees and send summary to Slack" -} From bbd5b7aa5de02a1af6b9e0f0c40832dbb673fba3 Mon Sep 17 00:00:00 2001 From: tusharmagar Date: Mon, 8 Sep 2025 15:15:32 +0530 Subject: [PATCH 3/5] Refactor project templates to remove the Example Agent and update related documentation. Updated the copilot behaviour aswell. --- apps/rowboat/app/lib/project_templates.ts | 17 ++--------------- .../application/lib/copilot/current_workflow.ts | 2 +- .../lib/copilot/example_multi_agent_1.ts | 14 +++++++------- 3 files changed, 10 insertions(+), 23 deletions(-) diff --git a/apps/rowboat/app/lib/project_templates.ts b/apps/rowboat/app/lib/project_templates.ts index 4dbfa310b..774e2ca87 100644 --- a/apps/rowboat/app/lib/project_templates.ts +++ b/apps/rowboat/app/lib/project_templates.ts @@ -18,21 +18,8 @@ function loadTemplatesFromFiles(): { [key: string]: z.infer Date: Mon, 8 Sep 2025 15:19:48 +0530 Subject: [PATCH 4/5] Merge remote-tracking branch 'origin/dev' into prebuiltcards From 340fd015d9c0fe010afcf077913e54f20e6e9be3 Mon Sep 17 00:00:00 2001 From: tusharmagar Date: Mon, 8 Sep 2025 15:54:10 +0530 Subject: [PATCH 5/5] Update SHOW_PREBUILT_CARDS logic to use environment variable for better configuration management --- .../app/projects/components/build-assistant-section.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/rowboat/app/projects/components/build-assistant-section.tsx b/apps/rowboat/app/projects/components/build-assistant-section.tsx index 2861470cb..8fa1250b1 100644 --- a/apps/rowboat/app/projects/components/build-assistant-section.tsx +++ b/apps/rowboat/app/projects/components/build-assistant-section.tsx @@ -17,8 +17,7 @@ import { Project } from "@/src/entities/models/project"; import { z } from "zod"; import Link from 'next/link'; -const SHOW_PREBUILT_CARDS = true; // Temporarily hardcoded for testing -// const SHOW_PREBUILT_CARDS = process.env.NEXT_PUBLIC_SHOW_PREBUILT_CARDS === 'true'; +const SHOW_PREBUILT_CARDS = process.env.NEXT_PUBLIC_SHOW_PREBUILT_CARDS !== 'false';