-
Notifications
You must be signed in to change notification settings - Fork 3
Add HubSpot CRM skill example #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Kurume98
wants to merge
3
commits into
lua-ai-global:master
Choose a base branch
from
Kurume98:master
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,157 @@ | ||
| # HubspotCRMSkills | ||
| # ⚡ HubSpot CRM Skill — Lua AI Agent | ||
|
|
||
| A lightweight Lua AI Agent skill that allows your agent to **create/update contacts in HubSpot CRM** using a secure Private App token. | ||
|
|
||
| ## 🚀 Quick Start | ||
|
|
||
| ```bash | ||
| # Install dependencies | ||
| npm install | ||
|
|
||
| # Build the project | ||
| npm run build | ||
|
|
||
| # Start interactive sandbox chat | ||
| lua chat | ||
|
|
||
| # Push skill to server | ||
| lua push | ||
|
|
||
| # Deploy to production (only when ready) | ||
| lua push --deploy | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## 📁 Project Structure | ||
|
|
||
| ``` | ||
| hubspot-skill/ | ||
| ├── src/ | ||
| │ ├── index.ts # Agent configuration | ||
| │ ├── skills/ | ||
| │ │ ├── hubspotCRM.skill.ts # Skill definition | ||
| │ │ └── createContact.ts # Tool: create HubSpot contact | ||
| ├── .env.example # Environment variable template | ||
| ├── lua.skill.yaml # Auto-generated agent metadata | ||
| ├── package.json # Project config | ||
| ├── tsconfig.json # TypeScript settings | ||
| └── README.md # This file | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## 🔧 Environment Variables | ||
|
|
||
| Create a `.env` file: | ||
|
|
||
| ``` | ||
| HUBSPOT_PRIVATE_APP_TOKEN=your-token-here | ||
| HUBSPOT_API_BASE_URL=https://api.hubapi.com | ||
| ``` | ||
|
|
||
| Set in production using: | ||
|
|
||
| ```bash | ||
| lua env production | ||
| # ➕ Add new variable: | ||
| # Name: HUBSPOT_PRIVATE_APP_TOKEN | ||
| # Value: <your-token> | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## 🛠️ Tool: Create Contact | ||
|
|
||
| This project includes **1 tool**: | ||
|
|
||
| ### `createContact` | ||
| Creates a new contact in HubSpot CRM using your private app token. | ||
|
|
||
| Input fields: | ||
| - `email` *(optional but recommended)* | ||
| - `firstname` | ||
| - `lastname` | ||
| - `phone` *(optional)* | ||
| - `company` *(optional)* | ||
|
|
||
| Example prompt in lua chat: | ||
|
|
||
| ``` | ||
| Create a contact with email test@example.com, firstname Test, lastname User. | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## 🧠 Skill: HubSpot CRM | ||
|
|
||
| The skill groups all HubSpot-related tools and gives your agent the ability to talk to HubSpot's CRM API. | ||
|
|
||
| Location: | ||
| ``` | ||
| src/skills/hubspotCRM.skill.ts | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## 🤖 Agent Configuration | ||
|
|
||
| Located in: | ||
| ``` | ||
| src/index.ts | ||
| ``` | ||
|
|
||
| Defines: | ||
| - Agent name | ||
| - Persona | ||
| - Skills registered | ||
| - Runtime behavior | ||
|
|
||
| You can test locally using: | ||
|
|
||
| ```bash | ||
| lua chat | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## 🧪 Testing | ||
|
|
||
| ### Test only the tool: | ||
| ```bash | ||
| lua test | ||
| ``` | ||
|
|
||
| ### Test entire agent (sandbox): | ||
| ```bash | ||
| lua chat | ||
| ``` | ||
|
|
||
| ### Test production after deployment: | ||
| ```bash | ||
| lua chat | ||
| # Select 🚀 Production | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## 🛡️ Best Practices | ||
|
|
||
| 1. **Never commit your `.env` file to GitHub** | ||
| 2. Keep each tool focused on one task | ||
| 3. Test in sandbox before pushing to production | ||
| 4. Ensure your HubSpot private app has the following scopes: | ||
| - `crm.objects.contacts.write` | ||
| - `crm.objects.contacts.read` | ||
|
|
||
| --- | ||
|
|
||
| ## 💬 Support | ||
|
|
||
| - Lua Docs: https://docs.heylua.ai | ||
| - HubSpot API Docs: https://developers.hubspot.com/docs/api/overview | ||
|
|
||
| --- | ||
|
|
||
| *Skill created using **Lua CLI** and the HubSpot CRM API.* | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| { | ||
|
Kurume98 marked this conversation as resolved.
|
||
| "$schema": "https://json.schemastore.org/package.json", | ||
| "name": "hubspot-skill", | ||
| "version": "1.0.0", | ||
| "scripts": { | ||
| "dev": "lua dev", | ||
| "build": "tsc", | ||
| "compile": "lua compile" | ||
| }, | ||
| "dependencies": { | ||
| "lua-cli": "^1.0.0", | ||
|
Kurume98 marked this conversation as resolved.
Outdated
|
||
| "node-fetch": "^2.6.7", | ||
|
Kurume98 marked this conversation as resolved.
Outdated
|
||
| "zod": "^3.23.2" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/node": "^25.0.0", | ||
| "tsx": "^3.12.7", | ||
| "typescript": "^4.9.5" | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| // src/index.ts | ||
| // DO NOT import runtime classes from "lua-cli". | ||
| // Lua CLI injects real runtime versions at bundle/test/run time. | ||
|
|
||
| declare const LuaAgent: any; | ||
|
Kurume98 marked this conversation as resolved.
Outdated
|
||
|
|
||
| import hubspotCRM from "./skills/hubspotCRM.skill"; | ||
|
|
||
| const agent = new LuaAgent({ | ||
| name: "Hubspot-crm-skill-agent", | ||
| persona: | ||
| "Sophie Connect - Lead CRM Integration Specialist. Builds and operates CRM integrations and semantic middleware for context-aware routing.", | ||
| skills: [hubspotCRM], // <-- this is correct; makes the skill visible | ||
| }); | ||
|
|
||
| async function main() { | ||
| console.log("Agent configured"); | ||
| } | ||
|
|
||
| main().catch(console.error); | ||
|
|
||
| export default agent; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| // src/skills/createContact.ts | ||
| import fetch from "node-fetch"; | ||
|
Kurume98 marked this conversation as resolved.
Outdated
|
||
|
|
||
| export type CreateInput = { | ||
| email?: string; | ||
| firstname?: string; | ||
| lastname?: string; | ||
| phone?: string; | ||
| company?: string; | ||
| }; | ||
|
|
||
| const HUBSPOT_BASE = | ||
| (process.env.HUBSPOT_API_BASE_URL || "https://api.hubapi.com").replace(/\/+$/, ""); | ||
|
|
||
| const TOKEN = process.env.HUBSPOT_PRIVATE_APP_TOKEN; | ||
|
Kurume98 marked this conversation as resolved.
Outdated
|
||
|
|
||
| export default async function createContact(input: CreateInput) { | ||
| if (!TOKEN) { | ||
|
Kurume98 marked this conversation as resolved.
Outdated
|
||
| return { | ||
| ok: false, | ||
| error: "Missing HUBSPOT_PRIVATE_APP_TOKEN in environment.", | ||
| }; | ||
| } | ||
|
|
||
| const properties: Record<string, any> = {}; | ||
|
|
||
| if (input.email) properties.email = input.email; | ||
| if (input.firstname) properties.firstname = input.firstname; | ||
| if (input.lastname) properties.lastname = input.lastname; | ||
| if (input.phone) properties.phone = input.phone; | ||
| if (input.company) properties.company = input.company; | ||
|
|
||
| if (Object.keys(properties).length === 0) { | ||
| return { ok: false, error: "No properties provided." }; | ||
| } | ||
|
|
||
| const url = `${HUBSPOT_BASE}/crm/v3/objects/contacts`; | ||
|
|
||
| try { | ||
| const res = await fetch(url, { | ||
| method: "POST", | ||
| headers: { | ||
| Authorization: `Bearer ${TOKEN}`, | ||
| "Content-Type": "application/json", | ||
| Accept: "application/json", | ||
| }, | ||
| body: JSON.stringify({ properties }), | ||
| }); | ||
|
|
||
| const body: any = await res.json(); | ||
|
|
||
| if (!res.ok) { | ||
| return { | ||
| ok: false, | ||
| status: res.status, | ||
| error: body?.message ?? JSON.stringify(body), | ||
| }; | ||
| } | ||
|
|
||
| return { | ||
| ok: true, | ||
| id: body?.id, | ||
| properties: body?.properties ?? {}, | ||
| raw: body, | ||
| }; | ||
| } catch (err: any) { | ||
| return { ok: false, error: err.message ?? String(err) }; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| // src/skills/hubspotCRM.skill.ts | ||
| import { z } from "zod"; | ||
| import createContact from "./createContact"; | ||
|
|
||
| /** | ||
| * Do NOT import runtime classes from "lua-cli" at compile-time. | ||
| * The CLI injects real runtime objects at bundling/execution time. | ||
| * | ||
| * Provide *type* aliases for compile-time only so `implements` works | ||
| * without TS confusing a runtime value with a type. | ||
| */ | ||
| export type LuaToolType = any; | ||
|
Kurume98 marked this conversation as resolved.
Outdated
|
||
| export type LuaSkillType = any; | ||
|
|
||
| /** | ||
| * Top-level exported zod schema (stable name) so the bundler can extract it. | ||
| * Must be an exported const with the exact name referenced below. | ||
| */ | ||
| export const createContactInputSchema = z.object({ | ||
| email: z.string().email().optional(), | ||
| firstname: z.string().optional(), | ||
| lastname: z.string().optional(), | ||
| phone: z.string().optional(), | ||
| company: z.string().optional(), | ||
| }); | ||
|
|
||
| class CreateContactTool implements LuaToolType { | ||
|
Kurume98 marked this conversation as resolved.
Outdated
|
||
| name = "createContact"; | ||
| description = "Creates a new contact in HubSpot CRM"; | ||
|
|
||
| // Must reference the SAME top-level exported zod const name above. | ||
| inputSchema = createContactInputSchema; | ||
|
|
||
| async execute(input: any) { | ||
| // zod parsing keeps runtime safe | ||
| const parsed = input ? this.inputSchema.parse(input) : {}; | ||
| return await createContact(parsed); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * NOTE: do NOT import `LuaSkill` from lua-cli at compile time. | ||
| * Use the `LuaSkill` type alias above only for typing. | ||
| * | ||
| * The CLI will inject the real `LuaSkill` value at runtime. | ||
| */ | ||
| const hubspotCRM = new (((globalThis as any).LuaSkill) || (class {}))({ | ||
|
Kurume98 marked this conversation as resolved.
Outdated
|
||
| // the CLI replaces this at runtime — but this way TypeScript compiles | ||
| // If the runtime doesn't provide LuaSkill here, the CLI's bundling will | ||
| // instantiate a real LuaSkill when running inside the Lua runtime. | ||
| name: "hubspotCRM", | ||
| description: "Skill for creating contacts in HubSpot CRM", | ||
| context: "Use createContact to create a new contact in HubSpot. Provide email or firstname+lastname.", | ||
|
Kurume98 marked this conversation as resolved.
Outdated
|
||
| tools: [new CreateContactTool()], | ||
| }) as any; | ||
|
|
||
| /** | ||
| * Export default must be a valid value the bundler can find. | ||
| * If the runtime/cli will inject LuaSkill, the CLI will handle wiring. | ||
| */ | ||
| export default hubspotCRM; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| // src/types/lua-cli.d.ts | ||
|
Kurume98 marked this conversation as resolved.
Outdated
|
||
| declare module "lua-cli" { | ||
| // runtime objects provided by the lua CLI - typed as `any` for compile-time safety | ||
| export const LuaAgent: any; | ||
| export const LuaSkill: any; | ||
| export const LuaTool: any; | ||
|
|
||
| // if you import a default or named, add them too: | ||
| const _default: any; | ||
| export default _default; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| { | ||
| "compilerOptions": { | ||
| "target": "ES2020", | ||
| "module": "ESNext", | ||
| "moduleResolution": "node", | ||
| "lib": ["ES2020", "DOM"], | ||
| "esModuleInterop": true, | ||
| "allowSyntheticDefaultImports": true, | ||
| "resolveJsonModule": true, | ||
| "forceConsistentCasingInFileNames": true, | ||
| "strict": true, | ||
| "skipLibCheck": true, | ||
| "baseUrl": ".", | ||
| "outDir": "./dist", | ||
| "types": ["node"] | ||
| }, | ||
| "exclude": ["dist", "node_modules"] | ||
| } |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.