@zibby/skills 0.1.40 → 0.1.42
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/gitlab.d.ts +76 -0
- package/dist/gitlab.js +5 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.js +100 -82
- package/dist/kvMemory.d.ts +78 -0
- package/dist/kvMemory.js +16 -0
- package/dist/notion.d.ts +21 -4
- package/dist/notion.js +5 -5
- package/dist/package.json +1 -1
- package/docs/cloud/editing-prompts.md +79 -0
- package/package.json +1 -1
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export namespace kvMemorySkill {
|
|
2
|
+
let id: string;
|
|
3
|
+
let serverName: string;
|
|
4
|
+
let allowedTools: string[];
|
|
5
|
+
let description: string;
|
|
6
|
+
let promptFragment: string;
|
|
7
|
+
function resolve(): {
|
|
8
|
+
command: any;
|
|
9
|
+
args: any[];
|
|
10
|
+
env: {};
|
|
11
|
+
description: string;
|
|
12
|
+
type?: undefined;
|
|
13
|
+
alwaysLoad?: undefined;
|
|
14
|
+
} | {
|
|
15
|
+
type: string;
|
|
16
|
+
command: string;
|
|
17
|
+
args: any[];
|
|
18
|
+
env: {};
|
|
19
|
+
description: string;
|
|
20
|
+
alwaysLoad: boolean;
|
|
21
|
+
};
|
|
22
|
+
function handleToolCall(name: any, args: any): Promise<string>;
|
|
23
|
+
let tools: ({
|
|
24
|
+
name: string;
|
|
25
|
+
description: string;
|
|
26
|
+
input_schema: {
|
|
27
|
+
type: string;
|
|
28
|
+
properties: {
|
|
29
|
+
key: {
|
|
30
|
+
type: string;
|
|
31
|
+
description: string;
|
|
32
|
+
};
|
|
33
|
+
keyPrefix?: undefined;
|
|
34
|
+
content?: undefined;
|
|
35
|
+
metadata?: undefined;
|
|
36
|
+
};
|
|
37
|
+
required: string[];
|
|
38
|
+
};
|
|
39
|
+
} | {
|
|
40
|
+
name: string;
|
|
41
|
+
description: string;
|
|
42
|
+
input_schema: {
|
|
43
|
+
type: string;
|
|
44
|
+
properties: {
|
|
45
|
+
keyPrefix: {
|
|
46
|
+
type: string;
|
|
47
|
+
description: string;
|
|
48
|
+
};
|
|
49
|
+
key?: undefined;
|
|
50
|
+
content?: undefined;
|
|
51
|
+
metadata?: undefined;
|
|
52
|
+
};
|
|
53
|
+
required: string[];
|
|
54
|
+
};
|
|
55
|
+
} | {
|
|
56
|
+
name: string;
|
|
57
|
+
description: string;
|
|
58
|
+
input_schema: {
|
|
59
|
+
type: string;
|
|
60
|
+
properties: {
|
|
61
|
+
key: {
|
|
62
|
+
type: string;
|
|
63
|
+
description: string;
|
|
64
|
+
};
|
|
65
|
+
content: {
|
|
66
|
+
type: string;
|
|
67
|
+
description: string;
|
|
68
|
+
};
|
|
69
|
+
metadata: {
|
|
70
|
+
type: string;
|
|
71
|
+
description: string;
|
|
72
|
+
};
|
|
73
|
+
keyPrefix?: undefined;
|
|
74
|
+
};
|
|
75
|
+
required: string[];
|
|
76
|
+
};
|
|
77
|
+
})[];
|
|
78
|
+
}
|
package/dist/kvMemory.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import{existsSync as a,readFileSync as p}from"node:fs";import{homedir as l}from"node:os";import{join as y,dirname as u,resolve as d}from"node:path";import{fileURLToPath as m}from"node:url";function f(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let r=u(m(import.meta.url)),e=d(r,"..","bin","mcp-skill.mjs");return a(e)?e:null}function k(){if(process.env.PROJECT_API_TOKEN)return process.env.PROJECT_API_TOKEN;if(process.env.ZIBBY_USER_TOKEN)return process.env.ZIBBY_USER_TOKEN;try{let r=y(l(),".zibby","config.json");return a(r)&&JSON.parse(p(r,"utf-8")).sessionToken||null}catch{return null}}function _(){return process.env.ZIBBY_ACCOUNT_API_URL?process.env.ZIBBY_ACCOUNT_API_URL.replace(/\/$/,""):(process.env.ZIBBY_ENV||"prod")==="local"?"http://localhost:3001":process.env.ZIBBY_PROD_ACCOUNT_API_URL||"https://api-prod.zibby.app"}function v(){return(typeof process.env.WORKFLOW_TYPE=="string"?process.env.WORKFLOW_TYPE.trim():"")||"agent"}function s(r){return`${v()}:${r}`}async function i(r,e){let t=k();if(!t)throw new Error("No backend credential (PROJECT_API_TOKEN). KV memory is only available inside a Zibby run.");let n=`${_()}/credits/review-memory`,o=await fetch(n,{method:"POST",headers:{Authorization:`Bearer ${t}`,"Content-Type":"application/json"},body:JSON.stringify({op:r,...e})});if(!o.ok){let c=await o.text().catch(()=>"");throw new Error(`KV memory ${r} failed (${o.status}): ${c.slice(0,300)}`)}return o.json()}var T={id:"kv-memory",serverName:"kv_memory",allowedTools:["mcp__kv_memory__*"],description:"KV memory \u2014 a private, per-agent persistent key\u2192value store across stateless runs (auto-namespaced)",promptFragment:`## KV Memory (private, per-agent, persistent key-value store)
|
|
2
|
+
You have a PRIVATE per-agent key-value memory that survives across your
|
|
3
|
+
stateless runs. It is automatically namespaced to YOU (this agent type) \u2014 other
|
|
4
|
+
agents cannot see or collide with your entries, and you don't need to prefix
|
|
5
|
+
anything. Just use plain keys.
|
|
6
|
+
|
|
7
|
+
Tools:
|
|
8
|
+
- kv_recall: Recall the value stored under a plain \`key\` (exact match).
|
|
9
|
+
Use at the START of a run to pick up what a prior run of yours recorded.
|
|
10
|
+
- kv_recall_prefix: List entries whose plain \`keyPrefix\` matches
|
|
11
|
+
(e.g. "seen#" to list everything you've marked seen). Capped at 25.
|
|
12
|
+
- kv_store: Store (overwrite) a concise value under a plain \`key\`.
|
|
13
|
+
Use to record durable facts \u2014 e.g. dedup markers, prior decisions, summaries.
|
|
14
|
+
|
|
15
|
+
Your namespace is added for you automatically; pass plain keys like
|
|
16
|
+
"seen#owner/repo#42" or "lastRun".`,resolve(){let r=f();if(!r)return{command:null,args:[],env:{},description:this.description};let e={};for(let t of["PROJECT_API_TOKEN","ZIBBY_ACCOUNT_API_URL","ZIBBY_ENV","ZIBBY_PROD_ACCOUNT_API_URL","ZIBBY_USER_TOKEN","WORKFLOW_TYPE"])process.env[t]&&(e[t]=process.env[t]);return{type:"stdio",command:"node",args:[r,"../dist/kvMemory.js","kvMemorySkill"],env:e,description:this.description,alwaysLoad:!0}},async handleToolCall(r,e){try{switch(r){case"kv_recall":{let t=typeof e?.key=="string"?e.key.trim():"";if(!t)return JSON.stringify({error:"key is required"});let n=await i("recall",{scope:s(t)});return JSON.stringify(n)}case"kv_recall_prefix":{let t=typeof e?.keyPrefix=="string"?e.keyPrefix.trim():"";if(!t)return JSON.stringify({error:"keyPrefix is required"});let n=await i("recall-prefix",{scopePrefix:s(t)});return JSON.stringify(n)}case"kv_store":{let t=typeof e?.key=="string"?e.key.trim():"";if(!t)return JSON.stringify({error:"key is required"});if(typeof e?.content!="string"||e.content.length===0)return JSON.stringify({error:"content is required (non-empty string)"});let n={scope:s(t),content:e.content};e.metadata!=null&&(n.metadata=e.metadata);let o=await i("store",n);return JSON.stringify(o)}default:return JSON.stringify({error:`Unknown tool: ${r}`})}}catch(t){return JSON.stringify({error:t.message})}},tools:[{name:"kv_recall",description:'Recall the value you stored under a plain key (exact match). Your per-agent namespace is added automatically \u2014 pass a plain key like "seen#owner/repo#42".',input_schema:{type:"object",properties:{key:{type:"string",description:'Plain storage key (no namespace prefix needed) \u2014 e.g. "seen#owner/repo#42" or "lastRun".'}},required:["key"]}},{name:"kv_recall_prefix",description:'List your entries whose plain key STARTS WITH a prefix (e.g. "seen#"). Your per-agent namespace is added automatically. Capped at 25.',input_schema:{type:"object",properties:{keyPrefix:{type:"string",description:'Plain key prefix to match (no namespace prefix needed) \u2014 e.g. "seen#".'}},required:["keyPrefix"]}},{name:"kv_store",description:"Store (overwrite) a value under a plain key so a later run of yours can recall it. Your per-agent namespace is added automatically.",input_schema:{type:"object",properties:{key:{type:"string",description:"Plain storage key (no namespace prefix needed). Same key you recall by."},content:{type:"string",description:"The value to persist. Free-form markdown/text."},metadata:{type:"object",description:"Optional structured metadata."}},required:["key","content"]}}]};export{T as kvMemorySkill};
|
package/dist/notion.d.ts
CHANGED
|
@@ -23,15 +23,32 @@ export function notionApi(path: any, opts?: {}): Promise<any>;
|
|
|
23
23
|
export namespace notionSkill {
|
|
24
24
|
let id: string;
|
|
25
25
|
let serverName: string;
|
|
26
|
+
let allowedTools: string[];
|
|
26
27
|
let requiresIntegration: "notion";
|
|
27
28
|
let description: string;
|
|
28
29
|
let promptFragment: string;
|
|
29
30
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
31
|
+
* Spawn the GENERIC skill MCP server (bin/mcp-skill.mjs) pointing at this
|
|
32
|
+
* module's notionSkill export, so the AGENT gets real mcp__notion__* tools
|
|
33
|
+
* and can pull a referenced page itself (the agent-driven code-review flow).
|
|
34
|
+
*
|
|
35
|
+
* Previously this returned null (no MCP server) because Notion was only ever
|
|
36
|
+
* called by deterministic node code via handleToolCall — there was no agent
|
|
37
|
+
* tool surface. Now the review agent gathers context itself, so it needs the
|
|
38
|
+
* tools. The bin imports ../dist/notion.js (resolved relative to bin/, like
|
|
39
|
+
* mcp-sentry.mjs) and dispatches through handleToolCall; auth flows through
|
|
40
|
+
* the INHERITED env (PROJECT_API_TOKEN → resolveIntegrationToken('notion')),
|
|
41
|
+
* so no provider-specific env keys are needed here. When unconnected,
|
|
42
|
+
* handleToolCall returns { ok:false, error } — the agent tolerates it.
|
|
33
43
|
*/
|
|
34
|
-
function resolve():
|
|
44
|
+
function resolve(): {
|
|
45
|
+
type: string;
|
|
46
|
+
command: string;
|
|
47
|
+
args: any[];
|
|
48
|
+
env: {};
|
|
49
|
+
description: string;
|
|
50
|
+
alwaysLoad: boolean;
|
|
51
|
+
};
|
|
35
52
|
function handleToolCall(name: any, args: any): Promise<string>;
|
|
36
53
|
let tools: ({
|
|
37
54
|
name: string;
|
package/dist/notion.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import{resolveIntegrationToken as
|
|
1
|
+
import{existsSync as $}from"fs";import{fileURLToPath as A}from"url";import{dirname as w,resolve as x}from"path";import{resolveIntegrationToken as I,clearTokenCache as O}from"@zibby/core/backend-client.js";var g=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",FIGMA:"figma"}),j=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"},figma:{id:"figma",name:"Figma",connectPath:"/integrations?provider=figma"}});function R(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let t=w(A(import.meta.url)),a=x(t,"..","bin","mcp-skill.mjs");return $(a)?a:null}var S="2022-06-28",T="https://api.notion.com/v1",f=2e4,b=25,v=25;function _(t){if(!t||typeof t!="string")return null;let i=t.trim().split(/[?#]/)[0],e=i.match(/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/);if(e)return e[0].toLowerCase();let n=i.match(/[0-9a-fA-F]{32}/g);if(n&&n.length){let o=n[n.length-1].toLowerCase();return`${o.slice(0,8)}-${o.slice(8,12)}-${o.slice(12,16)}-${o.slice(16,20)}-${o.slice(20)}`}return null}async function h(t,a={}){let i=async()=>{let{token:e}=await I("notion");if(typeof e!="string"||!e)throw new Error(`Invalid notion token type: ${typeof e}`);let n=await fetch(`${T}${t}`,{method:a.method||"GET",headers:{Authorization:`Bearer ${e}`,"Notion-Version":S,Accept:"application/json",...a.body?{"Content-Type":"application/json"}:{},...a.headers},body:a.body?JSON.stringify(a.body):void 0});if(!n.ok){let s=await n.text().catch(()=>"");throw new Error(`Notion API ${n.status}: ${s.slice(0,300)}`)}let o=await n.text().catch(()=>"");if(!o||!o.trim())return{};try{return JSON.parse(o)}catch{return{raw:o}}};try{return await i()}catch(e){let n=String(e?.message||e||"").toLowerCase();if(!(n.includes("token")||n.includes("401")||n.includes("unauthorized")))throw e;return O("notion"),i()}}function p(t){if(!Array.isArray(t))return"";let a="";for(let i of t){let e=i?.plain_text??i?.text?.content??"";if(!e)continue;let n=i.annotations||{};n.code&&(e=`\`${e}\``),n.bold&&(e=`**${e}**`),n.italic&&(e=`_${e}_`),n.strikethrough&&(e=`~~${e}~~`);let o=i?.href||i?.text?.link?.url;o&&(e=`[${e}](${o})`),a+=e}return a}function L(t,a,i){let e=t?.type,n=t?.[e]||{},o=" ".repeat(Math.max(0,a)),s=(c="rich_text")=>p(n[c]),r;switch(e){case"paragraph":r=s();break;case"heading_1":r=`# ${s()}`;break;case"heading_2":r=`## ${s()}`;break;case"heading_3":r=`### ${s()}`;break;case"bulleted_list_item":r=`${o}- ${s()}`;break;case"numbered_list_item":r=`${o}1. ${s()}`;break;case"to_do":r=`${o}- [${n.checked?"x":" "}] ${s()}`;break;case"toggle":r=`${o}- ${s()}`;break;case"quote":r=`> ${s()}`;break;case"callout":{r=`> ${n.icon?.emoji?`${n.icon.emoji} `:""}${s()}`;break}case"code":{r=`\`\`\`${n.language||""}
|
|
2
2
|
${s()}
|
|
3
|
-
\`\`\``;break}case"divider":r="---";break;case"child_page":r=`[child page: ${
|
|
4
|
-
`)}async function y(
|
|
5
|
-
`)}function
|
|
3
|
+
\`\`\``;break}case"divider":r="---";break;case"child_page":r=`[child page: ${n.title||""}]`;break;case"child_database":r=`[child database: ${n.title||""}]`;break;case"bookmark":case"embed":case"link_preview":r=n.url?`<${n.url}>`:"";break;case"equation":r=n.expression?`$${n.expression}$`:"";break;case"table":case"column_list":case"column":r="";break;case"table_row":{let c=(n.cells||[]).map(d=>p(d).trim());r=`${o}| ${c.join(" | ")} |`;break}default:r=s();break}let l=[];return r&&r.trim()&&l.push(r),i&&i.trim()&&l.push(i),l.join(`
|
|
4
|
+
`)}async function y(t,a,i){let e=[],n,o=0;do{if(i.used>=f)break;let s=new URLSearchParams({page_size:"100"});n&&s.set("start_cursor",n);let r=await h(`/blocks/${t}/children?${s.toString()}`),l=Array.isArray(r.results)?r.results:[];for(let c of l){let d="";c.has_children&&(d=await y(c.id,a+1,i));let u=L(c,a,d);if(u&&(e.push(u),i.used+=u.length+1),i.used>=f)break}n=r.has_more?r.next_cursor:void 0,o+=1}while(n&&o<v);return e.join(`
|
|
5
|
+
`)}function k(t){let a=t?.properties||{};for(let i of Object.values(a))if(i?.type==="title"){let e=p(i.title).trim();if(e)return e}return""}function P(t){if(!t||!t.type)return"";switch(t.type){case"title":return p(t.title).trim();case"rich_text":return p(t.rich_text).trim();case"number":return t.number==null?"":String(t.number);case"select":return t.select?.name||"";case"status":return t.status?.name||"";case"multi_select":return(t.multi_select||[]).map(i=>i.name).join(", ");case"checkbox":return t.checkbox?"true":"false";case"url":return t.url||"";case"email":return t.email||"";case"phone_number":return t.phone_number||"";case"date":return t.date?.start||"";case"people":return(t.people||[]).map(i=>i.name||i.id).join(", ");default:return""}}var B={id:"notion",serverName:"notion",allowedTools:["mcp__notion__*"],requiresIntegration:g.NOTION,description:"Notion read-only context (pull a page/database as markdown)",promptFragment:`## Notion (connected, read-only context)
|
|
6
6
|
You can pull a referenced Notion page in as extra context. This is OPTIONAL \u2014 only use it when the task references a Notion page/URL (e.g. an engineering-standards or design doc to review against).
|
|
7
7
|
- notion_get_page: pass a Notion page id OR a full Notion URL; returns { id, title, url, text } where text is the page flattened to markdown (truncated to ~20k chars). Use the text as reference context.
|
|
8
8
|
- notion_query_database: pass a database id/URL; returns a small list of rows ({ id, title, url, props }). Use to find a specific page, then notion_get_page it.
|
|
9
|
-
Do not block the task if Notion is unavailable \u2014 these tools return { ok:false, error } on failure; treat a missing page as "no extra context" and continue.`,resolve(){return null},async handleToolCall(
|
|
9
|
+
Do not block the task if Notion is unavailable \u2014 these tools return { ok:false, error } on failure; treat a missing page as "no extra context" and continue.`,resolve(){let t=R();return t?{type:"stdio",command:"node",args:[t,"../dist/notion.js","notionSkill"],env:{},description:this.description,alwaysLoad:!0}:null},async handleToolCall(t,a){try{switch(t){case"notion_get_page":{let i=a?.pageId||a?.page||a?.url||a?.id,e=_(i);if(!e)return JSON.stringify({ok:!1,error:"A valid Notion page id or URL is required"});let n=await h(`/pages/${e}`),o=k(n),s=n?.url||`https://www.notion.so/${e.replace(/-/g,"")}`,l=await y(e,0,{used:0}),c=!1;return l.length>f&&(l=l.slice(0,f),c=!0),JSON.stringify({ok:!0,id:e,title:o,url:s,text:l,...c?{truncated:!0}:{}})}case"notion_query_database":{let i=a?.databaseId||a?.database||a?.url||a?.id,e=_(i);if(!e)return JSON.stringify({ok:!1,error:"A valid Notion database id or URL is required"});let o={page_size:Math.max(1,Math.min(Number(a?.maxResults)||b,b))};a?.filter&&typeof a.filter=="object"&&(o.filter=a.filter);let s=await h(`/databases/${e}/query`,{method:"POST",body:o}),l=(Array.isArray(s.results)?s.results:[]).map(c=>{let d={};for(let[u,N]of Object.entries(c.properties||{})){let m=P(N);m&&(d[u]=m)}return{id:c.id,title:k(c),url:c.url||`https://www.notion.so/${String(c.id||"").replace(/-/g,"")}`,props:d}});return JSON.stringify({ok:!0,id:e,count:l.length,hasMore:!!s.has_more,rows:l})}default:return JSON.stringify({ok:!1,error:`Unknown tool: ${t}`})}}catch(i){return JSON.stringify({ok:!1,error:i.message})}},tools:[{name:"notion_get_page",description:"Fetch a Notion page and its content flattened to markdown, for use as read-only context. Accepts a raw page id OR a full Notion URL. Returns { ok, id, title, url, text }. Text is truncated to ~20k chars.",input_schema:{type:"object",properties:{pageId:{type:"string",description:"Notion page id (dashed UUID or 32-char) OR a full Notion page URL."}},required:["pageId"]}},{name:"notion_query_database",description:"Query a Notion database and return a bounded list of rows (id, title, url, key props). Accepts a database id OR full Notion URL. Optional Notion filter object. Returns at most 25 rows.",input_schema:{type:"object",properties:{databaseId:{type:"string",description:"Notion database id (dashed UUID or 32-char) OR a full Notion database URL."},filter:{type:"object",description:"Optional Notion filter object (Notion query filter syntax).",additionalProperties:!0},maxResults:{type:"number",description:"Max rows to return (default 25, max 25)."}},required:["databaseId"]}}]};export{h as notionApi,B as notionSkill,_ as parseNotionId};
|
package/dist/package.json
CHANGED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 6
|
|
3
|
+
title: Editing node prompts
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Editing node prompts
|
|
7
|
+
|
|
8
|
+
Every node in an agent has a **prompt** — the instructions handed to the
|
|
9
|
+
coding agent when that step runs. In the graph editor (both the deploy modal
|
|
10
|
+
and a deployed agent's **Overview** tab) each prompt is tagged with a badge:
|
|
11
|
+
|
|
12
|
+
| Badge | What it means |
|
|
13
|
+
|---|---|
|
|
14
|
+
| <span style={{color:'#34d399',fontWeight:600}}>EDITABLE</span> | The prompt is a stored text template. You can change it right here. |
|
|
15
|
+
| <span style={{color:'#d4b483',fontWeight:600}}>READ-ONLY</span> | The prompt is generated by the node's code at runtime. It can't be edited in the UI. |
|
|
16
|
+
|
|
17
|
+
The `!` next to the badge explains the same thing on hover.
|
|
18
|
+
|
|
19
|
+
## Editable prompts
|
|
20
|
+
|
|
21
|
+
A prompt is editable when the node declares it as a plain text template (a
|
|
22
|
+
string, optionally with `{{variables}}`).
|
|
23
|
+
|
|
24
|
+
The graph shown in the marketplace **deploy modal is a read-only preview** —
|
|
25
|
+
you pick the name and model there, not edit prompts. You edit editable
|
|
26
|
+
prompts **after you deploy**:
|
|
27
|
+
|
|
28
|
+
- Open the deployed agent's **Overview** tab.
|
|
29
|
+
- Click the node, edit the prompt, and save.
|
|
30
|
+
- The change applies to the next run **live — no redeploy needed**.
|
|
31
|
+
|
|
32
|
+
This is the right choice when you want non-developers to tune wording,
|
|
33
|
+
tone, or rules without touching code.
|
|
34
|
+
|
|
35
|
+
## Read-only prompts
|
|
36
|
+
|
|
37
|
+
A prompt is read-only when the node builds it **in code** — a function that
|
|
38
|
+
assembles the text from state, fetched context, or conditionals at runtime.
|
|
39
|
+
There's no fixed string to edit, so the editor shows a rendered preview only.
|
|
40
|
+
|
|
41
|
+
To change a read-only prompt you edit the agent's source and redeploy. You
|
|
42
|
+
don't need to leave your AI coding agent to do it — the
|
|
43
|
+
[Zibby MCP](../packages/mcp-cli) drives the whole loop from chat:
|
|
44
|
+
|
|
45
|
+
1. **Download the agent** — pull the deployed sources back to your machine.
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
zibby agent download <uuid>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Or, from an MCP-aware agent (Claude Code / Cursor / Codex / Gemini),
|
|
52
|
+
ask it to call `zibby_download_agent`.
|
|
53
|
+
|
|
54
|
+
2. **Edit the node's code** — open the node file under
|
|
55
|
+
`.zibby/workflows/<name>/nodes/` and change how the prompt is built. Your
|
|
56
|
+
coding agent can do this directly.
|
|
57
|
+
|
|
58
|
+
3. **Redeploy to update** — ship the change back to the same agent.
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
zibby agent deploy <name>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Or have the agent call `zibby_deploy_agent`. The deployed agent updates in
|
|
65
|
+
place; its UUID and trigger config stay the same.
|
|
66
|
+
|
|
67
|
+
:::tip Want it editable instead?
|
|
68
|
+
If a prompt would be better tuned by non-developers, convert the node from a
|
|
69
|
+
function prompt to a **string template**. Once it's a stored template, the
|
|
70
|
+
badge flips to **Editable** and it gains live-update from Overview.
|
|
71
|
+
:::
|
|
72
|
+
|
|
73
|
+
## Why the split exists
|
|
74
|
+
|
|
75
|
+
Function prompts exist for a reason: some nodes need to assemble instructions
|
|
76
|
+
from things only known at runtime — the linked ticket, the diff, prior
|
|
77
|
+
reviews, whether an integration is connected. A fixed text box can't express
|
|
78
|
+
that logic, so those nodes keep their prompt in code. Simpler, static prompts
|
|
79
|
+
stay as editable templates so anyone can adjust them.
|