@zibby/skills 0.1.32 → 0.1.34
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 +4 -3
- package/dist/gitlab.js +19 -0
- package/dist/index.js +141 -120
- 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 +4 -3
- package/dist/trackers/index.js +13 -12
- package/dist/trackers/jira-adapter.js +1 -1
- package/dist/trackers/linear-adapter.js +16 -16
- package/docs/apps/agent-ops.md +6 -6
- package/docs/apps/deploy.md +1 -1
- package/docs/apps/index.md +45 -42
- package/docs/apps/managing.md +1 -1
- package/docs/cli-reference.md +67 -67
- package/docs/cloning-repositories.md +9 -9
- package/docs/cloud/bundles.md +8 -8
- package/docs/cloud/dedicated-egress.md +6 -6
- package/docs/cloud/env-vars.md +29 -29
- package/docs/cloud/limits.md +11 -11
- package/docs/cloud/triggering.md +16 -16
- package/docs/concepts/agents.md +4 -4
- package/docs/concepts/sessions.md +7 -7
- package/docs/concepts/state.md +1 -1
- package/docs/concepts/sub-graphs.md +9 -9
- package/docs/get-started/deploy.md +14 -14
- package/docs/get-started/install.md +5 -3
- package/docs/get-started/run-locally.md +12 -12
- package/docs/get-started/trigger-and-logs.md +14 -14
- package/docs/get-started/use-from-agents.md +17 -17
- package/docs/get-started/your-first-workflow.md +10 -7
- 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 +16 -12
- package/docs/legacy/test-automation.md +2 -2
- package/docs/packages/cli.md +11 -11
- package/docs/packages/core.md +2 -2
- package/docs/packages/mcp-cli.md +18 -18
- package/docs/packages/skills.md +2 -2
- package/docs/packages/ui-memory.md +2 -2
- 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/recipes/sentry-triage.md +7 -7
- package/docs/recipes/test.md +6 -6
- package/docs/skills/browser.md +2 -2
- package/docs/skills/chat-memory.md +40 -11
- package/docs/skills/core-tools.md +1 -1
- package/docs/skills/function-skill.md +1 -1
- package/docs/skills/github.md +2 -2
- package/docs/skills/index.md +4 -0
- package/docs/skills/jira.md +1 -1
- package/docs/skills/lark.md +1 -1
- package/docs/skills/memory.md +2 -2
- package/docs/skills/sentry.md +1 -1
- package/docs/skills/slack.md +2 -2
- 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/agent-ops.md
CHANGED
|
@@ -19,7 +19,7 @@ This is the structural difference between "deploy button on a VM" and **Zibby**.
|
|
|
19
19
|
| **Upgrade orchestration** | on schedule | When a new app version lands in the catalog, agent-ops can run the in-place upgrade on a cron you set. |
|
|
20
20
|
| **Activity log** | every action | One row in the app's "Agent activity" tab, with structured fields you can grep / chart. |
|
|
21
21
|
|
|
22
|
-
Every action lands in DynamoDB as an `app-runs` record — queryable by anything from
|
|
22
|
+
Every action lands in DynamoDB as an `app-runs` record — queryable by anything from an agent node to a Grafana dashboard.
|
|
23
23
|
|
|
24
24
|
## See it in action
|
|
25
25
|
|
|
@@ -85,20 +85,20 @@ Per-instance agent-ops behavior is tunable via env vars (set on the app instance
|
|
|
85
85
|
| `AGENT_OPS_AUTO_UPGRADE` | `false` | If `true`, upgrade automatically when catalog publishes a new version |
|
|
86
86
|
| `AGENT_OPS_NOTIFY_WEBHOOK` | — | URL to POST run records to (any HTTPS endpoint — your own backend, n8n, etc.) |
|
|
87
87
|
|
|
88
|
-
`AGENT_OPS_NOTIFY_WEBHOOK` is how you wire agent-ops into your existing observability stack — fire every run record into your team's #ops Slack via
|
|
88
|
+
`AGENT_OPS_NOTIFY_WEBHOOK` is how you wire agent-ops into your existing observability stack — fire every run record into your team's #ops Slack via an agent trigger, into Datadog via their webhook receiver, or into your own database.
|
|
89
89
|
|
|
90
|
-
## Hooking agent-ops into
|
|
90
|
+
## Hooking agent-ops into an agent
|
|
91
91
|
|
|
92
|
-
The most powerful pattern: a Zibby
|
|
92
|
+
The most powerful pattern: a Zibby agent that runs **on agent-ops events**.
|
|
93
93
|
|
|
94
|
-
Example: when an `oom_recovery` fires, run
|
|
94
|
+
Example: when an `oom_recovery` fires, run an agent that pulls the container's last-100-lines, classifies the crash, and pages whoever owns this app:
|
|
95
95
|
|
|
96
96
|
```bash
|
|
97
97
|
zibby app env set a1b2c3d4 \
|
|
98
98
|
AGENT_OPS_NOTIFY_WEBHOOK=https://api-prod.zibby.app/v1/workflows/<wf-uuid>/trigger
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
-
The
|
|
101
|
+
The agent receives the run record as `input`, can call back to `zibby app logs` / `zibby app status`, and decides what to do. Agent-ops + agents compose into a self-operating fleet — humans only get pinged for genuinely novel failure modes.
|
|
102
102
|
|
|
103
103
|
## Upgrade orchestration
|
|
104
104
|
|
package/docs/apps/deploy.md
CHANGED
|
@@ -16,7 +16,7 @@ npm install -g @zibby/cli
|
|
|
16
16
|
zibby login # OAuth in browser, saves session to ~/.zibby/config.json
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
You also need a project. If you don't have one yet, deploy
|
|
19
|
+
You also need a project. If you don't have one yet, deploy an agent first or create one in the [Zibby dashboard](https://studio.zibby.dev) — apps are scoped to projects so per-instance EFS volumes can be isolated per team.
|
|
20
20
|
|
|
21
21
|
## Browse the catalog
|
|
22
22
|
|
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
|
|
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/).
|
|
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/apps/managing.md
CHANGED
|
@@ -79,7 +79,7 @@ This picks up whatever's currently in your workspace credentials (set via [Setti
|
|
|
79
79
|
|
|
80
80
|
## ENV vars
|
|
81
81
|
|
|
82
|
-
Every app instance has a per-instance encrypted env-var bag, same shape as
|
|
82
|
+
Every app instance has a per-instance encrypted env-var bag, same shape as agent env. Use it for per-instance config (e.g. `N8N_ENCRYPTION_KEY`, `DATABASE_URL` pointing at an external RDS).
|
|
83
83
|
|
|
84
84
|
Set via the dashboard (Apps → instance → ENV tab) or via CLI:
|
|
85
85
|
|