[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-ac45fac9-de72-4c78-80d1-f46544433597":3,"$fs4SBXJfpznopg9TnBUbN_-wh1PcwfHkWq5I8ywaAb3E":43},{"id":4,"title":5,"description":6,"categoryId":7,"moduleId":8,"tags":9,"prompt":10,"icon":11,"source":12,"sourceUrl":13,"authorId":14,"authorName":15,"isPublic":16,"stars":17,"runs":18,"createdAt":19,"updatedAt":19,"module":20,"category":27,"packages":34},"ac45fac9-de72-4c78-80d1-f46544433597","hubspot-integration","HubSpot CRM集成专家模式包括OAuth","cat_writing_copywriting","mod_writing","sickn33,writing","---\nname: hubspot-integration\ndescription: Expert patterns for HubSpot CRM integration including OAuth\n  authentication, CRM objects, associations, batch operations, webhooks, and\n  custom objects. Covers Node.js and Python SDKs.\nrisk: unknown\nsource: vibeship-spawner-skills (Apache 2.0)\ndate_added: 2026-02-27\n---\n\n# HubSpot Integration\n\nExpert patterns for HubSpot CRM integration including OAuth authentication,\nCRM objects, associations, batch operations, webhooks, and custom objects.\nCovers Node.js and Python SDKs.\n\n## Patterns\n\n### OAuth 2.0 Authentication\n\nSecure authentication for public apps\n\n**When to use**: Building public app or multi-account integration\n\n### Template\n\n\u002F\u002F OAuth 2.0 flow for HubSpot\nimport { Client } from \"@hubspot\u002Fapi-client\";\n\n\u002F\u002F Environment variables\nconst CLIENT_ID = process.env.HUBSPOT_CLIENT_ID;\nconst CLIENT_SECRET = process.env.HUBSPOT_CLIENT_SECRET;\nconst REDIRECT_URI = process.env.HUBSPOT_REDIRECT_URI;\nconst SCOPES = \"crm.objects.contacts.read crm.objects.contacts.write\";\n\n\u002F\u002F Step 1: Generate authorization URL\nfunction getAuthUrl(): string {\n  const authUrl = new URL(\"https:\u002F\u002Fapp.hubspot.com\u002Foauth\u002Fauthorize\");\n  authUrl.searchParams.set(\"client_id\", CLIENT_ID);\n  authUrl.searchParams.set(\"redirect_uri\", REDIRECT_URI);\n  authUrl.searchParams.set(\"scope\", SCOPES);\n  return authUrl.toString();\n}\n\n\u002F\u002F Step 2: Handle OAuth callback\nasync function handleOAuthCallback(code: string) {\n  const response = await fetch(\"https:\u002F\u002Fapi.hubapi.com\u002Foauth\u002Fv1\u002Ftoken\", {\n    method: \"POST\",\n    headers: { \"Content-Type\": \"application\u002Fx-www-form-urlencoded\" },\n    body: new URLSearchParams({\n      grant_type: \"authorization_code\",\n      client_id: CLIENT_ID,\n      client_secret: CLIENT_SECRET,\n      redirect_uri: REDIRECT_URI,\n      code: code,\n    }),\n  });\n\n  const tokens = await response.json();\n  \u002F\u002F {\n  \u002F\u002F   access_token: \"xxx\",\n  \u002F\u002F   refresh_token: \"xxx\",\n  \u002F\u002F   expires_in: 1800  \u002F\u002F 30 minutes\n  \u002F\u002F }\n\n  \u002F\u002F Store tokens securely\n  await storeTokens(tokens);\n\n  return tokens;\n}\n\n\u002F\u002F Step 3: Refresh access token (before expiry)\nasync function refreshAccessToken(refreshToken: string) {\n  const response = await fetch(\"https:\u002F\u002Fapi.hubapi.com\u002Foauth\u002Fv1\u002Ftoken\", {\n    method: \"POST\",\n    headers: { \"Content-Type\": \"application\u002Fx-www-form-urlencoded\" },\n    body: new URLSearchParams({\n      grant_type: \"refresh_token\",\n      client_id: CLIENT_ID,\n      client_secret: CLIENT_SECRET,\n      refresh_token: refreshToken,\n    }),\n  });\n\n  return response.json();\n}\n\n\u002F\u002F Step 4: Create authenticated client\nfunction createClient(accessToken: string): Client {\n  const hubspotClient = new Client({ accessToken });\n  return hubspotClient;\n}\n\n### Notes\n\n- Access tokens expire in 30 minutes\n- Refresh tokens before expiry\n- Store refresh tokens securely\n- Rotate tokens every 6 months\n\n### Private App Token\n\nAuthentication for single-account integrations\n\n**When to use**: Building internal integration for one HubSpot account\n\n### Template\n\n\u002F\u002F Private App Token - simpler for single account\nimport { Client } from \"@hubspot\u002Fapi-client\";\n\n\u002F\u002F Create client with private app token\nconst hubspotClient = new Client({\n  accessToken: process.env.HUBSPOT_PRIVATE_APP_TOKEN,\n});\n\n\u002F\u002F Private app tokens don't expire\n\u002F\u002F But should be rotated every 6 months for security\n\n\u002F\u002F Example: Get contacts\nasync function getContacts() {\n  try {\n    const response = await hubspotClient.crm.contacts.basicApi.getPage(\n      100,  \u002F\u002F limit\n      undefined,  \u002F\u002F after cursor\n      [\"firstname\", \"lastname\", \"email\", \"phone\"],  \u002F\u002F properties\n    );\n\n    return response.results;\n  } catch (error) {\n    if (error.code === 429) {\n      \u002F\u002F Rate limited - implement backoff\n      const retryAfter = error.headers?.[\"retry-after\"] || 10;\n      await sleep(retryAfter * 1000);\n      return getContacts();\n    }\n    throw error;\n  }\n}\n\n\u002F\u002F Python equivalent\n\u002F\u002F from hubspot import HubSpot\n\u002F\u002F\n\u002F\u002F client = HubSpot(access_token=os.environ[\"HUBSPOT_PRIVATE_APP_TOKEN\"])\n\u002F\u002F\n\u002F\u002F contacts = client.crm.contacts.basic_api.get_page(\n\u002F\u002F     limit=100,\n\u002F\u002F     properties=[\"firstname\", \"lastname\", \"email\"]\n\u002F\u002F )\n\n### Notes\n\n- Private app tokens don't expire\n- All private apps share daily rate limit\n- Each private app has own burst limit\n- Recommended: Rotate every 6 months\n\n### CRM Object CRUD Operations\n\nCreate, read, update, delete CRM records\n\n**When to use**: Working with contacts, companies, deals, tickets\n\n### Template\n\nimport { Client } from \"@hubspot\u002Fapi-client\";\n\nconst hubspotClient = new Client({\n  accessToken: process.env.HUBSPOT_TOKEN,\n});\n\n\u002F\u002F CREATE contact\nasync function createContact(data: {\n  email: string;\n  firstname: string;\n  lastname: string;\n}) {\n  const response = await hubspotClient.crm.contacts.basicApi.create({\n    properties: {\n      email: data.email,\n      firstname: data.firstname,\n      lastname: data.lastname,\n    },\n  });\n\n  return response;\n}\n\n\u002F\u002F READ contact by ID\nasync function getContact(contactId: string) {\n  const response = await hubspotClient.crm.contacts.basicApi.getById(\n    contactId,\n    [\"firstname\", \"lastname\", \"email\", \"phone\", \"company\"],\n  );\n\n  return response;\n}\n\n\u002F\u002F UPDATE contact\nasync function updateContact(contactId: string, properties: object) {\n  const response = await hubspotClient.crm.contacts.basicApi.update(\n    contactId,\n    { properties },\n  );\n\n  return response;\n}\n\n\u002F\u002F DELETE contact\nasync function deleteContact(contactId: string) {\n  await hubspotClient.crm.contacts.basicApi.archive(contactId);\n}\n\n\u002F\u002F SEARCH contacts\nasync function searchContacts(query: string) {\n  const response = await hubspotClient.crm.contacts.searchApi.doSearch({\n    query,\n    limit: 100,\n    properties: [\"firstname\", \"lastname\", \"email\"],\n    sorts: [{ propertyName: \"createdate\", direction: \"DESCENDING\" }],\n  });\n\n  return response.results;\n}\n\n\u002F\u002F LIST with pagination\nasync function getAllContacts() {\n  const allContacts = [];\n  let after = undefined;\n\n  do {\n    const response = await hubspotClient.crm.contacts.basicApi.getPage(\n      100,\n      after,\n      [\"firstname\", \"lastname\", \"email\"],\n    );\n\n    allContacts.push(...response.results);\n    after = response.paging?.next?.after;\n  } while (after);\n\n  return allContacts;\n}\n\n### Notes\n\n- Use properties param to fetch only needed fields\n- Search API has 10k result limit\n- Always implement pagination for lists\n- Archive (soft delete) vs. GDPR delete available\n\n### Batch Operations\n\nBulk create, update, or read records efficiently\n\n**When to use**: Processing multiple records (reduce rate limit usage)\n\n### Template\n\nimport { Client } from \"@hubspot\u002Fapi-client\";\n\nconst hubspotClient = new Client({\n  accessToken: process.env.HUBSPOT_TOKEN,\n});\n\n\u002F\u002F BATCH CREATE contacts (up to 100 per batch)\nasync function batchCreateContacts(contacts: Array\u003C{\n  email: string;\n  firstname: string;\n  lastname: string;\n}>) {\n  const inputs = contacts.map((contact) => ({\n    properties: {\n      email: contact.email,\n      firstname: contact.firstname,\n      lastname: contact.lastname,\n    },\n  }));\n\n  const response = await hubspotClient.crm.contacts.batchApi.create({\n    inputs,\n  });\n\n  return response.results;\n}\n\n\u002F\u002F BATCH UPDATE contacts\nasync function batchUpdateContacts(\n  updates: Array\u003C{ id: string; properties: object }>\n) {\n  const inputs = updates.map(({ id, properties }) => ({\n    id,\n    properties,\n  }));\n\n  const response = await hubspotClient.crm.contacts.batchApi.update({\n    inputs,\n  });\n\n  return response.results;\n}\n\n\u002F\u002F BATCH READ contacts by ID\nasync function batchReadContacts(\n  ids: string[],\n  properties: string[] = [\"firstname\", \"lastname\", \"email\"]\n) {\n  const response = await hubspotClient.crm.contacts.batchApi.read({\n    inputs: ids.map((id) => ({ id })),\n    properties,\n  });\n\n  return response.results;\n}\n\n\u002F\u002F BATCH ARCHIVE contacts\nasync function batchDeleteContacts(ids: string[]) {\n  await hubspotClient.crm.contacts.batchApi.archive({\n    inputs: ids.map((id) => ({ id })),\n  });\n}\n\n\u002F\u002F Process large dataset in chunks\nasync function processLargeDataset(allContacts: any[]) {\n  const BATCH_SIZE = 100;\n  const results = [];\n\n  for (let i = 0; i \u003C allContacts.length; i += BATCH_SIZE) {\n    const batch = allContacts.slice(i, i + BATCH_SIZE);\n    const batchResults = await batchCreateContacts(batch);\n    results.push(...batchResults);\n\n    \u002F\u002F Respect rate limits - wait between batches\n    if (i + BATCH_SIZE \u003C allContacts.length) {\n      await sleep(100);  \u002F\u002F 100ms between batches\n    }\n  }\n\n  return results;\n}\n\n### Notes\n\n- Max 100 items per batch request\n- Saves up to 80% of rate limit quota\n- Batch operations are atomic per item (partial success possible)\n- Check response.errors for failed items\n\n### Associations v4 API\n\nCreate relationships between CRM records\n\n**When to use**: Linking contacts to companies, deals, etc.\n\n### Template\n\nimport { Client, AssociationTypes } from \"@hubspot\u002Fapi-client\";\n\nconst hubspotClient = new Client({\n  accessToken: process.env.HUBSPOT_TOKEN,\n});\n\n\u002F\u002F CREATE association (Contact to Company)\nasync function associateContactToCompany(\n  contactId: string,\n  companyId: string\n) {\n  await hubspotClient.crm.associations.v4.basicApi.create(\n    \"contacts\",\n    contactId,\n    \"companies\",\n    companyId,\n    [\n      {\n        associationCategory: \"HUBSPOT_DEFINED\",\n        associationTypeId: AssociationTypes.contactToCompany,\n      },\n    ]\n  );\n}\n\n\u002F\u002F CREATE association (Deal to Contact)\nasync function associateDealToContact(dealId: string, contactId: string) {\n  await hubspotClient.crm.associations.v4.basicApi.create(\n    \"deals\",\n    dealId,\n    \"contacts\",\n    contactId,\n    [\n      {\n        associationCategory: \"HUBSPOT_DEFINED\",\n        associationTypeId: 3,  \u002F\u002F deal_to_contact\n      },\n    ]\n  );\n}\n\n\u002F\u002F GET associations for a record\nasync function getContactCompanies(contactId: string) {\n  const response = await hubspotClient.crm.associations.v4.basicApi.getPage(\n    \"contacts\",\n    contactId,\n    \"companies\",\n    undefined,\n    500\n  );\n\n  return response.results;\n}\n\n\u002F\u002F CREATE association with custom label\nasync function createLabeledAssociation(\n  contactId: string,\n  companyId: string,\n  labelId: number  \u002F\u002F Custom association label ID\n) {\n  await hubspotClient.crm.associations.v4.basicApi.create(\n    \"contacts\",\n    contactId,\n    \"companies\",\n    companyId,\n    [\n      {\n        associationCategory: \"USER_DEFINED\",\n        associationTypeId: labelId,\n      },\n    ]\n  );\n}\n\n\u002F\u002F BATCH create associations\nasync function batchAssociateContactsToCompany(\n  contactIds: string[],\n  companyId: string\n) {\n  const inputs = contactIds.map((contactId) => ({\n    _from: { id: contactId },\n    to: { id: companyId },\n    types: [\n      {\n        associationCategory: \"HUBSPOT_DEFINED\",\n        associationTypeId: AssociationTypes.contactToCompany,\n      },\n    ],\n  }));\n\n  await hubspotClient.crm.associations.v4.batchApi.create(\n    \"contacts\",\n    \"companies\",\n    { inputs }\n  );\n}\n\n\u002F\u002F Common association type IDs\n\u002F\u002F Contact to Company: 1\n\u002F\u002F Company to Contact: 2\n\u002F\u002F Deal to Contact: 3\n\u002F\u002F Contact to Deal: 4\n\u002F\u002F Deal to Company: 5\n\u002F\u002F Company to Deal: 6\n\n### Notes\n\n- Requires SDK version 9.0.0+ for v4 API\n- Association labels supported for custom relationships\n- Use batch API for multiple associations\n- HUBSPOT_DEFINED for standard, USER_DEFINED for custom labels\n\n### Webhook Handling\n\nReceive real-time notifications from HubSpot\n\n**When to use**: Need instant updates on CRM changes\n\n### Template\n\nimport crypto from \"crypto\";\nimport { Client } from \"@hubspot\u002Fapi-client\";\n\n\u002F\u002F Webhook signature validation\nfunction validateWebhookSignature(\n  requestBody: string,\n  signature: string,\n  clientSecret: string\n): boolean {\n  \u002F\u002F For v2 signature (most common)\n  const expectedSignature = crypto\n    .createHmac(\"sha256\", clientSecret)\n    .update(requestBody)\n    .digest(\"hex\");\n\n  return signature === expectedSignature;\n}\n\n\u002F\u002F Express webhook handler\napp.post(\"\u002Fwebhooks\u002Fhubspot\", async (req, res) => {\n  const signature = req.headers[\"x-hubspot-signature-v3\"] as string;\n  const timestamp = req.headers[\"x-hubspot-request-timestamp\"] as string;\n  const requestBody = JSON.stringify(req.body);\n\n  \u002F\u002F Validate signature\n  const isValid = validateWebhookSignature(\n    requestBody,\n    signature,\n    process.env.HUBSPOT_CLIENT_SECRET\n  );\n\n  if (!isValid) {\n    console.error(\"Invalid webhook signature\");\n    return res.status(401).send(\"Unauthorized\");\n  }\n\n  \u002F\u002F Check timestamp (prevent replay attacks)\n  const timestampAge = Date.now() - parseInt(timestamp);\n  if (timestampAge > 300000) {  \u002F\u002F 5 minutes\n    console.error(\"Webhook timestamp too old\");\n    return res.status(401).send(\"Timestamp expired\");\n  }\n\n  \u002F\u002F Process events - respond quickly!\n  const events = req.body;\n\n  \u002F\u002F Queue for async processing\n  for (const event of events) {\n    await queue.add(\"hubspot-webhook\", event);\n  }\n\n  \u002F\u002F Respond immediately\n  res.status(200).send(\"OK\");\n});\n\n\u002F\u002F Async processor\nasync function processWebhookEvent(event: any) {\n  const { subscriptionType, objectId, propertyName, propertyValue } = event;\n\n  switch (subscriptionType) {\n    case \"contact.creation\":\n      await handleContactCreated(objectId);\n      break;\n\n    case \"contact.propertyChange\":\n      await handleContactPropertyChange(objectId, propertyName, propertyValue);\n      break;\n\n    case \"deal.creation\":\n      await handleDealCreated(objectId);\n      break;\n\n    case \"contact.deletion\":\n      await handleContactDeleted(objectId);\n      break;\n\n    default:\n      console.log(`Unhandled event: ${subscriptionType}`);\n  }\n}\n\n\u002F\u002F Webhook subscription types:\n\u002F\u002F contact.creation, contact.deletion, contact.propertyChange\n\u002F\u002F company.creation, company.deletion, company.propertyChange\n\u002F\u002F deal.creation, deal.deletion, deal.propertyChange\n\n### Notes\n\n- Validate signature before processing\n- Respond within 5 seconds\n- Queue heavy processing for async\n- Max 1000 webhook subscriptions per app\n\n### Custom Objects\n\nCreate and manage custom object types\n\n**When to use**: Standard objects don't fit your data model\n\n### Template\n\nimport { Client } from \"@hubspot\u002Fapi-client\";\n\nconst hubspotClient = new Client({\n  accessToken: process.env.HUBSPOT_TOKEN,\n});\n\n\u002F\u002F CREATE custom object schema\nasync function createCustomObjectSchema() {\n  const schema = {\n    name: \"projects\",\n    labels: {\n      singular: \"Project\",\n      plural: \"Projects\",\n    },\n    primaryDisplayProperty: \"project_name\",\n    requiredProperties: [\"project_name\"],\n    properties: [\n      {\n        name: \"project_name\",\n        label: \"Project Name\",\n        type: \"string\",\n        fieldType: \"text\",\n      },\n      {\n        name: \"status\",\n        label: \"Status\",\n        type: \"enumeration\",\n        fieldType: \"select\",\n        options: [\n          { label: \"Active\", value: \"active\" },\n          { label: \"Completed\", value: \"completed\" },\n          { label: \"On Hold\", value: \"on_hold\" },\n        ],\n      },\n      {\n        name: \"budget\",\n        label: \"Budget\",\n        type: \"number\",\n        fieldType: \"number\",\n      },\n      {\n        name: \"start_date\",\n        label: \"Start Date\",\n        type: \"date\",\n        fieldType: \"date\",\n      },\n    ],\n    associatedObjects: [\"CONTACT\", \"COMPANY\"],\n  };\n\n  const response = await hubspotClient.crm.schemas.coreApi.create(schema);\n  return response;\n}\n\n\u002F\u002F CREATE custom object record\nasync function createProject(data: {\n  project_name: string;\n  status: string;\n  budget: number;\n}) {\n  const response = await hubspotClient.crm.objects.basicApi.create(\n    \"projects\",  \u002F\u002F Custom object name\n    { properties: data }\n  );\n\n  return response;\n}\n\n\u002F\u002F READ custom object by ID\nasync function getProject(projectId: string) {\n  const response = await hubspotClient.crm.objects.basicApi.getById(\n    \"projects\",\n    projectId,\n    [\"project_name\", \"status\", \"budget\", \"start_date\"]\n  );\n\n  return response;\n}\n\n\u002F\u002F UPDATE custom object\nasync function updateProject(projectId: string, properties: object) {\n  const response = await hubspotClient.crm.objects.basicApi.update(\n    \"projects\",\n    projectId,\n    { properties }\n  );\n\n  return response;\n}\n\n\u002F\u002F SEARCH custom objects\nasync function searchProjects(status: string) {\n  const response = await hubspotClient.crm.objects.searchApi.doSearch(\n    \"projects\",\n    {\n      filterGroups: [\n        {\n          filters: [\n            {\n              propertyName: \"status\",\n              operator: \"EQ\",\n              value: status,\n            },\n          ],\n        },\n      ],\n      properties: [\"project_name\", \"status\", \"budget\"],\n      limit: 100,\n    }\n  );\n\n  return response.results;\n}\n\n### Notes\n\n- Custom objects require Enterprise tier\n- Max 10 custom objects per account\n- Use crm.objects API with object name as parameter\n- Can associate with standard and other custom objects\n\n## Sharp Edges\n\n### Rate Limits Vary by App Type and Hub Tier\n\nSeverity: HIGH\n\n### 5% Error Rate Threshold for Marketplace Apps\n\nSeverity: HIGH\n\n### API Keys Deprecated - Use OAuth or Private App Tokens\n\nSeverity: CRITICAL\n\n### OAuth Access Tokens Expire in 30 Minutes\n\nSeverity: HIGH\n\n### Webhook Requests Must Be Validated\n\nSeverity: CRITICAL\n\n### All List Endpoints Require Pagination\n\nSeverity: MEDIUM\n\n### Associations v4 API Has Breaking Changes\n\nSeverity: HIGH\n\n### Polling Limited to 100,000 Requests Per Day\n\nSeverity: MEDIUM\n\n## Validation Checks\n\n### Hardcoded HubSpot API Key\n\nSeverity: ERROR\n\nAPI keys must never be hardcoded\n\nMessage: Hardcoded HubSpot API key detected. Use environment variables. Note: API keys are deprecated - use Private App tokens.\n\n### Hardcoded HubSpot Access Token\n\nSeverity: ERROR\n\nAccess tokens must use environment variables\n\nMessage: Hardcoded HubSpot access token. Use environment variables.\n\n### Hardcoded Client Secret\n\nSeverity: ERROR\n\nOAuth client secrets must be secured\n\nMessage: Hardcoded client secret. Use environment variables.\n\n### Missing Webhook Signature Validation\n\nSeverity: ERROR\n\nWebhook endpoints must validate HubSpot signatures\n\nMessage: Webhook endpoint without signature validation. Validate X-HubSpot-Signature-v3.\n\n### Missing Rate Limit Handling\n\nSeverity: WARNING\n\nAPI calls should handle 429 responses\n\nMessage: HubSpot API calls without rate limit handling. Implement retry logic with backoff.\n\n### Unthrottled Parallel API Calls\n\nSeverity: WARNING\n\nParallel calls can exceed rate limits\n\nMessage: Parallel HubSpot API calls without throttling. Use rate limiter.\n\n### Missing Pagination for List Calls\n\nSeverity: WARNING\n\nList endpoints return paginated results\n\nMessage: API call without pagination handling. Implement cursor-based pagination.\n\n### Individual Operations in Loop\n\nSeverity: INFO\n\nUse batch operations for multiple items\n\nMessage: Individual API calls in loop. Consider batch operations for better performance.\n\n### Token Storage Without Expiry\n\nSeverity: WARNING\n\nOAuth tokens expire and need refresh logic\n\nMessage: Token storage without expiry tracking. Store expiresAt for refresh logic.\n\n### Deprecated API Key Usage\n\nSeverity: ERROR\n\nAPI keys are deprecated\n\nMessage: Using deprecated API key. Migrate to Private App token or OAuth 2.0.\n\n## Collaboration\n\n### Delegation Triggers\n\n- user needs email marketing automation -> email-marketing (Beyond HubSpot's built-in email tools)\n- user needs custom CRM UI -> frontend (Building portal or dashboard)\n- user needs data pipeline -> data-engineer (ETL from HubSpot to warehouse)\n- user needs Salesforce integration -> salesforce-development (HubSpot + Salesforce sync)\n- user needs payment processing -> stripe-integration (Payments beyond HubSpot quotes)\n- user needs analytics dashboard -> analytics-specialist (Custom reporting beyond HubSpot)\n\n## When to Use\n- User mentions or implies: hubspot\n- User mentions or implies: hubspot api\n- User mentions or implies: hubspot crm\n- User mentions or implies: hubspot integration\n- User mentions or implies: contacts api\n\n## Limitations\n- Use this skill only when the task clearly matches the scope described above.\n- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.\n- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.\n","","imported","https:\u002F\u002Fgithub.com\u002Fsickn33\u002Fantigravity-awesome-skills","user_system_seed","SkillOPIC",true,226,1613,"2026-05-16 13:22:26",{"id":8,"name":21,"slug":22,"icon":23,"description":24,"sort":25,"createdAt":26},"写作研究","writing","mdi-pencil-outline","从学术写作到创意文案，让 AI 成为你的专属写作助手",1,"2026-05-16 12:53:40",{"id":7,"name":28,"slug":29,"icon":30,"description":31,"moduleId":8,"sort":32,"skillCount":33,"createdAt":26},"文案策划","copywriting","mdi-comment-text-outline","广告文案、品牌故事、Slogan",4,72,[35],{"id":36,"skillId":4,"version":37,"fileName":38,"fileSize":39,"filePath":40,"fileHash":41,"manifest":42,"createdAt":19},"23c203b7-a5de-464c-8ab2-563e98b60ea8","1.0.0","hubspot-integration.zip",6027,"uploads\u002Fskills\u002Fac45fac9-de72-4c78-80d1-f46544433597\u002Fhubspot-integration.zip","9d80aa2837430f5ca35f477c91424f851d6857556b0493616023d874bfedf739","[{\"path\":\"SKILL.md\",\"isDirectory\":false,\"size\":20194}]",{"code":44,"message":45,"data":46},200,"success",{"items":47,"stats":48,"page":51},[],{"averageRating":49,"totalRatings":49,"ratingCounts":50},0,[49,49,49,49,49],{"limit":52,"offset":49,"hasMore":53,"nextOffset":52,"ratedOnly":16},15,false]