@zibby/skills 0.1.31 → 0.1.33
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.
- package/dist/chat-memory.js +29 -27
- package/dist/chat-notify.js +3 -3
- package/dist/github.js +3 -3
- package/dist/index.js +75 -73
- package/dist/integrations.js +1 -1
- package/dist/jira.js +2 -2
- package/dist/lark.js +1 -1
- package/dist/linear.js +14 -14
- package/dist/llm-billing.js +1 -1
- package/dist/package.json +3 -1
- package/dist/plane.js +1 -1
- package/dist/sentry.js +2 -2
- package/dist/slack.js +1 -1
- package/dist/trackers/github-adapter.js +3 -3
- package/dist/trackers/index.js +12 -12
- package/dist/trackers/jira-adapter.js +1 -1
- package/dist/trackers/linear-adapter.js +16 -16
- package/docs/apps/index.md +45 -42
- package/docs/cli-reference.md +2 -2
- package/docs/get-started/install.md +2 -0
- package/docs/get-started/your-first-workflow.md +5 -2
- package/docs/integrations/gitlab.md +43 -0
- package/docs/integrations/lark.md +41 -0
- package/docs/integrations/linear.md +43 -0
- package/docs/integrations/notion.md +33 -0
- package/docs/integrations/plane.md +46 -0
- package/docs/integrations/sentry.md +42 -0
- package/docs/integrations/slack.md +33 -0
- package/docs/intro.md +15 -11
- package/docs/packages/ui-memory.md +1 -1
- package/docs/recipes/bug-autofix.md +85 -0
- package/docs/recipes/github-ai-scout.md +61 -0
- package/docs/recipes/index.md +39 -34
- package/docs/recipes/pipeline-supervisor.md +57 -0
- package/docs/skills/chat-memory.md +39 -10
- package/docs/skills/index.md +4 -0
- package/package.json +3 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var S=process.env.LINEAR_API_URL||"https://api.linear.app/graphql";function
|
|
1
|
+
var k=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",PLANE:"plane",LINEAR:"linear"}),C=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"},plane:{id:"plane",name:"Plane",connectPath:"/integrations?provider=plane"},linear:{id:"linear",name:"Linear",connectPath:"/integrations?provider=linear"}});var S=process.env.LINEAR_API_URL||"https://api.linear.app/graphql";function E(){if(process.env.LINEAR_OAUTH_TOKEN)return`Bearer ${process.env.LINEAR_OAUTH_TOKEN}`;let n=process.env.LINEAR_API_KEY;if(!n)throw new Error("Linear is not connected: set LINEAR_API_KEY (personal API key) or LINEAR_OAUTH_TOKEN.");return n}async function f(n,i={}){let t=await fetch(S,{method:"POST",headers:{Authorization:E(),"Content-Type":"application/json"},body:JSON.stringify({query:n,variables:i})});if(!t.ok){let s=await t.text().catch(()=>"");throw new Error(`Linear API ${t.status}: ${s.slice(0,300)}`)}let e=await t.json().catch(()=>null);if(!e)throw new Error("Linear API returned a non-JSON body");if(Array.isArray(e.errors)&&e.errors.length){let s=e.errors.map(a=>a?.message||String(a)).join("; ");throw new Error(`Linear GraphQL error: ${s.slice(0,300)}`)}return e.data}function h(n){return String(n||"").toLowerCase().replace(/\s+/g,"").replace(/[()\-_::"'`]/g,"")}function v(n,i){let t=h(n),e=h(i);if(!t||!e)return 0;if(t===e)return 1;if(t.length===1||e.length===1)return t===e?1:0;let s=d=>{let c=new Map;for(let y=0;y<d.length-1;y++){let g=d.slice(y,y+2);c.set(g,(c.get(g)||0)+1)}return c},a=s(t),r=s(e),o=0,l=0,m=0;for(let d of a.values())l+=d;for(let d of r.values())m+=d;for(let[d,c]of a.entries())o+=Math.min(c,r.get(d)||0);return 2*o/Math.max(1,l+m)}function T(n,i){let t=Array.isArray(n)?n:[];if(!t.length)return{state:null,strategy:"no-states"};let e=h(i);if(!e)return{state:null,strategy:"no-target"};let s=t.find(o=>h(o.name)===e);if(s)return{state:s,strategy:"exact"};let a={backlog:["backlog"],unstarted:["todo","unstarted","open"],started:["inprogress","started","doing","wip"],completed:["done","completed","closed","resolved","fixed"],canceled:["canceled","cancelled","wontfix","won'tfix"],triage:["triage"]};for(let[o,l]of Object.entries(a)){if(!l.some(d=>h(d)===e))continue;let m=t.find(d=>d.type===o);if(m)return{state:m,strategy:"type-alias"}}let r=t.map(o=>({s:o,score:v(i,o.name)})).sort((o,l)=>l.score-o.score);return r[0]&&r[0].score>=.5?{state:r[0].s,strategy:"fuzzy"}:{state:null,strategy:"no-match"}}var O=`
|
|
2
2
|
id
|
|
3
3
|
identifier
|
|
4
4
|
number
|
|
@@ -12,7 +12,7 @@ var S=process.env.LINEAR_API_URL||"https://api.linear.app/graphql";function L(){
|
|
|
12
12
|
assignee { id name displayName email }
|
|
13
13
|
labels { nodes { id name color } }
|
|
14
14
|
team { id key name }
|
|
15
|
-
`,
|
|
15
|
+
`,I={id:"linear",serverName:"linear",allowedTools:["mcp__linear__*"],requiresIntegration:k.LINEAR,envKeys:["LINEAR_API_KEY","LINEAR_OAUTH_TOKEN"],description:"Linear \u2014 issues, comments, workflow states (GraphQL API key)",promptFragment:`## Linear (connected)
|
|
16
16
|
You have direct access to the user's Linear workspace (GraphQL API). Tools:
|
|
17
17
|
|
|
18
18
|
### Discovery
|
|
@@ -30,32 +30,32 @@ You have direct access to the user's Linear workspace (GraphQL API). Tools:
|
|
|
30
30
|
|
|
31
31
|
### Notes
|
|
32
32
|
- Always resolve a team first when you need states or want to create/move issues by state name \u2014 states only make sense within their team.
|
|
33
|
-
- Issue identifier (ENG-123) and internal id (uuid) are both accepted by get/update tools.`,resolve(){let
|
|
33
|
+
- Issue identifier (ENG-123) and internal id (uuid) are both accepted by get/update tools.`,resolve(){let n={};for(let i of this.envKeys)process.env[i]&&(n[i]=process.env[i]);return process.env.LINEAR_API_URL&&(n.LINEAR_API_URL=process.env.LINEAR_API_URL),{command:null,args:[],env:n,description:this.description}},async handleToolCall(n,i){try{switch(n){case"linear_list_teams":{let e=(await f(`
|
|
34
34
|
query Teams($first: Int) {
|
|
35
35
|
teams(first: $first) {
|
|
36
36
|
nodes { id key name description }
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
-
`,{first:i?.limit||50}))?.teams?.nodes||[];return JSON.stringify({count:e.length,teams:e})}case"linear_list_states":{let{teamId:t,teamKey:e}=i||{},
|
|
39
|
+
`,{first:i?.limit||50}))?.teams?.nodes||[];return JSON.stringify({count:e.length,teams:e})}case"linear_list_states":{let{teamId:t,teamKey:e}=i||{},s=t;if(!s&&e&&(s=await N(e)),s){let l=(await f(`
|
|
40
40
|
query States($teamId: String!) {
|
|
41
41
|
team(id: $teamId) {
|
|
42
42
|
id key name
|
|
43
43
|
states { nodes { id name type color position } }
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
-
`,{teamId:
|
|
46
|
+
`,{teamId:s}))?.team,m=(l?.states?.nodes||[]).slice().sort((d,c)=>(d.position||0)-(c.position||0));return JSON.stringify({team:l?{id:l.id,key:l.key,name:l.name}:null,count:m.length,states:m})}let r=(await f(`
|
|
47
47
|
query AllStates($first: Int) {
|
|
48
48
|
workflowStates(first: $first) {
|
|
49
49
|
nodes { id name type color team { id key name } }
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
-
`,{first:i?.limit||200}))?.workflowStates?.nodes||[];return JSON.stringify({scope:"workspace",count:r.length,states:r})}case"linear_list_labels":{let{teamId:t}=i||{},
|
|
52
|
+
`,{first:i?.limit||200}))?.workflowStates?.nodes||[];return JSON.stringify({scope:"workspace",count:r.length,states:r})}case"linear_list_labels":{let{teamId:t}=i||{},s=(await f(`
|
|
53
53
|
query Labels($first: Int, $filter: IssueLabelFilter) {
|
|
54
54
|
issueLabels(first: $first, filter: $filter) {
|
|
55
55
|
nodes { id name color team { id key } }
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
-
`,{first:i?.limit||100,filter:t?{team:{id:{eq:t}}}:void 0}))?.issueLabels?.nodes||[];return JSON.stringify({count:
|
|
58
|
+
`,{first:i?.limit||100,filter:t?{team:{id:{eq:t}}}:void 0}))?.issueLabels?.nodes||[];return JSON.stringify({count:s.length,labels:s})}case"linear_list_issues":{let{teamId:t,teamKey:e,stateId:s,stateName:a,label:r,assigneeId:o,updatedAfter:l,limit:m}=i||{},d={},c=t;!c&&e&&(c=await N(e)),c&&(d.team={id:{eq:c}}),s?d.state={id:{eq:s}}:a&&(d.state={name:{eqIgnoreCase:a}}),r&&(d.labels={name:{eqIgnoreCase:r}}),o&&(d.assignee={id:{eq:o}}),l&&(d.updatedAt={gt:l});let g=((await f(`
|
|
59
59
|
query Issues($first: Int, $filter: IssueFilter, $orderBy: PaginationOrderBy) {
|
|
60
60
|
issues(first: $first, filter: $filter, orderBy: $orderBy) {
|
|
61
61
|
nodes {
|
|
@@ -67,48 +67,48 @@ You have direct access to the user's Linear workspace (GraphQL API). Tools:
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
-
`,{first:m||30,filter:Object.keys(d).length?d:void 0,orderBy:"updatedAt"}))?.issues?.nodes||[]).map(u=>({id:u.id,identifier:u.identifier,number:u.number,title:u.title,url:u.url,priority:u.priority,state:u.state?.name,stateType:u.state?.type,assignee:u.assignee?.displayName||null,labels:(u.labels?.nodes||[]).map(p=>p.name),team:u.team?.key,createdAt:u.createdAt,updatedAt:u.updatedAt}));return JSON.stringify({count:g.length,issues:g})}case"linear_get_issue":{let t=i?.issueId||i?.identifier||i?.issueKey;if(!t)return JSON.stringify({error:"issueId or identifier is required"});let e=await
|
|
70
|
+
`,{first:m||30,filter:Object.keys(d).length?d:void 0,orderBy:"updatedAt"}))?.issues?.nodes||[]).map(u=>({id:u.id,identifier:u.identifier,number:u.number,title:u.title,url:u.url,priority:u.priority,state:u.state?.name,stateType:u.state?.type,assignee:u.assignee?.displayName||null,labels:(u.labels?.nodes||[]).map(p=>p.name),team:u.team?.key,createdAt:u.createdAt,updatedAt:u.updatedAt}));return JSON.stringify({count:g.length,issues:g})}case"linear_get_issue":{let t=i?.issueId||i?.identifier||i?.issueKey;if(!t)return JSON.stringify({error:"issueId or identifier is required"});let e=await _(t);return JSON.stringify(e?{id:e.id,identifier:e.identifier,number:e.number,title:e.title,description:e.description||"",url:e.url,priority:e.priority,state:e.state?.name,stateId:e.state?.id,stateType:e.state?.type,assignee:e.assignee?.displayName||e.assignee?.name||null,assigneeId:e.assignee?.id||null,labels:(e.labels?.nodes||[]).map(s=>s.name),team:e.team?{id:e.team.id,key:e.team.key,name:e.team.name}:null,createdAt:e.createdAt,updatedAt:e.updatedAt}:{error:`Issue not found: ${t}`})}case"linear_get_comments":{let t=i?.issueId||i?.identifier||i?.issueKey;if(!t)return JSON.stringify({error:"issueId or identifier is required"});let e=await _(t,`
|
|
71
71
|
id identifier
|
|
72
72
|
comments(first: ${Number(i?.limit)||50}) {
|
|
73
73
|
nodes { id body createdAt updatedAt user { id name displayName } }
|
|
74
74
|
}
|
|
75
|
-
`);if(!e)return JSON.stringify({error:`Issue not found: ${t}`});let
|
|
75
|
+
`);if(!e)return JSON.stringify({error:`Issue not found: ${t}`});let s=(e.comments?.nodes||[]).map(a=>({id:a.id,author:a.user?.displayName||a.user?.name||"Unknown",body:a.body||"",createdAt:a.createdAt,updatedAt:a.updatedAt})).sort((a,r)=>String(r.createdAt).localeCompare(String(a.createdAt)));return JSON.stringify({count:s.length,issue:e.identifier,comments:s})}case"linear_add_comment":{let t=i?.issueId||i?.identifier||i?.issueKey,e=i?.body;if(!t||!e)return JSON.stringify({error:"issueId/identifier and body are required"});let s=await _(t,"id identifier");if(!s)return JSON.stringify({error:`Issue not found: ${t}`});let r=(await f(`
|
|
76
76
|
mutation AddComment($input: CommentCreateInput!) {
|
|
77
77
|
commentCreate(input: $input) {
|
|
78
78
|
success
|
|
79
79
|
comment { id url createdAt }
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
-
`,{input:{issueId:
|
|
82
|
+
`,{input:{issueId:s.id,body:e}}))?.commentCreate;return JSON.stringify({ok:!!r?.success,commentId:r?.comment?.id,url:r?.comment?.url})}case"linear_update_state":{let t=i?.issueId||i?.identifier||i?.issueKey,{stateId:e,stateName:s,toStatus:a,status:r}=i||{};if(!t)return JSON.stringify({error:"issueId or identifier is required"});let o=await _(t,`
|
|
83
83
|
id identifier
|
|
84
84
|
state { id name type }
|
|
85
85
|
team { id key states { nodes { id name type position } } }
|
|
86
|
-
`);if(!o)return JSON.stringify({error:`Issue not found: ${t}`});let l=e,m=e?{strategy:"explicit-id"}:null;if(!l){let y=String(a||
|
|
86
|
+
`);if(!o)return JSON.stringify({error:`Issue not found: ${t}`});let l=e,m=e?{strategy:"explicit-id"}:null;if(!l){let y=String(s||a||r||"").trim(),g=(o.team?.states?.nodes||[]).slice().sort((p,L)=>(p.position||0)-(L.position||0));if(!y)return JSON.stringify({ok:!1,error:"stateId or stateName/toStatus is required",issue:o.identifier,availableStates:g.map(p=>({id:p.id,name:p.name,type:p.type}))});let u=T(g,y);if(!u.state)return JSON.stringify({ok:!1,error:`No workflow state matches "${y}" in team ${o.team?.key}`,issue:o.identifier,availableStates:g.map(p=>({id:p.id,name:p.name,type:p.type}))});l=u.state.id,m={strategy:u.strategy,matchedName:u.state.name}}let c=(await f(`
|
|
87
87
|
mutation MoveIssue($id: String!, $input: IssueUpdateInput!) {
|
|
88
88
|
issueUpdate(id: $id, input: $input) {
|
|
89
89
|
success
|
|
90
90
|
issue { id identifier state { id name type } }
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
|
-
`,{id:o.id,input:{stateId:l}}))?.issueUpdate;return JSON.stringify({ok:!!c?.success,issue:c?.issue?.identifier||o.identifier,stateAfter:c?.issue?.state?.name||null,stateTypeAfter:c?.issue?.state?.type||null,resolution:m})}case"linear_link_attachment":{let t=i?.issueId||i?.identifier||i?.issueKey,{url:e,title:
|
|
93
|
+
`,{id:o.id,input:{stateId:l}}))?.issueUpdate;return JSON.stringify({ok:!!c?.success,issue:c?.issue?.identifier||o.identifier,stateAfter:c?.issue?.state?.name||null,stateTypeAfter:c?.issue?.state?.type||null,resolution:m})}case"linear_link_attachment":{let t=i?.issueId||i?.identifier||i?.issueKey,{url:e,title:s,subtitle:a}=i||{};if(!t||!e)return JSON.stringify({error:"issueId/identifier and url are required"});let r=await _(t,"id identifier");if(!r)return JSON.stringify({error:`Issue not found: ${t}`});let l=(await f(`
|
|
94
94
|
mutation LinkAttachment($input: AttachmentCreateInput!) {
|
|
95
95
|
attachmentCreate(input: $input) {
|
|
96
96
|
success
|
|
97
97
|
attachment { id url title }
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
|
-
`,{input:{issueId:r.id,url:e,title:
|
|
100
|
+
`,{input:{issueId:r.id,url:e,title:s||e,subtitle:a||void 0}}))?.attachmentCreate;return JSON.stringify({ok:!!l?.success,attachmentId:l?.attachment?.id,url:l?.attachment?.url})}default:return JSON.stringify({error:`Unknown tool: ${n}`})}}catch(t){return JSON.stringify({error:t.message})}},tools:[{name:"linear_list_teams",description:"List Linear teams (id, key, name). Needed to scope workflow states and issue queries.",input_schema:{type:"object",properties:{limit:{type:"number",description:"Max teams (default: 50)"}}}},{name:"linear_list_states",description:"List a team's workflow states (id, name, type: backlog|unstarted|started|completed|canceled|triage). Linear states are PER-TEAM. Omit team to list all states across the workspace.",input_schema:{type:"object",properties:{teamId:{type:"string",description:"Team uuid"},teamKey:{type:"string",description:"Team key (e.g. ENG); resolved to an id if teamId omitted"},limit:{type:"number",description:"Max states when listing workspace-wide (default: 200)"}}}},{name:"linear_list_labels",description:"List issue labels, optionally scoped to a team.",input_schema:{type:"object",properties:{teamId:{type:"string",description:"Optional team uuid to scope labels"},limit:{type:"number",description:"Max labels (default: 100)"}}}},{name:"linear_list_issues",description:"List/poll Linear issues filtered by team, state, label, assignee, and an updatedAfter cursor. Returns newest-updated first.",input_schema:{type:"object",properties:{teamId:{type:"string",description:"Team uuid"},teamKey:{type:"string",description:"Team key (e.g. ENG); resolved if teamId omitted"},stateId:{type:"string",description:"Filter by workflow state uuid"},stateName:{type:"string",description:"Filter by state name (case-insensitive)"},label:{type:"string",description:"Filter by label name (case-insensitive)"},assigneeId:{type:"string",description:"Filter by assignee uuid"},updatedAfter:{type:"string",description:"ISO-8601 timestamp; only issues updated after this (polling cursor)"},limit:{type:"number",description:"Max issues (default: 30)"}}}},{name:"linear_get_issue",description:"Get a single Linear issue by identifier (e.g. ENG-123) or internal uuid \u2014 title, description, state, labels, assignee, url.",input_schema:{type:"object",properties:{identifier:{type:"string",description:"Issue identifier, e.g. ENG-123"},issueId:{type:"string",description:"Internal issue uuid (alternative to identifier)"}}}},{name:"linear_get_comments",description:"Get comments on a Linear issue (newest first).",input_schema:{type:"object",properties:{identifier:{type:"string",description:"Issue identifier, e.g. ENG-123"},issueId:{type:"string",description:"Internal issue uuid (alternative to identifier)"},limit:{type:"number",description:"Max comments (default: 50)"}}}},{name:"linear_add_comment",description:"Add a comment to a Linear issue (markdown supported).",input_schema:{type:"object",properties:{identifier:{type:"string",description:"Issue identifier, e.g. ENG-123"},issueId:{type:"string",description:"Internal issue uuid (alternative to identifier)"},body:{type:"string",description:"Comment body (markdown)"}},required:["body"]}},{name:"linear_update_state",description:"Move a Linear issue to a different workflow state. Pass a state NAME (toStatus/stateName) and the tool resolves it to the issue's team's matching state id (exact -> type-alias -> fuzzy), or pass stateId directly. Linear has no transitions \u2014 this sets the state.",input_schema:{type:"object",properties:{identifier:{type:"string",description:"Issue identifier, e.g. ENG-123"},issueId:{type:"string",description:"Internal issue uuid (alternative to identifier)"},stateId:{type:"string",description:"Target workflow state uuid (skips name resolution)"},stateName:{type:"string",description:'Target state name (e.g. "In Progress", "Done")'},toStatus:{type:"string",description:"Alias for stateName"}}}},{name:"linear_link_attachment",description:"Attach a URL (e.g. a GitHub PR) to a Linear issue via native attachments. Use this for PR links; fall back to linear_add_comment if it fails.",input_schema:{type:"object",properties:{identifier:{type:"string",description:"Issue identifier, e.g. ENG-123"},issueId:{type:"string",description:"Internal issue uuid (alternative to identifier)"},url:{type:"string",description:"The URL to attach (e.g. a PR link)"},title:{type:"string",description:"Attachment title (defaults to the URL)"},subtitle:{type:"string",description:"Optional attachment subtitle"}},required:["url"]}}]};async function N(n){return(await f(`
|
|
101
101
|
query TeamByKey($filter: TeamFilter) {
|
|
102
102
|
teams(first: 1, filter: $filter) { nodes { id key } }
|
|
103
103
|
}
|
|
104
|
-
`,{filter:{key:{eq:
|
|
104
|
+
`,{filter:{key:{eq:n}}}))?.teams?.nodes?.[0]?.id||null}async function _(n,i=O){let t=String(n).trim(),e=/^([A-Za-z][A-Za-z0-9]*)-(\d+)$/.exec(t);if(e){let a=e[1].toUpperCase(),r=Number(e[2]);return(await f(`
|
|
105
105
|
query IssueByIdentifier($filter: IssueFilter) {
|
|
106
106
|
issues(first: 1, filter: $filter) {
|
|
107
107
|
nodes { ${i} }
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
|
-
`,{filter:{number:{eq:r},team:{key:{eq:
|
|
110
|
+
`,{filter:{number:{eq:r},team:{key:{eq:a}}}}))?.issues?.nodes?.[0]||null}return(await f(`
|
|
111
111
|
query IssueById($id: String!) {
|
|
112
112
|
issue(id: $id) { ${i} }
|
|
113
113
|
}
|
|
114
|
-
`,{id:t}))?.issue||null}var
|
|
114
|
+
`,{id:t}))?.issue||null}var $={triage:"todo",backlog:"todo",unstarted:"todo",started:"in_progress",completed:"done",canceled:"done"},R=/\b(blocked|on[\s-]?hold|waiting|stuck)\b/i;function w(n){return n?n.name&&R.test(n.name)?"blocked":$[n.type]||"unknown":"unknown"}function b(n){let i=JSON.parse(n);if(i&&i.error)throw new Error(i.error);return i}function A(n){let i=n.state||null,t=n.stateType||null;return{id:String(n.id||n.identifier||""),key:n.identifier||String(n.id||""),title:n.title||"",body:n.description||"",state:i,stateCategory:w(i?{name:i,type:t}:null),assignee:n.assignee||null,url:n.url||null,_raw:n}}var P={id:"linear",toStateCategory:w,toNeutral:A,async listCandidates(n={}){let i=n.ctx||{},t={teamId:i.teamId,teamKey:i.teamKey,stateName:n.state,label:Array.isArray(n.labels)?n.labels[0]:n.labels,assigneeId:i.assigneeId,updatedAfter:n.updatedAfter,limit:n.limit};return(b(await I.handleToolCall("linear_list_issues",t)).issues||[]).map(A)},async getTicket(n){if(!n)throw new Error("key is required");let i=JSON.parse(await I.handleToolCall("linear_get_issue",{identifier:n}));return i.error?null:A(i)},async getComments(n){if(!n)throw new Error("key is required");return(b(await I.handleToolCall("linear_get_comments",{identifier:n})).comments||[]).map(t=>({id:String(t.id),author:t.author||"Unknown",body:t.body||"",createdAt:t.createdAt||null,updatedAt:t.updatedAt||null,_raw:t}))},async addComment(n,i){if(!n||!i)throw new Error("key and body are required");let t=b(await I.handleToolCall("linear_add_comment",{identifier:n,body:i}));return{ok:!!t.ok,id:t.commentId||null}},async transition(n,i){if(!n)throw new Error("key is required");let t=JSON.parse(await I.handleToolCall("linear_update_state",{identifier:n,toStatus:i}));if(!t.ok)return{ok:!1,error:t.error||"state update failed",_raw:t};let e=t.stateAfter||null;return{ok:!0,stateAfter:e,stateCategoryAfter:w(e?{name:e,type:t.stateTypeAfter}:null),_raw:t}},async linkPullRequest(n,i,t){if(!n||!i)throw new Error("key and prUrl are required");try{if(b(await I.handleToolCall("linear_link_attachment",{identifier:n,url:i,title:t||i})).ok)return{ok:!0,via:"attachment"};throw new Error("attachmentCreate returned ok:false")}catch(e){let s=`${t?`${t}: `:"Linked PR: "}${i}`;return{ok:!!b(await I.handleToolCall("linear_add_comment",{identifier:n,body:s})).ok,via:"comment",error:String(e?.message||e)}}}},K=P;export{K as default,P as linearAdapter};
|
package/docs/apps/index.md
CHANGED
|
@@ -24,17 +24,17 @@ There are two ways to land a container on the apps fleet, and you pick by **whet
|
|
|
24
24
|
| Source | Curated bundle (image + EFS layout + defaults) | Free-form natural-language install |
|
|
25
25
|
| Time-to-live | ~45-90 s | 2-15 min (Claude writes + runs the install script) |
|
|
26
26
|
| Licensing | Pre-cleared by Zibby | You direct the install; you accept the upstream license |
|
|
27
|
-
| Best for | Anything in the
|
|
27
|
+
| Best for | Anything in the 22-app catalog | n8n, random GitHub project, anything not in the catalog |
|
|
28
28
|
|
|
29
29
|
Both paths land in the same shape — Fargate task, per-instance EFS volume, ALB target group, agent-ops sidecar — and look identical to every downstream `zibby app logs/status/upgrade` command. The only difference is **who wrote the install recipe**.
|
|
30
30
|
|
|
31
31
|
See [Goal-mode deploys](./goal-mode) for the long form.
|
|
32
32
|
|
|
33
|
-
## Why apps (not
|
|
33
|
+
## Why apps (not agents)
|
|
34
34
|
|
|
35
35
|
Both are pillars of Zibby Cloud. Pick by **how long the thing needs to run**:
|
|
36
36
|
|
|
37
|
-
| | **
|
|
37
|
+
| | **Agent** | **App** |
|
|
38
38
|
|---|---|---|
|
|
39
39
|
| Lifetime | Per-trigger (seconds to minutes) | Long-lived (24/7 or paused) |
|
|
40
40
|
| Surface | A graph of agent CLI calls | A whole open-source application |
|
|
@@ -42,7 +42,7 @@ Both are pillars of Zibby Cloud. Pick by **how long the thing needs to run**:
|
|
|
42
42
|
| Persistence | Session JSONL + S3 artifacts | Encrypted-at-rest EFS volume |
|
|
43
43
|
| Best for | "When ticket lands, classify it" | "Host Grafana for the team" |
|
|
44
44
|
|
|
45
|
-
If you find yourself wanting to **run an open-source web app behind a stable URL**, that's an App. If you want **agent-driven business logic that fires on events**, that's a
|
|
45
|
+
If you find yourself wanting to **run an open-source web app behind a stable URL**, that's an App. If you want **agent-driven business logic that fires on events**, that's an [Agent](../recipes/) (a workflow graph under the hood).
|
|
46
46
|
|
|
47
47
|
## What you get with every app
|
|
48
48
|
|
|
@@ -57,58 +57,61 @@ If you find yourself wanting to **run an open-source web app behind a stable URL
|
|
|
57
57
|
|
|
58
58
|
## The catalog
|
|
59
59
|
|
|
60
|
-
Each catalog entry is a curated bundle: container image, EFS volume layout, ALB wiring, secrets pattern, resource defaults. Today's catalog is **
|
|
60
|
+
Each catalog entry is a curated bundle: container image, EFS volume layout, ALB wiring, secrets pattern, resource defaults. Today's catalog is **22 apps**. Run `zibby app templates` for the canonical, always-up-to-date list with live tier + hourly rate — the sample below is a snapshot, grouped by what each app is for.
|
|
61
61
|
|
|
62
62
|
### AI
|
|
63
63
|
|
|
64
|
-
| App |
|
|
65
|
-
|
|
66
|
-
| **Open WebUI** |
|
|
67
|
-
| **OpenHands** |
|
|
68
|
-
| **Gas Town** |
|
|
64
|
+
| App | What it does |
|
|
65
|
+
|---|---|
|
|
66
|
+
| **Open WebUI** | ChatGPT-style UI for Ollama / OpenAI-compatible endpoints |
|
|
67
|
+
| **OpenHands** | AI software-engineer agent — drives the repo end-to-end, GitHub PR workflows |
|
|
68
|
+
| **Gas Town** | Multi-agent workspace — coordinate Claude, Codex, Cursor, Gemini |
|
|
69
|
+
| **Open Design** | Local-first design-artifact generator via your installed coding-agent CLIs |
|
|
70
|
+
| **Plane** | Self-hosted Jira / Linear alternative — issues, cycles, modules, pages |
|
|
69
71
|
|
|
70
|
-
###
|
|
72
|
+
### Automation
|
|
71
73
|
|
|
72
|
-
| App |
|
|
73
|
-
|
|
74
|
-
| **
|
|
75
|
-
| **
|
|
76
|
-
| **
|
|
74
|
+
| App | What it does |
|
|
75
|
+
|---|---|
|
|
76
|
+
| **Activepieces** | Open-source Zapier alternative — visual automation, 400+ MCP servers |
|
|
77
|
+
| **ChangeDetection.io** | Watch any web page for changes — notifies on diff |
|
|
78
|
+
| **Gotify** | Self-hosted push-notification + webhook server |
|
|
77
79
|
|
|
78
|
-
###
|
|
80
|
+
### Data + APIs
|
|
79
81
|
|
|
80
|
-
| App |
|
|
81
|
-
|
|
82
|
-
| **
|
|
83
|
-
| **
|
|
84
|
-
| **
|
|
82
|
+
| App | What it does |
|
|
83
|
+
|---|---|
|
|
84
|
+
| **PostgREST** | Serverless REST API on top of any Postgres schema |
|
|
85
|
+
| **Mathesar** | Spreadsheet-style web UI for Postgres |
|
|
86
|
+
| **PocketBase** | Single-file backend — SQLite + REST + realtime + auth + admin UI |
|
|
85
87
|
|
|
86
|
-
###
|
|
88
|
+
### Productivity + docs
|
|
87
89
|
|
|
88
|
-
| App |
|
|
89
|
-
|
|
90
|
-
| **
|
|
91
|
-
| **
|
|
92
|
-
| **
|
|
93
|
-
| **
|
|
94
|
-
| **
|
|
90
|
+
| App | What it does |
|
|
91
|
+
|---|---|
|
|
92
|
+
| **Docmost** | Real-time collaborative wiki (multi-service: web + Postgres + Redis) |
|
|
93
|
+
| **SiYuan** | Privacy-first, block-based note-taking / PKM, local-first |
|
|
94
|
+
| **draw.io** | Client-side diagram editor (flowcharts, UML, ER, network) |
|
|
95
|
+
| **Glance** | Self-hosted homepage / dashboard with a feed of RSS, GitHub, monitors |
|
|
96
|
+
| **Homepage** | Self-hosted dashboard with service integrations + bookmarks |
|
|
95
97
|
|
|
96
|
-
###
|
|
98
|
+
### Observability
|
|
97
99
|
|
|
98
|
-
| App |
|
|
99
|
-
|
|
100
|
-
| **
|
|
101
|
-
| **
|
|
100
|
+
| App | What it does |
|
|
101
|
+
|---|---|
|
|
102
|
+
| **Grafana** | Dashboards for metrics, logs, traces |
|
|
103
|
+
| **OpenObserve** | Petabyte-scale logs + metrics + traces in one binary |
|
|
104
|
+
| **Uptime Kuma** | Self-hosted uptime monitor + status page |
|
|
105
|
+
| **Beszel** | Lightweight server monitor with historical charts |
|
|
102
106
|
|
|
103
|
-
###
|
|
107
|
+
### Identity
|
|
104
108
|
|
|
105
|
-
| App |
|
|
106
|
-
|
|
107
|
-
| **
|
|
108
|
-
| **
|
|
109
|
-
| **Gotify** | Light | $0.05/hr | Self-hosted push-notification + webhook server |
|
|
109
|
+
| App | What it does |
|
|
110
|
+
|---|---|
|
|
111
|
+
| **Authentik** | Self-hosted SSO / IdP (OAuth/SAML/LDAP) |
|
|
112
|
+
| **ZITADEL** | Cloud-native identity + access management (OAuth2/OIDC/SAML/LDAP) |
|
|
110
113
|
|
|
111
|
-
`zibby app templates` is the canonical, always-up-to-date list — the
|
|
114
|
+
`zibby app templates` is the canonical, always-up-to-date list — the tables above are a snapshot, and the live command is the source of truth for each app's tier and hourly rate.
|
|
112
115
|
|
|
113
116
|
### Multi-service entries
|
|
114
117
|
|
package/docs/cli-reference.md
CHANGED
|
@@ -245,7 +245,7 @@ Bare init by default — writes `.zibby.config.mjs`, sets up agent credentials,
|
|
|
245
245
|
Common options:
|
|
246
246
|
- `-t, --template <name>` — workflow template to scaffold (see `zibby template list`). Default: none (config + creds only).
|
|
247
247
|
- `--agent <claude|cursor|codex|gemini>` — pick the agent up front instead of prompting
|
|
248
|
-
- `--memory-backend <dolt
|
|
248
|
+
- `--memory-backend <mem0|dolt>` — memory backend (default: `mem0` — semantic vector memory, billed through the agent run in cloud, falls back to `dolt` if the embedding proxy is unavailable; pass `dolt` for self-contained structured memory — see [Chat memory](./skills/chat-memory.md))
|
|
249
249
|
- `--skip-install` / `--skip-memory` — skip `npm install` / skip memory setup
|
|
250
250
|
- `-f, --force` — overwrite existing config
|
|
251
251
|
- `--api-key <key>` — non-interactive Zibby API key (for `--cloud-sync`)
|
|
@@ -267,7 +267,7 @@ Options on `add`:
|
|
|
267
267
|
|
|
268
268
|
## App commands {#app-commands}
|
|
269
269
|
|
|
270
|
-
`zibby app` manages [Managed App instances](./apps/) — hosted open-source tools (Grafana, Open WebUI, Docmost, OpenHands, and
|
|
270
|
+
`zibby app` manages [Managed App instances](./apps/) — hosted open-source tools (Grafana, Open WebUI, Docmost, OpenHands, and 18 more in the catalog, plus anything you install via [goal-mode](./apps/goal-mode)) with an autonomous agent-ops sidecar. Each verb is keyed by **instance ID** (`a1b2c3d4`-style); `zibby app list` shows IDs alongside display names.
|
|
271
271
|
|
|
272
272
|
| Command | What it does |
|
|
273
273
|
|---|---|
|
|
@@ -7,6 +7,8 @@ pagination_next: get-started/your-first-workflow
|
|
|
7
7
|
|
|
8
8
|
# Install the CLI
|
|
9
9
|
|
|
10
|
+
You build **Agents** with Zibby — deployed automations, each a workflow graph under the hood. The CLI command is `zibby agent` (`zibby workflow` still works as an alias, and is used throughout these walkthroughs).
|
|
11
|
+
|
|
10
12
|
```bash
|
|
11
13
|
npm install -g @zibby/cli
|
|
12
14
|
```
|
|
@@ -5,10 +5,13 @@ pagination_prev: get-started/install
|
|
|
5
5
|
pagination_next: get-started/run-locally
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
# Scaffold
|
|
8
|
+
# Scaffold your first agent
|
|
9
|
+
|
|
10
|
+
An **Agent** is a deployed automation — a workflow graph of agent-CLI calls. Scaffold one with the CLI (the command is `zibby agent`; `zibby workflow` is a still-working alias):
|
|
9
11
|
|
|
10
12
|
```bash
|
|
11
|
-
zibby
|
|
13
|
+
zibby agent new my-pipeline
|
|
14
|
+
# zibby workflow new my-pipeline # alias — identical
|
|
12
15
|
```
|
|
13
16
|
|
|
14
17
|
This creates:
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 5
|
|
3
|
+
title: GitLab Integration
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# GitLab Integration
|
|
7
|
+
|
|
8
|
+
Connect a **self-hosted GitLab** instance (or GitLab SaaS) so your agents can read repos, merge requests, issues, and pipelines. GitLab uses a **Personal Access Token**.
|
|
9
|
+
|
|
10
|
+
## How It Works
|
|
11
|
+
|
|
12
|
+
You provide your GitLab instance URL and a Personal Access Token. The `gitlab` skill then exposes GitLab's API to any node that declares it. Outbound calls to a firewalled self-hosted instance can be pinned to a [dedicated egress IP](../cloud/dedicated-egress.md).
|
|
13
|
+
|
|
14
|
+
## Connect GitLab
|
|
15
|
+
|
|
16
|
+
1. In GitLab, go to **User Settings → Access Tokens** and create a Personal Access Token with the `read_api` and `read_repository` scopes (form `glpat-...`)
|
|
17
|
+
2. In the Zibby dashboard, go to **Settings → Integrations** and click **Connect GitLab**
|
|
18
|
+
3. Enter your **GitLab Instance URL** (e.g. `https://gitlab.company.com`)
|
|
19
|
+
4. Paste the **Personal Access Token**
|
|
20
|
+
5. Click **Connect** — Zibby validates both before saving
|
|
21
|
+
|
|
22
|
+
| Field | Required | Notes |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| GitLab Instance URL | Yes | Your instance, e.g. `https://gitlab.company.com` (or `https://gitlab.com`) |
|
|
25
|
+
| Personal Access Token | Yes | `glpat-...` with `read_api` + `read_repository` scopes |
|
|
26
|
+
|
|
27
|
+
## What Agents Can Do
|
|
28
|
+
|
|
29
|
+
Nodes that attach the [`gitlab` skill](../skills/index.md) get tools for repos, merge requests, issues, and pipelines, against either self-hosted GitLab or GitLab SaaS.
|
|
30
|
+
|
|
31
|
+
## Reference keys (SDK / CLI)
|
|
32
|
+
|
|
33
|
+
When running workflows directly, the connector reads `GITLAB_TOKEN` (and a base URL for self-hosted instances) from the environment.
|
|
34
|
+
|
|
35
|
+
## Firewalled instances
|
|
36
|
+
|
|
37
|
+
If your GitLab is behind a firewall, add the [dedicated egress IP addon](../cloud/dedicated-egress.md) so all outbound traffic comes from one whitelistable IP.
|
|
38
|
+
|
|
39
|
+
## Troubleshooting
|
|
40
|
+
|
|
41
|
+
**"401 Unauthorized"** — regenerate the token with `read_api` + `read_repository` scopes; expired or under-scoped tokens fail here.
|
|
42
|
+
|
|
43
|
+
**"Could not reach instance"** — confirm the instance URL is reachable from Zibby's egress IP and that it points at the GitLab base URL, not a project page.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 8
|
|
3
|
+
title: Lark Integration
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Lark Integration
|
|
7
|
+
|
|
8
|
+
Connect a Lark (Feishu) self-built app so your agents can send messages, reply, list chats, and read chat history. Lark uses an **App ID + App Secret**.
|
|
9
|
+
|
|
10
|
+
## How It Works
|
|
11
|
+
|
|
12
|
+
You create a self-built app in the Lark Developer Console and paste its App ID and App Secret into Zibby. Optionally add a Verification Token and Encrypt Key if you want inbound Lark events to trigger Zibby workflows. The `lark` skill then exposes Lark's API to any node that declares it.
|
|
13
|
+
|
|
14
|
+
## Connect Lark
|
|
15
|
+
|
|
16
|
+
1. In the Lark Developer Console, open your self-built app and go to **Credentials & Basic Info**
|
|
17
|
+
2. Copy the **App ID** (`cli_...`) and **App Secret**
|
|
18
|
+
3. In the Zibby dashboard, go to **Settings → Integrations** and click **Connect Lark**
|
|
19
|
+
4. Paste the App ID and App Secret
|
|
20
|
+
5. (Optional) Add the **Verification Token** and **Encrypt Key** from **Event Subscriptions** if you want inbound events
|
|
21
|
+
|
|
22
|
+
| Field | Required | Notes |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| App ID | Yes | `cli_...` from Credentials & Basic Info |
|
|
25
|
+
| App Secret | Yes | From Credentials & Basic Info |
|
|
26
|
+
| Verification Token | No | Required only for inbound events (Event Subscriptions) |
|
|
27
|
+
| Encrypt Key | No | Only if Lark-side event encryption is enabled |
|
|
28
|
+
|
|
29
|
+
## What Agents Can Do
|
|
30
|
+
|
|
31
|
+
Nodes that attach the [`lark` skill](../skills/lark.md) get `lark_send_message`, `lark_reply`, `lark_list_chats`, and `lark_get_chat_history` — used by routing recipes like [Sentry triage](../recipes/sentry-triage.md) as a Slack alternative.
|
|
32
|
+
|
|
33
|
+
## Inbound events
|
|
34
|
+
|
|
35
|
+
Adding the Verification Token (and Encrypt Key, if enabled) lets Lark events fire your Zibby workflows. Leave them blank for outbound-only (notifications).
|
|
36
|
+
|
|
37
|
+
## Troubleshooting
|
|
38
|
+
|
|
39
|
+
**"Invalid App Secret"** — regenerate the secret in the Developer Console and reconnect.
|
|
40
|
+
|
|
41
|
+
**Bot can't post to a chat** — the app must be added to the target chat/group and have the messaging permission scopes granted in the console.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 3
|
|
3
|
+
title: Linear Integration
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Linear Integration
|
|
7
|
+
|
|
8
|
+
Connect Linear so your agents can read and update issues, comments, and workflow states. Linear uses a **personal API key** — no OAuth round-trip.
|
|
9
|
+
|
|
10
|
+
## How It Works
|
|
11
|
+
|
|
12
|
+
You paste a single Linear personal API key. Zibby validates it (by querying the authenticated viewer) before storing it, then the `linear` skill exposes Linear's API to any node that declares it.
|
|
13
|
+
|
|
14
|
+
## Connect Linear
|
|
15
|
+
|
|
16
|
+
1. In Linear, go to **Settings → Security & access → Personal API keys**
|
|
17
|
+
2. Create a new key — it has the form `lin_api_...`
|
|
18
|
+
3. In the Zibby dashboard, go to **Settings → Integrations** and click **Connect Linear**
|
|
19
|
+
4. Paste the key and click **Connect**
|
|
20
|
+
|
|
21
|
+
That's it — one field. Zibby verifies the key against Linear's GraphQL API before saving.
|
|
22
|
+
|
|
23
|
+
## What Agents Can Do
|
|
24
|
+
|
|
25
|
+
Once connected, nodes that attach the [`linear` skill](../skills/index.md) get these tools:
|
|
26
|
+
|
|
27
|
+
| Tool | What it does |
|
|
28
|
+
|---|---|
|
|
29
|
+
| `linear_list_issues` | List issues (filter by team, state, assignee, label) |
|
|
30
|
+
| `linear_get_issue` | Fetch one issue with full detail |
|
|
31
|
+
| `linear_add_comment` | Comment on an issue |
|
|
32
|
+
| `linear_update_state` | Move an issue to a different workflow state |
|
|
33
|
+
| `linear_list_teams` / `linear_list_states` / `linear_list_labels` | Resolve names → IDs for the calls above |
|
|
34
|
+
|
|
35
|
+
## Reference key (SDK / CLI)
|
|
36
|
+
|
|
37
|
+
When running workflows directly (local or CI), the same connector reads from the `LINEAR_API_KEY` environment variable instead of the dashboard-stored credential.
|
|
38
|
+
|
|
39
|
+
## Troubleshooting
|
|
40
|
+
|
|
41
|
+
**"Invalid API key"** — regenerate the key in Linear (Settings → Security & access → Personal API keys) and reconnect. Keys are revoked when you remove them from Linear.
|
|
42
|
+
|
|
43
|
+
**Agent can't find a team/state** — call `linear_list_teams` / `linear_list_states` first; Linear's API keys are scoped to whatever the issuing user can see.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 9
|
|
3
|
+
title: Notion Integration
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Notion Integration
|
|
7
|
+
|
|
8
|
+
Connect a Notion workspace so your agents can read and write pages and databases — for example, publishing a rich digest as Notion blocks. Notion uses **OAuth** (workspace install).
|
|
9
|
+
|
|
10
|
+
## How It Works
|
|
11
|
+
|
|
12
|
+
You authorize Zibby against a Notion workspace via OAuth. Notion returns a long-lived bearer token (no refresh, no expiry) scoped to the pages and databases you grant. Zibby stores it encrypted with the workspace metadata. The report renderer then publishes structured digests as native Notion blocks.
|
|
13
|
+
|
|
14
|
+
## Connect Notion
|
|
15
|
+
|
|
16
|
+
1. In the Zibby dashboard, go to **Settings → Integrations** and click **Connect** next to Notion
|
|
17
|
+
2. You're redirected to Notion to authorize Zibby
|
|
18
|
+
3. Select which pages/databases Zibby may access
|
|
19
|
+
4. You're redirected back to Zibby — the card shows the connected workspace
|
|
20
|
+
|
|
21
|
+
## What Agents Can Do
|
|
22
|
+
|
|
23
|
+
Workflows can render a `report` object straight to Notion blocks (`reportToNotionBlocks`) and publish it to a connected page or database — the same digest you can route to Slack or Lark. The `notify-notion` template is the shipped example.
|
|
24
|
+
|
|
25
|
+
## Reference key (SDK / CLI)
|
|
26
|
+
|
|
27
|
+
When running workflows directly, the connector reads `NOTION_API_KEY` from the environment (a Notion internal-integration token works for the SDK path).
|
|
28
|
+
|
|
29
|
+
## Troubleshooting
|
|
30
|
+
|
|
31
|
+
**Agent can't see a page** — Notion access is page-scoped. Re-run the OAuth flow and grant the specific page/database, or share it with the integration from Notion's UI.
|
|
32
|
+
|
|
33
|
+
**Write fails** — confirm the granted pages include write access; read-only grants can't create blocks.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 4
|
|
3
|
+
title: Plane Integration
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Plane Integration
|
|
7
|
+
|
|
8
|
+
Connect [Plane](https://plane.so) — the open-source project-management tool — so your agents can read and write projects, work items, cycles, modules, and comments. Plane uses an **API key**, not OAuth.
|
|
9
|
+
|
|
10
|
+
## How It Works
|
|
11
|
+
|
|
12
|
+
You provide three values: an API key, a workspace slug, and (optionally) a base URL. Zibby validates them (by listing the workspace's projects) before storing, then the `plane` skill talks to Plane's official MCP server.
|
|
13
|
+
|
|
14
|
+
## Connect Plane
|
|
15
|
+
|
|
16
|
+
1. In Plane, go to **Workspace Settings → API tokens** and create a personal API token (form `plane_api_...`)
|
|
17
|
+
2. Note your **workspace slug** — it's the segment in your Plane URL (`app.plane.so/<workspace-slug>/...`)
|
|
18
|
+
3. In the Zibby dashboard, go to **Settings → Integrations** and click **Connect Plane**
|
|
19
|
+
4. Paste the API key and workspace slug
|
|
20
|
+
5. **Base URL** — leave blank for Plane Cloud (`https://api.plane.so`). For self-hosted or Zibby-hosted Plane, set it to your instance's API base.
|
|
21
|
+
|
|
22
|
+
Zibby lists your workspace's projects to confirm the credentials work before saving.
|
|
23
|
+
|
|
24
|
+
| Field | Required | Notes |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| API key | Yes | From Workspace Settings → API tokens |
|
|
27
|
+
| Workspace slug | Yes | The `<slug>` in your Plane URL |
|
|
28
|
+
| Base URL | No | Defaults to `https://api.plane.so` (Plane Cloud). Set for self-hosted. |
|
|
29
|
+
|
|
30
|
+
## What Agents Can Do
|
|
31
|
+
|
|
32
|
+
Nodes that attach the [`plane` skill](../skills/index.md) get tools for projects, work items, cycles, modules, epics, and comments, backed by Plane's official MCP server.
|
|
33
|
+
|
|
34
|
+
## Reference keys (SDK / CLI)
|
|
35
|
+
|
|
36
|
+
When running workflows directly, the connector reads `PLANE_API_KEY`, `PLANE_WORKSPACE_SLUG`, and `PLANE_BASE_URL` from the environment.
|
|
37
|
+
|
|
38
|
+
## Pairs well with hosted Plane
|
|
39
|
+
|
|
40
|
+
Plane is also in the [Managed Apps catalog](../apps/index.md) — you can host a Plane instance on Zibby and point this connector at it via the **Base URL** field.
|
|
41
|
+
|
|
42
|
+
## Troubleshooting
|
|
43
|
+
|
|
44
|
+
**"Workspace not found"** — double-check the workspace slug matches the segment in your Plane URL exactly.
|
|
45
|
+
|
|
46
|
+
**Self-hosted instance rejects the token** — confirm the **Base URL** points at the API base of your instance, not the web UI URL.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 6
|
|
3
|
+
title: Sentry Integration
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Sentry Integration
|
|
7
|
+
|
|
8
|
+
Connect a Sentry organization so your agents can list projects, list issues, and fetch issue detail — read-only. Sentry uses **OAuth 2.0 with PKCE**.
|
|
9
|
+
|
|
10
|
+
## How It Works
|
|
11
|
+
|
|
12
|
+
You authorize Zibby against your Sentry organization via OAuth (PKCE — no client secret). Once connected, the `sentry` skill exposes read-only Sentry tools to any node that declares it. This powers the [Sentry triage](../recipes/sentry-triage.md) agent.
|
|
13
|
+
|
|
14
|
+
## Connect Sentry
|
|
15
|
+
|
|
16
|
+
1. In the Zibby dashboard, go to **Settings → Integrations** and click **Connect Sentry**
|
|
17
|
+
2. You're redirected to Sentry to authorize Zibby
|
|
18
|
+
3. Pick the organization to grant access to
|
|
19
|
+
4. You're redirected back to Zibby
|
|
20
|
+
|
|
21
|
+
## What Agents Can Do
|
|
22
|
+
|
|
23
|
+
Nodes that attach the [`sentry` skill](../skills/sentry.md) get read-only tools:
|
|
24
|
+
|
|
25
|
+
| Tool | What it does |
|
|
26
|
+
|---|---|
|
|
27
|
+
| `sentry_list_projects` | List projects in the connected organization |
|
|
28
|
+
| `sentry_list_issues` | List issues (supports Sentry search syntax, sort, limit) |
|
|
29
|
+
| `sentry_get_issue` | Detailed info for one issue by ID |
|
|
30
|
+
|
|
31
|
+
Access is read-only by design — the triage agent reads issues and routes them via Slack/Lark rather than mutating Sentry.
|
|
32
|
+
|
|
33
|
+
## See also
|
|
34
|
+
|
|
35
|
+
- [Sentry skill reference](../skills/sentry.md) — full tool surface
|
|
36
|
+
- [Sentry triage recipe](../recipes/sentry-triage.md) — the agent that consumes this connector
|
|
37
|
+
|
|
38
|
+
## Troubleshooting
|
|
39
|
+
|
|
40
|
+
**"Reconnect Sentry"** — the OAuth grant was revoked or expired. Go to Settings → Integrations and reconnect.
|
|
41
|
+
|
|
42
|
+
**No issues returned** — check the project filter and that the org you authorized actually owns the project.
|