glenn-code 1.0.30 → 1.0.31

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.
@@ -2,16 +2,16 @@
2
2
  // Glenn Code - Bundled and minified
3
3
  // (c) DNM Lab - All rights reserved
4
4
 
5
- import{McpServer as O}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as R}from"@modelcontextprotocol/sdk/server/stdio.js";import{z as i}from"zod";import*as g from"fs";import*as _ from"path";import{execFileSync as T}from"child_process";var j=process.env.API_URL||"http://localhost:5338",a=process.env.PROJECT_ID,x=process.env.PROJECT_KEY,C=process.env.WORKSPACE_DIR||process.cwd(),E="/tmp/agent-logs",v=_.join(E,"stdio-mcp-server.log");try{g.existsSync(E)||g.mkdirSync(E,{recursive:!0})}catch{}function U(t){try{return JSON.stringify(t)}catch{return t instanceof Error?`Error: ${t.message}`:String(t)}}function s(t,...e){let r=`[${new Date().toISOString()}] ${t} ${e.map(o=>typeof o=="object"?U(o):String(o)).join(" ")}
6
- `;console.error(`[Stdio MCP Server] ${t}`,...e);try{g.appendFileSync(v,r,"utf8")}catch{}}s("Starting...");s("API_URL:",j);s("PROJECT_ID:",a||"(not set)");s("PROJECT_KEY:",x?`${x.slice(0,12)}...`:"(not set)");s("WORKSPACE_DIR:",C);s("Process cwd:",process.cwd());s("__filename equivalent:",import.meta.url);s("Log file:",v);function I(t){return t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}function h(t){return t&&t.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g,"\uFFFD")}function w(t){if(typeof t=="string")return h(t);if(Array.isArray(t))return t.map(e=>w(e));if(t&&typeof t=="object"){let e={};for(let[n,r]of Object.entries(t))e[n]=w(r);return e}return t}async function l(t,e){let n=`${j}${t}`,r={"Content-Type":"application/json"};return x&&(r["X-Project-Key"]=x),fetch(n,{...e,headers:{...r,...e?.headers}})}var p=new O({name:"agent-planning",version:"1.0.0"});p.tool("save_specification","Save a specification. First write content to a temp file with Write tool, then call this with the file path.",{name:i.string().describe("Specification name"),filePath:i.string().describe("Path to file containing the spec content")},async({name:t,filePath:e})=>{if(s("save_specification called",{name:t,filePath:e,projectId:a||"(not set)"}),!a)return s("save_specification failed: PROJECT_ID not set"),{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};let n=_.resolve(e);if(!g.existsSync(n))return s("save_specification failed: file not found",{resolvedPath:n}),{content:[{type:"text",text:`\u274C File not found: ${e}. Use Write tool first to create the file.`}]};let r=g.readFileSync(n,"utf-8"),o=I(t);s("save_specification reading file",{resolvedPath:n,contentLength:r.length,slug:o});try{let d=await l(`/api/projects/${a}/specifications`,{method:"POST",body:JSON.stringify({slug:o,name:t,content:r})});if(!d.ok){let u=await d.text();return s("save_specification failed:",{status:d.status,error:u}),{content:[{type:"text",text:`\u274C Failed to save: ${u}`}]}}let c=await d.json();return c.success?(s("save_specification succeeded:",{name:t,slug:o}),{content:[{type:"text",text:`\u2705 Saved specification: ${o}.spec.md`}]}):(s("save_specification failed:",{result:c}),{content:[{type:"text",text:`\u274C ${c.error||"Failed to save"}`}]})}catch(d){return s("save_specification error:",d instanceof Error?d.message:String(d),d),{content:[{type:"text",text:`\u274C Error: ${d instanceof Error?d.message:String(d)}`}]}}});p.tool("read_specification","Read a specification's content",{name:i.string().describe("Specification name")},async({name:t})=>{if(!a)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};let e=I(t);try{let n=await l(`/api/projects/${a}/specifications/${e}`);if(!n.ok)return{content:[{type:"text",text:`\u274C Specification "${t}" not found.`}]};let r=await n.json();return!r.success||!r.content?{content:[{type:"text",text:`\u274C ${r.error||"Specification not found"}`}]}:{content:[{type:"text",text:h(r.content)}]}}catch(n){return{content:[{type:"text",text:`\u274C Error: ${n instanceof Error?n.message:String(n)}`}]}}});p.tool("list_specifications","List all specifications",{},async()=>{if(!a)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let t=await l(`/api/projects/${a}/specifications`);if(!t.ok)return{content:[{type:"text",text:"\u{1F4CB} No specifications found."}]};let e=await t.json();return!e.success||!e.specifications||e.specifications.length===0?{content:[{type:"text",text:"\u{1F4CB} No specifications found."}]}:{content:[{type:"text",text:`\u{1F4CB} Specifications:
5
+ import{McpServer as J}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as N}from"@modelcontextprotocol/sdk/server/stdio.js";import{z as i}from"zod";import*as f from"fs";import*as y from"path";import{execFileSync as P}from"child_process";var T=process.env.API_URL||"http://localhost:5338",a=process.env.PROJECT_ID,w=process.env.PROJECT_KEY,O=process.env.WORKSPACE_DIR||process.cwd(),D="/tmp/agent-logs",R=y.join(D,"stdio-mcp-server.log");try{f.existsSync(D)||f.mkdirSync(D,{recursive:!0})}catch{}function q(t){try{return JSON.stringify(t)}catch{return t instanceof Error?`Error: ${t.message}`:String(t)}}function s(t,...e){let r=`[${new Date().toISOString()}] ${t} ${e.map(o=>typeof o=="object"?q(o):String(o)).join(" ")}
6
+ `;console.error(`[Stdio MCP Server] ${t}`,...e);try{f.appendFileSync(R,r,"utf8")}catch{}}s("Starting...");s("API_URL:",T);s("PROJECT_ID:",a||"(not set)");s("PROJECT_KEY:",w?`${w.slice(0,12)}...`:"(not set)");s("WORKSPACE_DIR:",O);s("Process cwd:",process.cwd());s("__filename equivalent:",import.meta.url);s("Log file:",R);function C(t){return t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}function _(t){return t&&t.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g,"\uFFFD")}function $(t){if(typeof t=="string")return _(t);if(Array.isArray(t))return t.map(e=>$(e));if(t&&typeof t=="object"){let e={};for(let[n,r]of Object.entries(t))e[n]=$(r);return e}return t}async function l(t,e){let n=`${T}${t}`,r={"Content-Type":"application/json"};return w&&(r["X-Project-Key"]=w),fetch(n,{...e,headers:{...r,...e?.headers}})}var p=new J({name:"agent-planning",version:"1.0.0"});p.tool("save_specification","Save a specification. First write content to a temp file with Write tool, then call this with the file path.",{name:i.string().describe("Specification name"),filePath:i.string().describe("Path to file containing the spec content")},async({name:t,filePath:e})=>{if(s("save_specification called",{name:t,filePath:e,projectId:a||"(not set)"}),!a)return s("save_specification failed: PROJECT_ID not set"),{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};let n=y.resolve(e);if(!f.existsSync(n))return s("save_specification failed: file not found",{resolvedPath:n}),{content:[{type:"text",text:`\u274C File not found: ${e}. Use Write tool first to create the file.`}]};let r=f.readFileSync(n,"utf-8"),o=C(t);s("save_specification reading file",{resolvedPath:n,contentLength:r.length,slug:o});try{let d=await l(`/api/projects/${a}/specifications`,{method:"POST",body:JSON.stringify({slug:o,name:t,content:r})});if(!d.ok){let u=await d.text();return s("save_specification failed:",{status:d.status,error:u}),{content:[{type:"text",text:`\u274C Failed to save: ${u}`}]}}let c=await d.json();return c.success?(s("save_specification succeeded:",{name:t,slug:o}),{content:[{type:"text",text:`\u2705 Saved specification: ${o}.spec.md`}]}):(s("save_specification failed:",{result:c}),{content:[{type:"text",text:`\u274C ${c.error||"Failed to save"}`}]})}catch(d){return s("save_specification error:",d instanceof Error?d.message:String(d),d),{content:[{type:"text",text:`\u274C Error: ${d instanceof Error?d.message:String(d)}`}]}}});p.tool("read_specification","Read a specification's content",{name:i.string().describe("Specification name")},async({name:t})=>{if(!a)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};let e=C(t);try{let n=await l(`/api/projects/${a}/specifications/${e}`);if(!n.ok)return{content:[{type:"text",text:`\u274C Specification "${t}" not found.`}]};let r=await n.json();return!r.success||!r.content?{content:[{type:"text",text:`\u274C ${r.error||"Specification not found"}`}]}:{content:[{type:"text",text:_(r.content)}]}}catch(n){return{content:[{type:"text",text:`\u274C Error: ${n instanceof Error?n.message:String(n)}`}]}}});p.tool("list_specifications","List all specifications",{},async()=>{if(!a)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let t=await l(`/api/projects/${a}/specifications`);if(!t.ok)return{content:[{type:"text",text:"\u{1F4CB} No specifications found."}]};let e=await t.json();return!e.success||!e.specifications||e.specifications.length===0?{content:[{type:"text",text:"\u{1F4CB} No specifications found."}]}:{content:[{type:"text",text:`\u{1F4CB} Specifications:
7
7
 
8
8
  ${e.specifications.map(r=>`- ${r.name} (${r.slug}.spec.md)`).join(`
9
- `)}`}]}}catch(t){return{content:[{type:"text",text:`\u274C Error: ${t instanceof Error?t.message:String(t)}`}]}}});p.tool("delete_specification","Delete a specification",{name:i.string().describe("Specification name")},async({name:t})=>{if(!a)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};let e=I(t);try{let n=await l(`/api/projects/${a}/specifications/${e}`,{method:"DELETE"});if(!n.ok)return{content:[{type:"text",text:`\u274C Specification "${t}" not found.`}]};let r=await n.json();return r.success?{content:[{type:"text",text:`\u{1F5D1}\uFE0F Deleted: ${e}.spec.md`}]}:{content:[{type:"text",text:`\u274C ${r.error||"Failed to delete"}`}]}}catch(n){return{content:[{type:"text",text:`\u274C Error: ${n instanceof Error?n.message:String(n)}`}]}}});var L=i.object({id:i.string().describe("Unique identifier for the question"),question:i.string().describe("The question to ask the user"),options:i.array(i.string()).optional().describe("Predefined options the user can choose from"),allowFreeText:i.boolean().optional().default(!0).describe("Allow user to type a custom answer"),multiSelect:i.boolean().optional().default(!1).describe("Allow selecting multiple options")}),$=null;p.tool("start_question_session",`Start a conversational question session to gather requirements from the user.
9
+ `)}`}]}}catch(t){return{content:[{type:"text",text:`\u274C Error: ${t instanceof Error?t.message:String(t)}`}]}}});p.tool("delete_specification","Delete a specification",{name:i.string().describe("Specification name")},async({name:t})=>{if(!a)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};let e=C(t);try{let n=await l(`/api/projects/${a}/specifications/${e}`,{method:"DELETE"});if(!n.ok)return{content:[{type:"text",text:`\u274C Specification "${t}" not found.`}]};let r=await n.json();return r.success?{content:[{type:"text",text:`\u{1F5D1}\uFE0F Deleted: ${e}.spec.md`}]}:{content:[{type:"text",text:`\u274C ${r.error||"Failed to delete"}`}]}}catch(n){return{content:[{type:"text",text:`\u274C Error: ${n instanceof Error?n.message:String(n)}`}]}}});var z=i.object({id:i.string().describe("Unique identifier for the question"),question:i.string().describe("The question to ask the user"),options:i.array(i.string()).optional().describe("Predefined options the user can choose from"),allowFreeText:i.boolean().optional().default(!0).describe("Allow user to type a custom answer"),multiSelect:i.boolean().optional().default(!1).describe("Allow selecting multiple options")}),j=null;p.tool("start_question_session",`Start a conversational question session to gather requirements from the user.
10
10
 
11
11
  Call this BEFORE asking any questions. It initializes the session context.
12
12
 
13
13
  After calling this, use ask_question to ask individual questions one at a time.
14
- Each question should build on the previous answer (leading questions approach).`,{purpose:i.string().describe("What you're trying to understand/gather (e.g., 'understand feature requirements')")},async({purpose:t})=>(s("start_question_session called",{purpose:t,projectId:a||"(not set)"}),$=t,{content:[{type:"text",text:`\u2705 Question session started.
14
+ Each question should build on the previous answer (leading questions approach).`,{purpose:i.string().describe("What you're trying to understand/gather (e.g., 'understand feature requirements')")},async({purpose:t})=>(s("start_question_session called",{purpose:t,projectId:a||"(not set)"}),j=t,{content:[{type:"text",text:`\u2705 Question session started.
15
15
  Purpose: ${t}
16
16
 
17
17
  Now call ask_question to ask your first question. Ask ONE question at a time, and let the user's answer guide your next question.`}]}));p.tool("ask_question",`Ask a SINGLE question to the user and wait for their answer.
@@ -31,7 +31,7 @@ Parameters:
31
31
  - question: The question text
32
32
  - options: Optional predefined choices (user can still type custom answer)
33
33
  - allowFreeText: Let user type custom answer (default: true)
34
- - multiSelect: Allow multiple selections (default: false)`,{question:i.string().describe("The question to ask"),options:i.array(i.string()).optional().describe("Predefined answer options"),allowFreeText:i.boolean().optional().default(!0),multiSelect:i.boolean().optional().default(!1)},async({question:t,options:e,allowFreeText:n,multiSelect:r})=>{if(s("ask_question called",{question:t,projectId:a||"(not set)"}),!a)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};let d={id:`q_${Date.now()}`,question:t,options:e||[],allowFreeText:n??!0,multiSelect:r??!1};try{let c=await l(`/api/projects/${a}/questions/ask`,{method:"POST",body:JSON.stringify({question:d,sessionContext:$?{purpose:$}:null})});if(!c.ok){let u=await c.text();return s("ask_question failed:",{status:c.status,error:u}),{content:[{type:"text",text:`\u274C Failed to ask question: ${u}`}]}}return s("ask_question succeeded"),{content:[{type:"text",text:`\u2705 Question sent to user. STOP HERE and wait for their answer.
34
+ - multiSelect: Allow multiple selections (default: false)`,{question:i.string().describe("The question to ask"),options:i.array(i.string()).optional().describe("Predefined answer options"),allowFreeText:i.boolean().optional().default(!0),multiSelect:i.boolean().optional().default(!1)},async({question:t,options:e,allowFreeText:n,multiSelect:r})=>{if(s("ask_question called",{question:t,projectId:a||"(not set)"}),!a)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};let d={id:`q_${Date.now()}`,question:t,options:e||[],allowFreeText:n??!0,multiSelect:r??!1};try{let c=await l(`/api/projects/${a}/questions/ask`,{method:"POST",body:JSON.stringify({question:d,sessionContext:j?{purpose:j}:null})});if(!c.ok){let u=await c.text();return s("ask_question failed:",{status:c.status,error:u}),{content:[{type:"text",text:`\u274C Failed to ask question: ${u}`}]}}return s("ask_question succeeded"),{content:[{type:"text",text:`\u2705 Question sent to user. STOP HERE and wait for their answer.
35
35
 
36
36
  The user's answer will be provided in the next message. Based on their answer, decide what to ask next (or proceed with implementation if you have enough info).`}]}}catch(c){return s("ask_question error:",c instanceof Error?c.message:String(c)),{content:[{type:"text",text:`\u274C Error: ${c instanceof Error?c.message:String(c)}`}]}}});p.tool("end_question_session",`End the current question session and summarize what you learned.
37
37
 
@@ -40,7 +40,7 @@ Call this when:
40
40
  2. The user said "start implementing", "that's enough", "let's go", etc.
41
41
  3. You've asked enough questions (aim for 3-7, don't exhaust the user!)
42
42
 
43
- After ending the session, you can proceed with implementation.`,{summary:i.string().describe("Brief summary of what you learned from the questions")},async({summary:t})=>{if(s("end_question_session called",{summary:t,projectId:a||"(not set)"}),!a)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{await l(`/api/projects/${a}/questions/end-session`,{method:"POST",body:JSON.stringify({summary:t})})}catch(e){s("end_question_session error (non-fatal):",e instanceof Error?e.message:String(e))}return $=null,{content:[{type:"text",text:`\u2705 Question session ended.
43
+ After ending the session, you can proceed with implementation.`,{summary:i.string().describe("Brief summary of what you learned from the questions")},async({summary:t})=>{if(s("end_question_session called",{summary:t,projectId:a||"(not set)"}),!a)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{await l(`/api/projects/${a}/questions/end-session`,{method:"POST",body:JSON.stringify({summary:t})})}catch(e){s("end_question_session error (non-fatal):",e instanceof Error?e.message:String(e))}return j=null,{content:[{type:"text",text:`\u2705 Question session ended.
44
44
 
45
45
  Summary: ${t}
46
46
 
@@ -76,25 +76,25 @@ ${r}
76
76
  \`\`\``:""].join(""),d=await l(`/api/projects/${a}/insights`,{method:"POST",body:JSON.stringify({type:"DebugInsight",category:t,description:o})});if(!d.ok){let c=await d.text();return s("report_debug_insight failed:",{status:d.status,error:c}),{content:[{type:"text",text:"\u{1F6D1} Debug insight recorded locally (API error). STOP and wait for user."}]}}return s("report_debug_insight succeeded:",{category:t}),{content:[{type:"text",text:`\u{1F6D1} Debug insight reported: ${t}
77
77
 
78
78
  You MUST now stop and say: "\u{1F6D1} DEBUG: I've hit an issue and reported it. Please review and tell me how to proceed."`}]}}catch(o){return s("report_debug_insight error:",o instanceof Error?o.message:String(o)),{content:[{type:"text",text:"\u{1F6D1} Debug insight recorded locally (error). STOP and wait for user."}]}}});p.tool("get_kanban_board",`Get kanban board overview - columns with card counts only.
79
- Use get_column_cards to fetch cards for a specific column.`,{},async()=>{if(s("get_kanban_board called",{projectId:a||"(not set)"}),!a)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let t=await l(`/api/kanban-boards/by-project/${a}/full`);if(!t.ok)return t.status===404?{content:[{type:"text",text:"\u{1F4CB} No kanban board found for this project."}]}:{content:[{type:"text",text:`\u274C Failed to get board: ${await t.text()}`}]};let e=await t.json(),n=w(e),r=`\u{1F4CB} **${n.title}**
79
+ Use get_column_cards to fetch cards for a specific column.`,{},async()=>{if(s("get_kanban_board called",{projectId:a||"(not set)"}),!a)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let t=await l(`/api/kanban-boards/by-project/${a}/full`);if(!t.ok)return t.status===404?{content:[{type:"text",text:"\u{1F4CB} No kanban board found for this project."}]}:{content:[{type:"text",text:`\u274C Failed to get board: ${await t.text()}`}]};let e=await t.json(),n=$(e),r=`\u{1F4CB} **${n.title}**
80
80
 
81
81
  `;for(let o of n.columns)r+=`- **${o.title}**: ${o.cards.length} cards (id: ${o.id})
82
82
  `;return r+="\nUse `get_column_cards` with column name to see cards.",s("get_kanban_board succeeded"),{content:[{type:"text",text:r}]}}catch(t){return s("get_kanban_board error:",t instanceof Error?t.message:String(t)),{content:[{type:"text",text:`\u274C Error: ${t instanceof Error?t.message:String(t)}`}]}}});p.tool("get_column_cards",`Get cards in a specific column. Returns card titles, priorities, and IDs.
83
- Use get_card_details for full card info including description and subtasks.`,{column:i.string().describe("Column name (e.g., 'Todo', 'In Progress', 'Done')")},async({column:t})=>{if(s("get_column_cards called",{column:t,projectId:a||"(not set)"}),!a)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let e=await l(`/api/kanban-boards/by-project/${a}/full`);if(!e.ok)return{content:[{type:"text",text:"\u274C Failed to get board"}]};let n=await e.json(),r=w(n),o=r.columns.find(c=>c.title.toLowerCase()===t.toLowerCase());if(!o){let c=r.columns.map(u=>u.title).join(", ");return{content:[{type:"text",text:`\u274C Column "${t}" not found. Available: ${c}`}]}}if(o.cards.length===0)return{content:[{type:"text",text:`\u{1F4CB} **${o.title}**: No cards`}]};let d=`\u{1F4CB} **${o.title}** (${o.cards.length} cards)
83
+ Use get_card_details for full card info including description and subtasks.`,{column:i.string().describe("Column name (e.g., 'Todo', 'In Progress', 'Done')")},async({column:t})=>{if(s("get_column_cards called",{column:t,projectId:a||"(not set)"}),!a)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let e=await l(`/api/kanban-boards/by-project/${a}/full`);if(!e.ok)return{content:[{type:"text",text:"\u274C Failed to get board"}]};let n=await e.json(),r=$(n),o=r.columns.find(c=>c.title.toLowerCase()===t.toLowerCase());if(!o){let c=r.columns.map(u=>u.title).join(", ");return{content:[{type:"text",text:`\u274C Column "${t}" not found. Available: ${c}`}]}}if(o.cards.length===0)return{content:[{type:"text",text:`\u{1F4CB} **${o.title}**: No cards`}]};let d=`\u{1F4CB} **${o.title}** (${o.cards.length} cards)
84
84
 
85
- `;for(let c of o.cards){let u=c.priority!=="Medium"?` [${c.priority}]`:"",f=c.subtasks?.length||0,m=f>0?` (${f} subtasks)`:"";d+=`- **${h(c.title)}**${u}${m}
85
+ `;for(let c of o.cards){let u=c.priority!=="Medium"?` [${c.priority}]`:"",g=c.subtasks?.length||0,h=g>0?` (${g} subtasks)`:"";d+=`- **${_(c.title)}**${u}${h}
86
86
  id: ${c.id}
87
- `}return d+="\nUse `get_card_details` with card ID for full info.",s("get_column_cards succeeded",{column:o.title,cardCount:o.cards.length}),{content:[{type:"text",text:d}]}}catch(e){return s("get_column_cards error:",e instanceof Error?e.message:String(e)),{content:[{type:"text",text:`\u274C Error: ${e instanceof Error?e.message:String(e)}`}]}}});p.tool("get_card_details","Get full details of a specific card including description and subtasks.",{cardId:i.string().describe("Card ID (GUID)")},async({cardId:t})=>{if(s("get_card_details called",{cardId:t,projectId:a||"(not set)"}),!a)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let e=await l(`/api/kanban-boards/by-project/${a}/full`);if(!e.ok)return{content:[{type:"text",text:"\u274C Failed to get board"}]};let n=await e.json(),r=w(n),o=null,d="";for(let u of r.columns){let f=u.cards.find(m=>m.id===t);if(f){o=f,d=u.title;break}}if(!o)return{content:[{type:"text",text:`\u274C Card not found: ${t}`}]};let c=`\u{1F4CB} **${h(o.title)}**
87
+ `}return d+="\nUse `get_card_details` with card ID for full info.",s("get_column_cards succeeded",{column:o.title,cardCount:o.cards.length}),{content:[{type:"text",text:d}]}}catch(e){return s("get_column_cards error:",e instanceof Error?e.message:String(e)),{content:[{type:"text",text:`\u274C Error: ${e instanceof Error?e.message:String(e)}`}]}}});p.tool("get_card_details","Get full details of a specific card including description and subtasks.",{cardId:i.string().describe("Card ID (GUID)")},async({cardId:t})=>{if(s("get_card_details called",{cardId:t,projectId:a||"(not set)"}),!a)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let e=await l(`/api/kanban-boards/by-project/${a}/full`);if(!e.ok)return{content:[{type:"text",text:"\u274C Failed to get board"}]};let n=await e.json(),r=$(n),o=null,d="";for(let u of r.columns){let g=u.cards.find(h=>h.id===t);if(g){o=g,d=u.title;break}}if(!o)return{content:[{type:"text",text:`\u274C Card not found: ${t}`}]};let c=`\u{1F4CB} **${_(o.title)}**
88
88
  `;if(c+=`Column: ${d}
89
89
  `,c+=`Priority: ${o.priority}
90
90
  `,o.dueDate&&(c+=`Due: ${new Date(o.dueDate).toLocaleDateString()}
91
91
  `),c+=`ID: ${o.id}
92
92
 
93
93
  `,o.description&&(c+=`**Description:**
94
- ${h(o.description)}
94
+ ${_(o.description)}
95
95
 
96
96
  `),o.subtasks&&o.subtasks.length>0){c+=`**Subtasks:**
97
- `;for(let u of o.subtasks){let f=u.isCompleted?"\u2705":"\u2B1C";c+=`${f} ${h(u.title)} (id: ${u.id})
97
+ `;for(let u of o.subtasks){let g=u.isCompleted?"\u2705":"\u2B1C";c+=`${g} ${_(u.title)} (id: ${u.id})
98
98
  `}}return s("get_card_details succeeded",{cardId:t}),{content:[{type:"text",text:c}]}}catch(e){return s("get_card_details error:",e instanceof Error?e.message:String(e)),{content:[{type:"text",text:`\u274C Error: ${e instanceof Error?e.message:String(e)}`}]}}});p.tool("create_kanban_card",`Create a new card on the kanban board.
99
99
  Column names are typically: Backlog, Todo, In Progress, Done
100
100
  The tool will match column names case-insensitively.`,{title:i.string().describe("Card title (max 200 chars)"),column:i.string().describe("Column name (e.g., 'Backlog', 'Todo', 'In Progress', 'Done')"),description:i.string().optional().describe("Card description (max 2000 chars)"),priority:i.enum(["Low","Medium","High","Urgent"]).optional().describe("Priority level (default: Medium)"),dueDate:i.string().optional().describe("Due date in ISO format (e.g., '2024-12-31')")},async({title:t,column:e,description:n,priority:r,dueDate:o})=>{if(s("create_kanban_card called",{title:t,column:e,projectId:a||"(not set)"}),!a)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let d=await l(`/api/projects/${a}/kanban/cards`,{method:"POST",body:JSON.stringify({title:t,column:e,description:n,priority:r||"Medium",dueDate:o?new Date(o).toISOString():void 0})}),c=await d.json();return!d.ok||!c.success?(s("create_kanban_card failed:",{result:c}),{content:[{type:"text",text:`\u274C ${c.error||"Failed to create card"}`}]}):(s("create_kanban_card succeeded:",{id:c.card.id}),{content:[{type:"text",text:`\u2705 Created card "${c.card.title}" in ${c.card.columnName} (id: ${c.card.id})`}]})}catch(d){return s("create_kanban_card error:",d instanceof Error?d.message:String(d)),{content:[{type:"text",text:`\u274C Error: ${d instanceof Error?d.message:String(d)}`}]}}});p.tool("update_kanban_card",`Update an existing kanban card's properties.
@@ -164,8 +164,8 @@ Status values:
164
164
  - "running": Test is in progress
165
165
  - "passed": Test completed successfully
166
166
  - "failed": Test failed
167
- - "cancelled": Test was cancelled`,{runId:i.string().describe("Test run ID (GUID from run_test or run_test_suite)"),status:i.enum(["running","passed","failed","cancelled"]).describe("New status"),resultSummary:i.string().optional().describe("Summary of test results (what was verified, any issues)"),errorMessage:i.string().optional().describe("Short error message if test failed"),errorDetails:i.string().optional().describe("Full error details/stack trace if failed"),passedTests:i.number().optional().describe("For suite runs: count of passed tests so far"),failedTests:i.number().optional().describe("For suite runs: count of failed tests so far")},async({runId:t,status:e,resultSummary:n,errorMessage:r,errorDetails:o,passedTests:d,failedTests:c})=>{if(s("report_test_status called",{runId:t,status:e,projectId:a||"(not set)"}),!a)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let u=await l(`/api/projects/${a}/tests/runs/${t}`,{method:"PUT",body:JSON.stringify({status:e,resultSummary:n,errorMessage:r,errorDetails:o,passedTests:d,failedTests:c})}),f=await u.json();return!u.ok||!f.success?(s("report_test_status failed:",{result:f}),{content:[{type:"text",text:`\u274C ${f.error||"Failed to update test status"}`}]}):{content:[{type:"text",text:`${e==="passed"?"\u2705":e==="failed"?"\u274C":e==="cancelled"?"\u{1F6AB}":"\u{1F504}"} Test run updated: ${e.toUpperCase()}${f.endedAt?`
168
- Completed at: ${f.endedAt}`:""}`}]}}catch(u){return s("report_test_status error:",u instanceof Error?u.message:String(u)),{content:[{type:"text",text:`\u274C Error: ${u instanceof Error?u.message:String(u)}`}]}}});p.tool("create_test_suite",`Create a new test suite for organizing tests.
167
+ - "cancelled": Test was cancelled`,{runId:i.string().describe("Test run ID (GUID from run_test or run_test_suite)"),status:i.enum(["running","passed","failed","cancelled"]).describe("New status"),resultSummary:i.string().optional().describe("Summary of test results (what was verified, any issues)"),errorMessage:i.string().optional().describe("Short error message if test failed"),errorDetails:i.string().optional().describe("Full error details/stack trace if failed"),passedTests:i.number().optional().describe("For suite runs: count of passed tests so far"),failedTests:i.number().optional().describe("For suite runs: count of failed tests so far")},async({runId:t,status:e,resultSummary:n,errorMessage:r,errorDetails:o,passedTests:d,failedTests:c})=>{if(s("report_test_status called",{runId:t,status:e,projectId:a||"(not set)"}),!a)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let u=await l(`/api/projects/${a}/tests/runs/${t}`,{method:"PUT",body:JSON.stringify({status:e,resultSummary:n,errorMessage:r,errorDetails:o,passedTests:d,failedTests:c})}),g=await u.json();return!u.ok||!g.success?(s("report_test_status failed:",{result:g}),{content:[{type:"text",text:`\u274C ${g.error||"Failed to update test status"}`}]}):{content:[{type:"text",text:`${e==="passed"?"\u2705":e==="failed"?"\u274C":e==="cancelled"?"\u{1F6AB}":"\u{1F504}"} Test run updated: ${e.toUpperCase()}${g.endedAt?`
168
+ Completed at: ${g.endedAt}`:""}`}]}}catch(u){return s("report_test_status error:",u instanceof Error?u.message:String(u)),{content:[{type:"text",text:`\u274C Error: ${u instanceof Error?u.message:String(u)}`}]}}});p.tool("create_test_suite",`Create a new test suite for organizing tests.
169
169
  A test suite is a logical grouping of related tests (e.g., "Login Tests", "Checkout Flow").
170
170
 
171
171
  Use this to create a container for related tests before adding individual tests to it.`,{name:i.string().describe("Name of the test suite (e.g., 'Login Tests', 'Checkout Flow')"),description:i.string().optional().describe("Optional description of what this suite tests")},async({name:t,description:e})=>{if(s("create_test_suite called",{name:t,projectId:a||"(not set)"}),!a)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let n=await l(`/api/projects/${a}/tests/suites`,{method:"POST",body:JSON.stringify({name:t,description:e})}),r=await n.json();return!n.ok||!r.success?(s("create_test_suite failed:",{result:r}),{content:[{type:"text",text:`\u274C ${r.error||"Failed to create test suite"}`}]}):{content:[{type:"text",text:`\u2705 Created test suite "${r.name}"
@@ -212,7 +212,7 @@ You can find it in the Commands section or use "/" in chat to access it.`}]}}cat
212
212
  Returns the project-specific prompt that gets injected into your system prompt.
213
213
  This describes what the project is about, its tech stack, patterns, and key rules.`,{},async()=>{if(s("get_project_prompt called",{projectId:a||"(not set)"}),!a)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let t=await l(`/api/projects/${a}/prompt`,{method:"GET"}),e=await t.json();return t.ok?(s("get_project_prompt succeeded:",{hasPrompt:!!e.prompt}),e.prompt?{content:[{type:"text",text:`\u{1F4CB} **Project Prompt:**
214
214
 
215
- ${h(e.prompt)}`}]}:{content:[{type:"text",text:"\u{1F4DD} No project prompt set. Use update_project_prompt to set one."}]}):(s("get_project_prompt failed:",{result:e}),{content:[{type:"text",text:`\u274C ${e.error||"Failed to get project prompt"}`}]})}catch(t){return s("get_project_prompt error:",t instanceof Error?t.message:String(t)),{content:[{type:"text",text:`\u274C Error: ${t instanceof Error?t.message:String(t)}`}]}}});p.tool("update_project_prompt",`Update the project prompt/description. Use this to define or refine the project context.
215
+ ${_(e.prompt)}`}]}:{content:[{type:"text",text:"\u{1F4DD} No project prompt set. Use update_project_prompt to set one."}]}):(s("get_project_prompt failed:",{result:e}),{content:[{type:"text",text:`\u274C ${e.error||"Failed to get project prompt"}`}]})}catch(t){return s("get_project_prompt error:",t instanceof Error?t.message:String(t)),{content:[{type:"text",text:`\u274C Error: ${t instanceof Error?t.message:String(t)}`}]}}});p.tool("update_project_prompt",`Update the project prompt/description. Use this to define or refine the project context.
216
216
  The prompt should describe:
217
217
  - What the project is about (overview)
218
218
  - Technology stack
@@ -225,13 +225,13 @@ Changes are synced in real-time via SignalR.`,{prompt:i.string().describe("The n
225
225
  Reference projects are customer prototypes (Lovable, Bolt, etc.) imported via zip upload.
226
226
  Use this to discover available reference projects before exploring their contents.`,{},async()=>{if(s("reference_projects_list called",{projectId:a||"(not set)"}),!a)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let t=await l(`/api/projects/${a}/reference-projects`);if(!t.ok){let r=await t.text();return s("reference_projects_list failed:",{status:t.status,error:r}),{content:[{type:"text",text:`\u274C Failed to list reference projects: ${r}`}]}}let e=await t.json();if(!e||e.length===0)return{content:[{type:"text",text:"\u{1F4C1} No reference projects found. Upload a zip via the UI first."}]};let n=`\u{1F4C1} **Reference Projects:**
227
227
 
228
- `;for(let r of e)n+=`\u2022 **${h(r.name)}**
228
+ `;for(let r of e)n+=`\u2022 **${_(r.name)}**
229
229
  `,n+=` ID: ${r.id}
230
230
  `,n+=` Status: ${r.status}
231
231
  `,n+=` Files: ${r.fileCount}
232
232
  `,r.techStack&&(n+=` Tech: ${r.techStack}
233
233
  `),r.specificationSlug&&(n+=` Spec: ${r.specificationSlug}
234
- `),r.notes&&(n+=` Notes: ${h(r.notes).substring(0,100)}${r.notes.length>100?"...":""}
234
+ `),r.notes&&(n+=` Notes: ${_(r.notes).substring(0,100)}${r.notes.length>100?"...":""}
235
235
  `),n+=`
236
236
  `;return n+="Use `reference_project_tree` with an ID to explore file structure.",s("reference_projects_list succeeded",{count:e.length}),{content:[{type:"text",text:n}]}}catch(t){return s("reference_projects_list error:",t instanceof Error?t.message:String(t)),{content:[{type:"text",text:`\u274C Error: ${t instanceof Error?t.message:String(t)}`}]}}});p.tool("reference_project_update_status",`Update a reference project's status and/or specification slug.
237
237
  Use this after completing analysis to mark progress.
@@ -248,10 +248,11 @@ The specificationSlug links to a saved specification (use save_specification fir
248
248
  Status: ${c.status}${c.specificationSlug?`
249
249
  Spec: ${c.specificationSlug}`:""}`}]}}catch(o){return s("reference_project_update_status error:",o instanceof Error?o.message:String(o)),{content:[{type:"text",text:`\u274C Error: ${o instanceof Error?o.message:String(o)}`}]}}});p.tool("reference_project_download",`Download a reference project to the local filesystem for exploration.
250
250
  This gets a presigned URL from the API and downloads directly from R2 storage.
251
+ For zip files, extracts the archive. For single files (e.g. .md, .pdf), copies them directly.
251
252
  After downloading, you can use Read, Glob, Grep tools to explore the codebase freely.
252
253
 
253
- Returns the local path where files are extracted.`,{referenceProjectId:i.string().describe("Reference project ID (GUID)"),targetDir:i.string().optional().describe("Optional target directory (defaults to ./ref-projects/{id})")},async({referenceProjectId:t,targetDir:e})=>{if(s("reference_project_download called",{referenceProjectId:t,targetDir:e,projectId:a||"(not set)"}),!a)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};if(!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(t))return{content:[{type:"text",text:"\u274C Invalid reference project ID format (expected GUID)"}]};try{let r;if(e){if(r=_.resolve(e),r.includes(".."))return{content:[{type:"text",text:"\u274C Invalid target directory (path traversal not allowed)"}]}}else r=_.join(C,"ref-projects",t);g.mkdirSync(r,{recursive:!0});let o=`${j}/api/projects/${a}/reference-projects/${t}/download-url`,d={"Content-Type":"application/json"};x&&(d["X-Project-Key"]=x),s("reference_project_download getting presigned URL",{urlEndpoint:o});let c=await fetch(o,{headers:d});if(!c.ok){if(c.status===404)return{content:[{type:"text",text:"\u274C Reference project not found"}]};let y=`HTTP ${c.status}`;try{let b=await c.text();try{y=JSON.parse(b).error||b}catch{y=b}}catch{}return s("reference_project_download failed to get URL:",{status:c.status,errorMessage:y}),{content:[{type:"text",text:`\u274C Failed to get download URL: ${y}`}]}}let{downloadUrl:u}=await c.json();s("reference_project_download fetching from R2",{downloadUrl:u.substring(0,100)+"..."});let f=await fetch(u);if(!f.ok)return s("reference_project_download R2 fetch failed:",{status:f.status}),{content:[{type:"text",text:`\u274C Failed to download from storage: ${f.status}`}]};let m=_.join("/tmp",`ref-project-${t}.zip`),P=await f.arrayBuffer();g.writeFileSync(m,Buffer.from(P)),s("reference_project_download extracting",{zipPath:m,extractPath:r});try{T("unzip",["-o","-q",m,"-d",r],{stdio:"pipe"})}catch(y){try{T("ditto",["-x","-k",m,r],{stdio:"pipe"})}catch{return s("reference_project_download extract failed:",y),{content:[{type:"text",text:"\u274C Failed to extract zip. Make sure 'unzip' is installed."}]}}}try{g.unlinkSync(m)}catch{}let S=0,D=y=>{let b=g.readdirSync(y,{withFileTypes:!0});for(let k of b)k.isDirectory()?D(_.join(y,k.name)):S++};return D(r),s("reference_project_download succeeded",{extractPath:r,fileCount:S}),{content:[{type:"text",text:`\u2705 Downloaded reference project to: ${r}
254
+ Returns the local path where files are extracted.`,{referenceProjectId:i.string().describe("Reference project ID (GUID)"),targetDir:i.string().optional().describe("Optional target directory (defaults to ./ref-projects/{id})")},async({referenceProjectId:t,targetDir:e})=>{if(s("reference_project_download called",{referenceProjectId:t,targetDir:e,projectId:a||"(not set)"}),!a)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};if(!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(t))return{content:[{type:"text",text:"\u274C Invalid reference project ID format (expected GUID)"}]};try{let r;if(e){if(r=y.resolve(e),r.includes(".."))return{content:[{type:"text",text:"\u274C Invalid target directory (path traversal not allowed)"}]}}else r=y.join(O,"ref-projects",t);f.mkdirSync(r,{recursive:!0});let o=`${T}/api/projects/${a}/reference-projects/${t}/download-url`,d={"Content-Type":"application/json"};w&&(d["X-Project-Key"]=w),s("reference_project_download getting presigned URL",{urlEndpoint:o});let c=await fetch(o,{headers:d});if(!c.ok){if(c.status===404)return{content:[{type:"text",text:"\u274C Reference project not found"}]};let m=`HTTP ${c.status}`;try{let b=await c.text();try{m=JSON.parse(b).error||b}catch{m=b}}catch{}return s("reference_project_download failed to get URL:",{status:c.status,errorMessage:m}),{content:[{type:"text",text:`\u274C Failed to get download URL: ${m}`}]}}let{downloadUrl:u,fileName:g}=await c.json();s("reference_project_download fetching from R2",{downloadUrl:u.substring(0,100)+"..."});let h=await fetch(u);if(!h.ok)return s("reference_project_download R2 fetch failed:",{status:h.status}),{content:[{type:"text",text:`\u274C Failed to download from storage: ${h.status}`}]};let S=g?y.extname(g).toLowerCase():".zip",k=S===".zip"||!S,x=y.join("/tmp",`ref-project-${t}${S||".zip"}`),U=await h.arrayBuffer();if(f.writeFileSync(x,Buffer.from(U)),s("reference_project_download processing",{tempPath:x,extractPath:r,isZip:k,fileName:g}),k){try{P("unzip",["-o","-q",x,"-d",r],{stdio:"pipe"})}catch(m){try{P("ditto",["-x","-k",x,r],{stdio:"pipe"})}catch{return s("reference_project_download extract failed:",m),{content:[{type:"text",text:"\u274C Failed to extract zip. Make sure 'unzip' is installed."}]}}}try{f.unlinkSync(x)}catch{}}else{let m=g||`file${S}`,b=y.join(r,m);f.copyFileSync(x,b);try{f.unlinkSync(x)}catch{}}let E=0,v=m=>{let b=f.readdirSync(m,{withFileTypes:!0});for(let I of b)I.isDirectory()?v(y.join(m,I.name)):E++};v(r);let F=k?"extracted":"downloaded";return s("reference_project_download succeeded",{extractPath:r,fileCount:E,isZip:k}),{content:[{type:"text",text:`\u2705 Downloaded reference project to: ${r}
254
255
 
255
- ${S} files extracted.
256
+ ${E} file(s) ${F}.
256
257
 
257
- You can now use Read, Glob, Grep to explore the codebase freely.`}]}}catch(r){let o=r instanceof Error?r.message:String(r);return s("reference_project_download error:",o),{content:[{type:"text",text:`\u274C Error: ${o}`}]}}});async function F(){try{s("Initializing transport...");let t=new R;s("Connecting to transport..."),await p.connect(t),s("Started successfully")}catch(t){s("Fatal error during startup:",t instanceof Error?t.message:String(t),t),process.exit(1)}}F().catch(t=>{s("Unhandled error:",t instanceof Error?t.message:String(t),t),process.exit(1)});
258
+ You can now use Read, Glob, Grep to explore the codebase freely.`}]}}catch(r){let o=r instanceof Error?r.message:String(r);return s("reference_project_download error:",o),{content:[{type:"text",text:`\u274C Error: ${o}`}]}}});async function A(){try{s("Initializing transport...");let t=new N;s("Connecting to transport..."),await p.connect(t),s("Started successfully")}catch(t){s("Fatal error during startup:",t instanceof Error?t.message:String(t),t),process.exit(1)}}A().catch(t=>{s("Unhandled error:",t instanceof Error?t.message:String(t),t),process.exit(1)});