@zibby/skills 0.1.64 → 0.1.68

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,225 @@
1
+ /**
2
+ * INJECTED-TOKEN fast path (Zibby Copilot per-turn credential injection) +
3
+ * NON-OWNER SAFETY GATE — the Google twin of linkedin.js injectedPersonalToken.
4
+ *
5
+ * The Zibby Copilot is ONE shared chat bot backed by ONE project's
6
+ * PROJECT_API_TOKEN. resolveIntegrationToken('google') authenticates with that
7
+ * PAT, so the account is inferred SERVER-SIDE from the PAT — meaning the shared
8
+ * bot would only ever see the PROJECT OWNER's Google (their readable Docs, their
9
+ * Drive), never the (different) person who actually sent the chat message.
10
+ *
11
+ * Two per-turn env signals, set + restored by the copilot-runtime (a TRUSTED
12
+ * backend service that already KMS-decrypts tenants' creds directly):
13
+ * - ZIBBY_INJECTED_GOOGLE_TOKEN (+ _EMAIL): the EMAIL-VERIFIED sender's OWN
14
+ * `google` integration access token (auto-refreshed server-side). When
15
+ * present it takes PRECEDENCE over resolveIntegrationToken, so every gdocs
16
+ * call runs against the SENDER's own Google.
17
+ * - ZIBBY_SENDER_IS_NON_OWNER=1: the verified sender's account differs from
18
+ * the PAT/tenant account. With NO injected token this is a HARD REFUSAL
19
+ * gate: the skill must NEVER fall through to the owner's token on a
20
+ * colleague's behalf (privacy: the owner's readable docs would be exposed
21
+ * and created docs would land in the owner's Drive). Every tool returns
22
+ * { ok:false, error } telling the sender to connect their own Google.
23
+ *
24
+ * Absent both (owner/self turns, Fargate workflows, self-host — no
25
+ * sender-identity context) → the normal PAT chokepoint runs unchanged.
26
+ */
27
+ export function injectedGoogleToken(): {
28
+ token: string;
29
+ email: string;
30
+ };
31
+ /** True when the runtime flagged this turn's verified sender as NOT the tenant owner. */
32
+ export function senderIsNonOwner(): boolean;
33
+ /**
34
+ * STRICT CHAT-TURN INVARIANT (fail-CLOSED) — ZIBBY_CHAT_STRICT_PERSONAL=1.
35
+ *
36
+ * Set UNCONDITIONALLY by the Copilot runtime for EVERY chat turn (Slack +
37
+ * Lark, owner or not). Under it, personal-tier providers (google here) must
38
+ * NEVER fall through to the PAT-resolved project-owner token: the ONLY
39
+ * accepted credential is the per-turn injected sender token
40
+ * (ZIBBY_INJECTED_GOOGLE_TOKEN — the runtime injects the OWNER's own Google
41
+ * through the same path, so the owner keeps working). No injected token →
42
+ * HARD REFUSE, for ANY sender: verified non-owners, same-account colleagues,
43
+ * UNVERIFIED senders (users.info failure / no email match — the exact class
44
+ * the old ZIBBY_SENDER_IS_NON_OWNER flag failed OPEN on), and even the owner
45
+ * when their own Google isn't connected. The non-owner flag stays as
46
+ * belt-and-braces; this strict flag is the primary gate.
47
+ *
48
+ * Absent both flags (Fargate workflows, self-host, direct tool use — no chat
49
+ * sender context) → the normal PAT chokepoint runs unchanged.
50
+ */
51
+ export function chatStrictPersonal(): boolean;
52
+ /**
53
+ * Extract a Google Docs document id from a raw id OR a Docs URL
54
+ * (https://docs.google.com/document/d/<id>/edit...). Returns the id string
55
+ * or null.
56
+ */
57
+ export function parseDocId(ref: any): string;
58
+ /**
59
+ * Low-level Google REST helper. Resolves the bearer via
60
+ * resolveIntegrationToken('google'), retries once on transient auth errors
61
+ * (clearing the client-side token cache so the backend re-refreshes), and
62
+ * returns parsed JSON.
63
+ *
64
+ * Keep this the single auth chokepoint — don't resolve tokens at call sites.
65
+ */
66
+ export function googleApi(url: any, opts?: {}): Promise<any>;
67
+ /**
68
+ * Parse ONE markdown line's inline marks (bold `**x**`, links `[t](u)`,
69
+ * inline code `` `x` ``) into { text, styles } where `styles` are ranges
70
+ * RELATIVE to the returned plain text.
71
+ */
72
+ export function parseInlineMarkdown(line: any): {
73
+ text: string;
74
+ styles: ({
75
+ start: number;
76
+ end: number;
77
+ link: string;
78
+ bold?: undefined;
79
+ code?: undefined;
80
+ } | {
81
+ start: number;
82
+ end: number;
83
+ bold: boolean;
84
+ link?: undefined;
85
+ code?: undefined;
86
+ } | {
87
+ start: number;
88
+ end: number;
89
+ code: boolean;
90
+ link?: undefined;
91
+ bold?: undefined;
92
+ })[];
93
+ };
94
+ /**
95
+ * Convert a small, common subset of markdown (headings #/##/###, bullet
96
+ * lists -/*, numbered lists `1.`, bold, links, inline code) into Google Docs
97
+ * batchUpdate requests that insert the content at `startIndex`.
98
+ *
99
+ * Strategy: ONE insertText request carrying the whole plain text (every line
100
+ * newline-terminated → each becomes its own paragraph), followed by
101
+ * updateParagraphStyle (headings), createParagraphBullets (consecutive list
102
+ * runs) and updateTextStyle (bold/link/code) requests over the computed
103
+ * ranges. Indices are UTF-16 code units — JS string .length matches the Docs
104
+ * API's index space exactly.
105
+ *
106
+ * Returns { requests, endIndex }. Plain text (no markdown) passes through as
107
+ * plain paragraphs — the converter is safe for the `text` input too.
108
+ */
109
+ export function markdownToRequests(markdown: any, startIndex: any): {
110
+ requests: {
111
+ insertText: {
112
+ location: {
113
+ index: any;
114
+ };
115
+ text: string;
116
+ };
117
+ }[];
118
+ endIndex: any;
119
+ };
120
+ /**
121
+ * Flatten a documents.get body into plain text (paragraph text runs joined,
122
+ * newline-separated — table cells + nested content included via recursion).
123
+ * Bounded by MAX_TEXT_CHARS.
124
+ */
125
+ export function extractPlainText(body: any): string;
126
+ export const NON_OWNER_REFUSAL: "You haven't connected your own Google account \u2014 connect it at https://studio.zibby.dev/integrations (Google Docs). For privacy, I can't use anyone else's Google (including the project owner's) on your behalf.";
127
+ export namespace googleDocsSkill {
128
+ let id: string;
129
+ let serverName: string;
130
+ let allowedTools: string[];
131
+ let requiresIntegration: "google";
132
+ let description: string;
133
+ let promptFragment: string;
134
+ /**
135
+ * Spawn the GENERIC skill MCP server (bin/mcp-skill.mjs) pointing at this
136
+ * module's googleDocsSkill export, so the AGENT gets real mcp__gdocs__*
137
+ * tools. Auth flows through the INHERITED env (PROJECT_API_TOKEN →
138
+ * resolveIntegrationToken('google')), so no provider-specific env keys are
139
+ * needed here. When unconnected, handleToolCall returns { ok:false, error }
140
+ * — the agent tolerates it.
141
+ */
142
+ function resolve(): {
143
+ type: string;
144
+ command: string;
145
+ args: any[];
146
+ env: {};
147
+ description: string;
148
+ alwaysLoad: boolean;
149
+ };
150
+ function handleToolCall(name: any, args: any): Promise<string>;
151
+ let tools: ({
152
+ name: string;
153
+ description: string;
154
+ input_schema: {
155
+ type: string;
156
+ properties: {
157
+ title: {
158
+ type: string;
159
+ description: string;
160
+ };
161
+ markdown: {
162
+ type: string;
163
+ description: string;
164
+ };
165
+ text: {
166
+ type: string;
167
+ description: string;
168
+ };
169
+ documentId?: undefined;
170
+ };
171
+ required: string[];
172
+ };
173
+ } | {
174
+ name: string;
175
+ description: string;
176
+ input_schema: {
177
+ type: string;
178
+ properties: {
179
+ documentId: {
180
+ type: string;
181
+ description: string;
182
+ };
183
+ markdown: {
184
+ type: string;
185
+ description: string;
186
+ };
187
+ text: {
188
+ type: string;
189
+ description: string;
190
+ };
191
+ title?: undefined;
192
+ };
193
+ required: string[];
194
+ };
195
+ } | {
196
+ name: string;
197
+ description: string;
198
+ input_schema: {
199
+ type: string;
200
+ properties: {
201
+ documentId: {
202
+ type: string;
203
+ description: string;
204
+ };
205
+ title?: undefined;
206
+ markdown?: undefined;
207
+ text?: undefined;
208
+ };
209
+ required: string[];
210
+ };
211
+ } | {
212
+ name: string;
213
+ description: string;
214
+ input_schema: {
215
+ type: string;
216
+ properties: {
217
+ title?: undefined;
218
+ markdown?: undefined;
219
+ text?: undefined;
220
+ documentId?: undefined;
221
+ };
222
+ required?: undefined;
223
+ };
224
+ })[];
225
+ }
@@ -0,0 +1,12 @@
1
+ import{existsSync as A}from"fs";import{fileURLToPath as v}from"url";import{dirname as R,resolve as L}from"path";import{resolveIntegrationToken as G,clearTokenCache as D}from"@zibby/core/backend-client.js";var w=Object.freeze({SENTRY:"sentry",JIRA:"jira",GITHUB:"github",GITLAB:"gitlab",SLACK:"slack",LARK:"lark",OPENAI_BILLING:"openai_billing",ANTHROPIC_BILLING:"anthropic_billing",CURSOR_ADMIN:"cursor_admin",NOTION:"notion",GOOGLE:"google",PLANE:"plane",LINEAR:"linear",FIGMA:"figma",OPEN_DESIGN:"open_design",LINKEDIN_PERSONAL:"linkedin_personal",LINKEDIN_BUSINESS:"linkedin_business",DISCORD:"discord"}),Y=Object.freeze({sentry:{id:"sentry",name:"Sentry",connectPath:"/integrations?provider=sentry"},jira:{id:"jira",name:"Jira",connectPath:"/integrations?provider=jira"},github:{id:"github",name:"GitHub",connectPath:"/integrations?provider=github"},gitlab:{id:"gitlab",name:"GitLab",connectPath:"/integrations?provider=gitlab"},slack:{id:"slack",name:"Slack",connectPath:"/integrations?provider=slack"},lark:{id:"lark",name:"Lark",connectPath:"/integrations?provider=lark"},openai_billing:{id:"openai_billing",name:"OpenAI Admin",connectPath:"/integrations?provider=openai_billing"},anthropic_billing:{id:"anthropic_billing",name:"Anthropic Admin",connectPath:"/integrations?provider=anthropic_billing"},cursor_admin:{id:"cursor_admin",name:"Cursor Admin",connectPath:"/integrations?provider=cursor_admin"},notion:{id:"notion",name:"Notion",connectPath:"/integrations?provider=notion"},google:{id:"google",name:"Google Docs",connectPath:"/integrations?provider=google"},plane:{id:"plane",name:"Plane",connectPath:"/integrations?provider=plane"},linear:{id:"linear",name:"Linear",connectPath:"/integrations?provider=linear"},figma:{id:"figma",name:"Figma",connectPath:"/integrations?provider=figma"},open_design:{id:"open_design",name:"OpenDesign",connectPath:"/integrations?provider=open_design"},linkedin_personal:{id:"linkedin_personal",name:"LinkedIn (Personal)",connectPath:"/integrations?provider=linkedin_personal"},linkedin_business:{id:"linkedin_business",name:"LinkedIn (Business)",connectPath:"/integrations?provider=linkedin_business"},discord:{id:"discord",name:"Discord",connectPath:"/integrations?provider=discord"}});function P(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let r=R(v(import.meta.url)),t=L(r,"..","bin","mcp-skill.mjs");return A(t)?t:null}var g="https://docs.googleapis.com/v1",C="https://www.googleapis.com/drive/v3";function B(){let r=String(process.env.ZIBBY_INJECTED_GOOGLE_TOKEN||"").trim();if(!r)return null;let t=String(process.env.ZIBBY_INJECTED_GOOGLE_EMAIL||"").trim();return{token:r,email:t}}function $(){return String(process.env.ZIBBY_SENDER_IS_NON_OWNER||"").trim()==="1"}function U(){return String(process.env.ZIBBY_CHAT_STRICT_PERSONAL||"").trim()==="1"}var q="You haven't connected your own Google account \u2014 connect it at https://studio.zibby.dev/integrations (Google Docs). For privacy, I can't use anyone else's Google (including the project owner's) on your behalf.",I=2e4,J=25;function x(r){if(!r||typeof r!="string")return null;let t=r.trim(),e=t.match(/\/document\/(?:u\/\d+\/)?d\/([a-zA-Z0-9_-]+)/);return e?e[1]:/^[a-zA-Z0-9_-]{20,}$/.test(t)?t:null}async function p(r,t={}){let e=async()=>{let o,n=B();if(n)o=n.token;else{if(U()||$())throw new Error(q);({token:o}=await G("google"))}if(typeof o!="string"||!o)throw new Error(`Invalid google token type: ${typeof o}`);let i=await fetch(r,{method:t.method||"GET",headers:{Authorization:`Bearer ${o}`,Accept:"application/json",...t.body?{"Content-Type":"application/json"}:{},...t.headers},body:t.body?JSON.stringify(t.body):void 0});if(!i.ok){let d=await i.text().catch(()=>"");throw new Error(`Google API ${i.status}: ${d.slice(0,300)}`)}let c=await i.text().catch(()=>"");if(!c||!c.trim())return{};try{return JSON.parse(c)}catch{return{raw:c}}};try{return await e()}catch(o){let n=String(o?.message||o||"").toLowerCase();if(!(n.includes("token")||n.includes("401")||n.includes("unauthorized")))throw o;return D("google"),e()}}function j(r){let t=[],e="",o=0;for(;o<r.length;){let n=/^\[([^\]]+)\]\(([^)\s]+)\)/.exec(r.slice(o));if(n){t.push({start:e.length,end:e.length+n[1].length,link:n[2]}),e+=n[1],o+=n[0].length;continue}let i=/^\*\*([^*]+)\*\*/.exec(r.slice(o));if(i){t.push({start:e.length,end:e.length+i[1].length,bold:!0}),e+=i[1],o+=i[0].length;continue}let c=/^`([^`]+)`/.exec(r.slice(o));if(c){t.push({start:e.length,end:e.length+c[1].length,code:!0}),e+=c[1],o+=c[0].length;continue}e+=r[o],o+=1}return{text:e,styles:t}}function N(r,t){let e=String(r??"").replace(/\r\n/g,`
2
+ `);if(!e.trim())return{requests:[],endIndex:t};let o=e.split(`
3
+ `),n="",i=[],c=[];for(let s of o){let l=s,k=null,m=null,f=/^(#{1,3})\s+(.*)$/.exec(l),b=/^\s*[-*]\s+(.*)$/.exec(l),S=/^\s*\d+[.)]\s+(.*)$/.exec(l);f?(k=`HEADING_${f[1].length}`,l=f[2]):b?(m="BULLET_DISC_CIRCLE_SQUARE",l=b[1]):S&&(m="NUMBERED_DECIMAL_ALPHA_ROMAN",l=S[1]);let{text:O,styles:T}=j(l),y=t+n.length;for(let _ of T)c.push({..._,start:y+_.start,end:y+_.end});n+=`${O}
4
+ `,i.push({start:y,end:t+n.length,named:k,bullet:m})}if(!n)return{requests:[],endIndex:t};let d=[{insertText:{location:{index:t},text:n}}];for(let s of i)s.named&&d.push({updateParagraphStyle:{range:{startIndex:s.start,endIndex:s.end},paragraphStyle:{namedStyleType:s.named},fields:"namedStyleType"}});let a=null,u=()=>{a&&(d.push({createParagraphBullets:{range:{startIndex:a.start,endIndex:a.end},bulletPreset:a.preset}}),a=null)};for(let s of i)s.bullet?a&&a.preset===s.bullet?a.end=s.end:(u(),a={start:s.start,end:s.end,preset:s.bullet}):u();u();for(let s of c)s.end<=s.start||(s.bold?d.push({updateTextStyle:{range:{startIndex:s.start,endIndex:s.end},textStyle:{bold:!0},fields:"bold"}}):s.link?d.push({updateTextStyle:{range:{startIndex:s.start,endIndex:s.end},textStyle:{link:{url:s.link}},fields:"link"}}):s.code&&d.push({updateTextStyle:{range:{startIndex:s.start,endIndex:s.end},textStyle:{weightedFontFamily:{fontFamily:"Courier New"}},fields:"weightedFontFamily"}}));return{requests:d,endIndex:t+n.length}}function M(r){let t="",e=o=>{for(let n of Array.isArray(o)?o:[]){if(t.length>=I)return;if(n.paragraph)for(let i of n.paragraph.elements||[])t+=i?.textRun?.content||"";else if(n.table)for(let i of n.table.tableRows||[])for(let c of i.tableCells||[])e(c.content);else n.tableOfContents&&e(n.tableOfContents.content)}};return e(r?.content),t.slice(0,I)}var h=r=>`https://docs.google.com/document/d/${r}/edit`,E=r=>{let t=typeof r?.markdown=="string"?r.markdown:null,e=typeof r?.text=="string"?r.text:null;return t??e},W={id:"google-docs",serverName:"gdocs",allowedTools:["mcp__gdocs__*"],requiresIntegration:w.GOOGLE,description:"Google Docs \u2014 create, append to, and read Google Docs (drive.file scoped)",promptFragment:`## Google Docs (connected)
5
+ You can create and edit Google Docs for the user. IMPORTANT visibility caveat: the integration uses Google's per-file drive.file scope, so you can only see docs this app CREATED (or the user explicitly picked) \u2014 not the user's whole Drive.
6
+ Docs access is PER-USER: each teammate connects their OWN Google account (Integrations \u2192 Google Docs). In shared-chat contexts the runtime routes these tools to the SENDER's own Google; a teammate who hasn't connected their own Google gets { ok:false } with connect instructions \u2014 for privacy the project owner's Google is NEVER used on someone else's behalf. Relay those instructions rather than retrying.
7
+ - gdocs_create_doc: create a new Google Doc from a title + markdown (headings/bold/bullets/links supported) or plain text; returns { documentId, url }. Share the url with the user.
8
+ - gdocs_append: append markdown/text to the end of a doc you created earlier (pass the documentId or doc URL).
9
+ - gdocs_get: read a doc back as plain text (works for app-created/user-picked docs; arbitrary docs need the extended documents.readonly connection).
10
+ - gdocs_list_created: list the Google Docs visible to this app (drive.file \u2192 only docs it created or the user picked).
11
+ These tools return { ok:false, error } on failure \u2014 treat an unavailable Google connection as "cannot deliver to Docs" and report it rather than blocking the task.`,resolve(){let r=P();if(!r)return null;let t={};for(let e of["ZIBBY_INJECTED_GOOGLE_TOKEN","ZIBBY_INJECTED_GOOGLE_EMAIL","ZIBBY_SENDER_IS_NON_OWNER","ZIBBY_CHAT_STRICT_PERSONAL"])process.env[e]&&(t[e]=process.env[e]);return{type:"stdio",command:"node",args:[r,"../dist/googleDocs.js","googleDocsSkill"],env:t,description:this.description,alwaysLoad:!0}},async handleToolCall(r,t){try{switch(r){case"gdocs_create_doc":{let e=typeof t?.title=="string"&&t.title.trim()?t.title.trim():null;if(!e)return JSON.stringify({ok:!1,error:"title is required"});let n=(await p(`${g}/documents`,{method:"POST",body:{title:e}}))?.documentId;if(!n)return JSON.stringify({ok:!1,error:"Google Docs create returned no documentId"});let i=E(t);if(i&&i.trim()){let{requests:c}=N(i,1);c.length&&await p(`${g}/documents/${n}:batchUpdate`,{method:"POST",body:{requests:c}})}return JSON.stringify({ok:!0,documentId:n,title:e,url:h(n)})}case"gdocs_append":{let e=x(t?.documentId||t?.url||t?.id);if(!e)return JSON.stringify({ok:!1,error:"A valid Google Docs documentId or URL is required"});let o=E(t);if(!o||!o.trim())return JSON.stringify({ok:!1,error:"markdown or text content is required"});let i=(await p(`${g}/documents/${e}`))?.body,c=Array.isArray(i?.content)?i.content:[],d=c.length&&c[c.length-1].endIndex||2,a=Math.max(1,d-1),u=[],s=a;a>1&&(u.push({insertText:{location:{index:a},text:`
12
+ `}}),s=a+1);let l=N(o,s);return u.push(...l.requests),await p(`${g}/documents/${e}:batchUpdate`,{method:"POST",body:{requests:u}}),JSON.stringify({ok:!0,documentId:e,url:h(e)})}case"gdocs_get":{let e=x(t?.documentId||t?.url||t?.id);if(!e)return JSON.stringify({ok:!1,error:"A valid Google Docs documentId or URL is required"});let o=await p(`${g}/documents/${e}`),n=M(o?.body);return JSON.stringify({ok:!0,documentId:e,title:o?.title||"",url:h(e),text:n,...n.length>=I?{truncated:!0}:{}})}case"gdocs_list_created":{let e=new URLSearchParams({q:"'me' in owners and mimeType='application/vnd.google-apps.document' and trashed=false",fields:"files(id,name,modifiedTime,webViewLink)",pageSize:String(J),orderBy:"modifiedTime desc"}),o=await p(`${C}/files?${e.toString()}`),n=(Array.isArray(o?.files)?o.files:[]).map(i=>({documentId:i.id,title:i.name,modifiedTime:i.modifiedTime,url:i.webViewLink||h(i.id)}));return JSON.stringify({ok:!0,count:n.length,files:n})}default:return JSON.stringify({ok:!1,error:`Unknown tool: ${r}`})}}catch(e){return JSON.stringify({ok:!1,error:e.message})}},tools:[{name:"gdocs_create_doc",description:"Create a new Google Doc with a title and optional content (markdown: #/##/### headings, - bullets, 1. numbered lists, **bold**, [links](url), `code`; or plain text). Returns { ok, documentId, url } \u2014 share the url with the user.",input_schema:{type:"object",properties:{title:{type:"string",description:"Document title."},markdown:{type:"string",description:"Document body as markdown (preferred)."},text:{type:"string",description:"Document body as plain text (used when markdown is absent)."}},required:["title"]}},{name:"gdocs_append",description:"Append markdown/text content to the END of an existing Google Doc. Only works on docs this app created or the user explicitly picked (drive.file scope). Accepts a documentId or a full docs.google.com URL. Returns { ok, documentId, url }.",input_schema:{type:"object",properties:{documentId:{type:"string",description:"Google Docs documentId OR a full https://docs.google.com/document/d/... URL."},markdown:{type:"string",description:"Content to append, as markdown (preferred)."},text:{type:"string",description:"Content to append, as plain text (used when markdown is absent)."}},required:["documentId"]}},{name:"gdocs_get",description:"Read a Google Doc back as plain text (truncated to ~20k chars). Under the default drive.file scope this works ONLY for docs this app created or the user explicitly picked; reading arbitrary docs requires the extended documents.readonly connection. Returns { ok, documentId, title, url, text }.",input_schema:{type:"object",properties:{documentId:{type:"string",description:"Google Docs documentId OR a full https://docs.google.com/document/d/... URL."}},required:["documentId"]}},{name:"gdocs_list_created",description:"List the Google Docs visible to this integration, newest first (max 25). NOTE: under the drive.file scope this lists ONLY docs the app created or the user explicitly picked \u2014 it is NOT a full Drive search. Returns { ok, count, files:[{ documentId, title, modifiedTime, url }] }.",input_schema:{type:"object",properties:{}}}]};export{q as NON_OWNER_REFUSAL,U as chatStrictPersonal,M as extractPlainText,p as googleApi,W as googleDocsSkill,B as injectedGoogleToken,N as markdownToRequests,x as parseDocId,j as parseInlineMarkdown,$ as senderIsNonOwner};
package/dist/index.d.ts CHANGED
@@ -11,7 +11,9 @@ export namespace SKILLS {
11
11
  let GIT_WRITE: string;
12
12
  let SLACK: string;
13
13
  let LARK: string;
14
+ let DISCORD: string;
14
15
  let NOTION: string;
16
+ let GOOGLE_DOCS: string;
15
17
  let LINKEDIN: string;
16
18
  let CHAT_NOTIFY: string;
17
19
  let SENTRY: string;
@@ -42,8 +44,10 @@ import { gitSkill } from './git.js';
42
44
  import { gitWriteSkill } from './git-write.js';
43
45
  import { slackSkill } from './slack.js';
44
46
  import { larkSkill } from './lark.js';
47
+ import { discordSkill } from './discord.js';
45
48
  import { notionSkill } from './notion.js';
46
49
  import { linkedinSkill } from './linkedin.js';
50
+ import { googleDocsSkill } from './googleDocs.js';
47
51
  import { chatNotifySkill } from './chat-notify.js';
48
52
  import { sentrySkill } from './sentry.js';
49
53
  import { memorySkill } from './memory.js';
@@ -55,7 +59,7 @@ import { testRunnerSkill } from './test-runner.js';
55
59
  import { skillInstallerSkill } from './skill-installer.js';
56
60
  import { coreToolsSkill } from './core-tools.js';
57
61
  import { workflowBuilderSkill } from './workflow-builder.js';
58
- export { browserSkill, jiraSkill, githubSkill, gitlabSkill, figmaSkill, linearSkill, planeSkill, opendesignSkill, gitSkill, gitWriteSkill, slackSkill, larkSkill, notionSkill, linkedinSkill, chatNotifySkill, sentrySkill, memorySkill, chatMemorySkill, kvMemorySkill, datasetStoreSkill, codebaseMemorySkill, testRunnerSkill, testRunnerSkill as runnerSkill, skillInstallerSkill, coreToolsSkill, workflowBuilderSkill };
62
+ export { browserSkill, jiraSkill, githubSkill, gitlabSkill, figmaSkill, linearSkill, planeSkill, opendesignSkill, gitSkill, gitWriteSkill, slackSkill, larkSkill, discordSkill, notionSkill, linkedinSkill, googleDocsSkill, chatNotifySkill, sentrySkill, memorySkill, chatMemorySkill, kvMemorySkill, datasetStoreSkill, codebaseMemorySkill, testRunnerSkill, testRunnerSkill as runnerSkill, skillInstallerSkill, coreToolsSkill, workflowBuilderSkill };
59
63
  export { openaiBillingSkill, anthropicBillingSkill, cursorAdminSkill, fetchOpenAICosts, fetchOpenAIProjects, fetchAnthropicCosts, fetchAnthropicWorkspaces, fetchCursorSpend, fetchAllProviders, groupByKey, meanStddev } from "./llm-billing.js";
60
64
  export { reportObjectSchema, reportToBlockKit, reportToLarkCard, SEVERITIES as REPORT_SEVERITIES } from "./report.js";
61
65
  export { skill, functionSkill } from "./function-skill.js";