glenn-code 1.0.0 → 1.0.2
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/adapters/mcps/stdio-server.js +224 -0
- package/dist/adapters/signalr/index.js +1260 -0
- package/dist/cli.js +43 -43
- package/dist/core.js +36 -36
- package/dist/index.d.ts +1 -1
- package/dist/index.js +55 -55
- package/package.json +1 -1
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Glenn Code - Bundled and minified
|
|
3
|
+
// (c) DNM Lab - All rights reserved
|
|
4
|
+
|
|
5
|
+
import{McpServer as E}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as I}from"@modelcontextprotocol/sdk/server/stdio.js";import{z as a}from"zod";import*as f from"fs";import*as b from"path";var k=process.env.API_URL||"http://localhost:5338",i=process.env.PROJECT_ID,_=process.env.PROJECT_KEY,D=process.env.WORKSPACE_DIR||process.cwd(),w="/tmp/agent-logs",S=b.join(w,"stdio-mcp-server.log");try{f.existsSync(w)||f.mkdirSync(w,{recursive:!0})}catch{}function n(e,...t){let s=`[${new Date().toISOString()}] ${e} ${t.map(o=>typeof o=="object"?JSON.stringify(o):String(o)).join(" ")}
|
|
6
|
+
`;console.error(`[Stdio MCP Server] ${e}`,...t);try{f.appendFileSync(S,s,"utf8")}catch{}}n("Starting...");n("API_URL:",k);n("PROJECT_ID:",i||"(not set)");n("PROJECT_KEY:",_?`${_.slice(0,12)}...`:"(not set)");n("WORKSPACE_DIR:",D);n("Process cwd:",process.cwd());n("__filename equivalent:",import.meta.url);n("Log file:",S);function $(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}function m(e){return e&&e.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g,"\uFFFD")}function h(e){if(typeof e=="string")return m(e);if(Array.isArray(e))return e.map(t=>h(t));if(e&&typeof e=="object"){let t={};for(let[r,s]of Object.entries(e))t[r]=h(s);return t}return e}async function p(e,t){let r=`${k}${e}`,s={"Content-Type":"application/json"};return _&&(s["X-Project-Key"]=_),fetch(r,{...t,headers:{...s,...t?.headers}})}var u=new E({name:"agent-planning",version:"1.0.0"});u.tool("save_specification","Save a specification. First write content to a temp file with Write tool, then call this with the file path.",{name:a.string().describe("Specification name"),filePath:a.string().describe("Path to file containing the spec content")},async({name:e,filePath:t})=>{if(n("save_specification called",{name:e,filePath:t,projectId:i||"(not set)"}),!i)return n("save_specification failed: PROJECT_ID not set"),{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};let r=b.resolve(t);if(!f.existsSync(r))return n("save_specification failed: file not found",{resolvedPath:r}),{content:[{type:"text",text:`\u274C File not found: ${t}. Use Write tool first to create the file.`}]};let s=f.readFileSync(r,"utf-8"),o=$(e);n("save_specification reading file",{resolvedPath:r,contentLength:s.length,slug:o});try{let c=await p(`/api/projects/${i}/specifications`,{method:"POST",body:JSON.stringify({slug:o,name:e,content:s})});if(!c.ok){let l=await c.text();return n("save_specification failed:",{status:c.status,error:l}),{content:[{type:"text",text:`\u274C Failed to save: ${l}`}]}}let d=await c.json();return d.success?(n("save_specification succeeded:",{name:e,slug:o}),{content:[{type:"text",text:`\u2705 Saved specification: ${o}.spec.md`}]}):(n("save_specification failed:",{result:d}),{content:[{type:"text",text:`\u274C ${d.error||"Failed to save"}`}]})}catch(c){return n("save_specification error:",c instanceof Error?c.message:String(c),c),{content:[{type:"text",text:`\u274C Error: ${c instanceof Error?c.message:String(c)}`}]}}});u.tool("read_specification","Read a specification's content",{name:a.string().describe("Specification name")},async({name:e})=>{if(!i)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};let t=$(e);try{let r=await p(`/api/projects/${i}/specifications/${t}`);if(!r.ok)return{content:[{type:"text",text:`\u274C Specification "${e}" not found.`}]};let s=await r.json();return!s.success||!s.content?{content:[{type:"text",text:`\u274C ${s.error||"Specification not found"}`}]}:{content:[{type:"text",text:m(s.content)}]}}catch(r){return{content:[{type:"text",text:`\u274C Error: ${r instanceof Error?r.message:String(r)}`}]}}});u.tool("list_specifications","List all specifications",{},async()=>{if(!i)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let e=await p(`/api/projects/${i}/specifications`);if(!e.ok)return{content:[{type:"text",text:"\u{1F4CB} No specifications found."}]};let t=await e.json();return!t.success||!t.specifications||t.specifications.length===0?{content:[{type:"text",text:"\u{1F4CB} No specifications found."}]}:{content:[{type:"text",text:`\u{1F4CB} Specifications:
|
|
7
|
+
|
|
8
|
+
${t.specifications.map(s=>`- ${s.name} (${s.slug}.spec.md)`).join(`
|
|
9
|
+
`)}`}]}}catch(e){return{content:[{type:"text",text:`\u274C Error: ${e instanceof Error?e.message:String(e)}`}]}}});u.tool("delete_specification","Delete a specification",{name:a.string().describe("Specification name")},async({name:e})=>{if(!i)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};let t=$(e);try{let r=await p(`/api/projects/${i}/specifications/${t}`,{method:"DELETE"});if(!r.ok)return{content:[{type:"text",text:`\u274C Specification "${e}" not found.`}]};let s=await r.json();return s.success?{content:[{type:"text",text:`\u{1F5D1}\uFE0F Deleted: ${t}.spec.md`}]}:{content:[{type:"text",text:`\u274C ${s.error||"Failed to delete"}`}]}}catch(r){return{content:[{type:"text",text:`\u274C Error: ${r instanceof Error?r.message:String(r)}`}]}}});var O=a.object({id:a.string().describe("Unique identifier for the question"),question:a.string().describe("The question to ask the user"),options:a.array(a.string()).optional().describe("Predefined options the user can choose from"),allowFreeText:a.boolean().optional().default(!0).describe("Allow user to type a custom answer"),multiSelect:a.boolean().optional().default(!1).describe("Allow selecting multiple options")}),x=null;u.tool("start_question_session",`Start a conversational question session to gather requirements from the user.
|
|
10
|
+
|
|
11
|
+
Call this BEFORE asking any questions. It initializes the session context.
|
|
12
|
+
|
|
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:a.string().describe("What you're trying to understand/gather (e.g., 'understand feature requirements')")},async({purpose:e})=>(n("start_question_session called",{purpose:e,projectId:i||"(not set)"}),x=e,{content:[{type:"text",text:`\u2705 Question session started.
|
|
15
|
+
Purpose: ${e}
|
|
16
|
+
|
|
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.`}]}));u.tool("ask_question",`Ask a SINGLE question to the user and wait for their answer.
|
|
18
|
+
|
|
19
|
+
This tool will:
|
|
20
|
+
1. Display the question in the UI
|
|
21
|
+
2. The user will answer
|
|
22
|
+
3. Their answer will be sent back to you as the next message
|
|
23
|
+
|
|
24
|
+
IMPORTANT RULES:
|
|
25
|
+
- Ask ONE question at a time (not multiple!)
|
|
26
|
+
- Each question should build on the previous answer
|
|
27
|
+
- After calling this tool, STOP and wait for the user's answer
|
|
28
|
+
- If user says "start implementing", "that's enough", etc. - stop asking and proceed
|
|
29
|
+
|
|
30
|
+
Parameters:
|
|
31
|
+
- question: The question text
|
|
32
|
+
- options: Optional predefined choices (user can still type custom answer)
|
|
33
|
+
- allowFreeText: Let user type custom answer (default: true)
|
|
34
|
+
- multiSelect: Allow multiple selections (default: false)`,{question:a.string().describe("The question to ask"),options:a.array(a.string()).optional().describe("Predefined answer options"),allowFreeText:a.boolean().optional().default(!0),multiSelect:a.boolean().optional().default(!1)},async({question:e,options:t,allowFreeText:r,multiSelect:s})=>{if(n("ask_question called",{question:e,projectId:i||"(not set)"}),!i)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};let c={id:`q_${Date.now()}`,question:e,options:t||[],allowFreeText:r??!0,multiSelect:s??!1};try{let d=await p(`/api/projects/${i}/questions/ask`,{method:"POST",body:JSON.stringify({question:c,sessionContext:x?{purpose:x}:null})});if(!d.ok){let l=await d.text();return n("ask_question failed:",{status:d.status,error:l}),{content:[{type:"text",text:`\u274C Failed to ask question: ${l}`}]}}return n("ask_question succeeded"),{content:[{type:"text",text:`\u2705 Question sent to user. STOP HERE and wait for their answer.
|
|
35
|
+
|
|
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(d){return n("ask_question error:",d instanceof Error?d.message:String(d)),{content:[{type:"text",text:`\u274C Error: ${d instanceof Error?d.message:String(d)}`}]}}});u.tool("end_question_session",`End the current question session and summarize what you learned.
|
|
37
|
+
|
|
38
|
+
Call this when:
|
|
39
|
+
1. You have gathered enough information to proceed
|
|
40
|
+
2. The user said "start implementing", "that's enough", "let's go", etc.
|
|
41
|
+
3. You've asked enough questions (aim for 3-7, don't exhaust the user!)
|
|
42
|
+
|
|
43
|
+
After ending the session, you can proceed with implementation.`,{summary:a.string().describe("Brief summary of what you learned from the questions")},async({summary:e})=>{if(n("end_question_session called",{summary:e,projectId:i||"(not set)"}),!i)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{await p(`/api/projects/${i}/questions/end-session`,{method:"POST",body:JSON.stringify({summary:e})})}catch(t){n("end_question_session error (non-fatal):",t instanceof Error?t.message:String(t))}return x=null,{content:[{type:"text",text:`\u2705 Question session ended.
|
|
44
|
+
|
|
45
|
+
Summary: ${e}
|
|
46
|
+
|
|
47
|
+
You may now proceed with implementation based on what you learned.`}]}});u.tool("report_learning",`Report a critical insight that would save significant time if added to CLAUDE.md.
|
|
48
|
+
Use this when you discover something important about the codebase or workflow.
|
|
49
|
+
|
|
50
|
+
Categories:
|
|
51
|
+
- missing_instruction: Something that should be documented in CLAUDE.md
|
|
52
|
+
- time_saver: Workflow tip that saves significant time
|
|
53
|
+
- gotcha: Common mistake or pitfall to avoid
|
|
54
|
+
- workflow_tip: Best practice you discovered`,{category:a.enum(["missing_instruction","time_saver","gotcha","workflow_tip"]).describe("Category of the learning"),description:a.string().describe("The insight - write it as you'd want it in CLAUDE.md"),file:a.string().optional().describe("Relevant file path if applicable")},async({category:e,description:t,file:r})=>{if(n("report_learning called",{category:e,projectId:i||"(not set)"}),!i)return n("report_learning failed: PROJECT_ID not set"),{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let s=await p(`/api/projects/${i}/insights`,{method:"POST",body:JSON.stringify({type:"Learning",category:e,description:t,file:r})});if(!s.ok){let o=await s.text();return n("report_learning failed:",{status:s.status,error:o}),{content:[{type:"text",text:`\u26A0\uFE0F Learning noted locally (API error): ${e}`}]}}return n("report_learning succeeded:",{category:e}),{content:[{type:"text",text:`\u2705 Learning recorded: ${e}`}]}}catch(s){return n("report_learning error:",s instanceof Error?s.message:String(s)),{content:[{type:"text",text:`\u26A0\uFE0F Learning noted locally (error): ${e}`}]}}});u.tool("report_bug",`Report a bug, issue, or enhancement that needs human attention.
|
|
55
|
+
|
|
56
|
+
Categories:
|
|
57
|
+
- bug: Code bug found in the application
|
|
58
|
+
- scaffold_improvement: Enhancement needed for scaffold tool
|
|
59
|
+
- agent_env: Issue with agent environment or setup
|
|
60
|
+
- code_generation: Problem with generated code patterns`,{category:a.enum(["bug","scaffold_improvement","agent_env","code_generation"]).describe("Category of the bug/issue"),description:a.string().describe("Clear description of the issue"),file:a.string().optional().describe("File where the issue was found")},async({category:e,description:t,file:r})=>{if(n("report_bug called",{category:e,projectId:i||"(not set)"}),!i)return n("report_bug failed: PROJECT_ID not set"),{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let s=await p(`/api/projects/${i}/insights`,{method:"POST",body:JSON.stringify({type:"Bug",category:e,description:t,file:r})});if(!s.ok){let o=await s.text();return n("report_bug failed:",{status:s.status,error:o}),{content:[{type:"text",text:`\u26A0\uFE0F Bug noted locally (API error): ${e}`}]}}return n("report_bug succeeded:",{category:e}),{content:[{type:"text",text:`\u{1F41B} Bug reported: ${e}`}]}}catch(s){return n("report_bug error:",s instanceof Error?s.message:String(s)),{content:[{type:"text",text:`\u26A0\uFE0F Bug noted locally (error): ${e}`}]}}});u.tool("report_debug_insight",`\u{1F6D1} DEBUG MODE TOOL: Report unexpected behavior and signal that you're stopping.
|
|
61
|
+
|
|
62
|
+
Use this when DEBUG MODE is active and you encounter:
|
|
63
|
+
- Scaffold failures or unexpected output
|
|
64
|
+
- Build errors (frontend or backend)
|
|
65
|
+
- Type errors that seem wrong
|
|
66
|
+
- Anything that deviates from expected flow
|
|
67
|
+
- Errors you don't understand
|
|
68
|
+
|
|
69
|
+
After calling this, you MUST stop and wait for user feedback.`,{category:a.enum(["scaffold_failure","build_error","unexpected_behavior","weird_error"]).describe("Category of the debug issue"),description:a.string().describe("What went wrong - be specific"),context:a.string().optional().describe("What you were trying to do when this happened"),errorOutput:a.string().optional().describe("The actual error message or output")},async({category:e,description:t,context:r,errorOutput:s})=>{if(n("report_debug_insight called",{category:e,projectId:i||"(not set)"}),!i)return n("report_debug_insight failed: PROJECT_ID not set"),{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let o=[t,r?`
|
|
70
|
+
|
|
71
|
+
**Context:** ${r}`:"",s?`
|
|
72
|
+
|
|
73
|
+
**Error Output:**
|
|
74
|
+
\`\`\`
|
|
75
|
+
${s}
|
|
76
|
+
\`\`\``:""].join(""),c=await p(`/api/projects/${i}/insights`,{method:"POST",body:JSON.stringify({type:"DebugInsight",category:e,description:o})});if(!c.ok){let d=await c.text();return n("report_debug_insight failed:",{status:c.status,error:d}),{content:[{type:"text",text:"\u{1F6D1} Debug insight recorded locally (API error). STOP and wait for user."}]}}return n("report_debug_insight succeeded:",{category:e}),{content:[{type:"text",text:`\u{1F6D1} Debug insight reported: ${e}
|
|
77
|
+
|
|
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 n("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."}]}}});u.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(n("get_kanban_board called",{projectId:i||"(not set)"}),!i)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let e=await p(`/api/kanban-boards/by-project/${i}/full`);if(!e.ok)return e.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 e.text()}`}]};let t=await e.json(),r=h(t),s=`\u{1F4CB} **${r.title}**
|
|
80
|
+
|
|
81
|
+
`;for(let o of r.columns)s+=`- **${o.title}**: ${o.cards.length} cards (id: ${o.id})
|
|
82
|
+
`;return s+="\nUse `get_column_cards` with column name to see cards.",n("get_kanban_board succeeded"),{content:[{type:"text",text:s}]}}catch(e){return n("get_kanban_board error:",e instanceof Error?e.message:String(e)),{content:[{type:"text",text:`\u274C Error: ${e instanceof Error?e.message:String(e)}`}]}}});u.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:a.string().describe("Column name (e.g., 'Todo', 'In Progress', 'Done')")},async({column:e})=>{if(n("get_column_cards called",{column:e,projectId:i||"(not set)"}),!i)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let t=await p(`/api/kanban-boards/by-project/${i}/full`);if(!t.ok)return{content:[{type:"text",text:"\u274C Failed to get board"}]};let r=await t.json(),s=h(r),o=s.columns.find(d=>d.title.toLowerCase()===e.toLowerCase());if(!o){let d=s.columns.map(l=>l.title).join(", ");return{content:[{type:"text",text:`\u274C Column "${e}" not found. Available: ${d}`}]}}if(o.cards.length===0)return{content:[{type:"text",text:`\u{1F4CB} **${o.title}**: No cards`}]};let c=`\u{1F4CB} **${o.title}** (${o.cards.length} cards)
|
|
84
|
+
|
|
85
|
+
`;for(let d of o.cards){let l=d.priority!=="Medium"?` [${d.priority}]`:"",g=d.subtasks?.length||0,y=g>0?` (${g} subtasks)`:"";c+=`- **${m(d.title)}**${l}${y}
|
|
86
|
+
id: ${d.id}
|
|
87
|
+
`}return c+="\nUse `get_card_details` with card ID for full info.",n("get_column_cards succeeded",{column:o.title,cardCount:o.cards.length}),{content:[{type:"text",text:c}]}}catch(t){return n("get_column_cards error:",t instanceof Error?t.message:String(t)),{content:[{type:"text",text:`\u274C Error: ${t instanceof Error?t.message:String(t)}`}]}}});u.tool("get_card_details","Get full details of a specific card including description and subtasks.",{cardId:a.string().describe("Card ID (GUID)")},async({cardId:e})=>{if(n("get_card_details called",{cardId:e,projectId:i||"(not set)"}),!i)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let t=await p(`/api/kanban-boards/by-project/${i}/full`);if(!t.ok)return{content:[{type:"text",text:"\u274C Failed to get board"}]};let r=await t.json(),s=h(r),o=null,c="";for(let l of s.columns){let g=l.cards.find(y=>y.id===e);if(g){o=g,c=l.title;break}}if(!o)return{content:[{type:"text",text:`\u274C Card not found: ${e}`}]};let d=`\u{1F4CB} **${m(o.title)}**
|
|
88
|
+
`;if(d+=`Column: ${c}
|
|
89
|
+
`,d+=`Priority: ${o.priority}
|
|
90
|
+
`,o.dueDate&&(d+=`Due: ${new Date(o.dueDate).toLocaleDateString()}
|
|
91
|
+
`),d+=`ID: ${o.id}
|
|
92
|
+
|
|
93
|
+
`,o.description&&(d+=`**Description:**
|
|
94
|
+
${m(o.description)}
|
|
95
|
+
|
|
96
|
+
`),o.subtasks&&o.subtasks.length>0){d+=`**Subtasks:**
|
|
97
|
+
`;for(let l of o.subtasks){let g=l.isCompleted?"\u2705":"\u2B1C";d+=`${g} ${m(l.title)} (id: ${l.id})
|
|
98
|
+
`}}return n("get_card_details succeeded",{cardId:e}),{content:[{type:"text",text:d}]}}catch(t){return n("get_card_details error:",t instanceof Error?t.message:String(t)),{content:[{type:"text",text:`\u274C Error: ${t instanceof Error?t.message:String(t)}`}]}}});u.tool("create_kanban_card",`Create a new card on the kanban board.
|
|
99
|
+
Column names are typically: Backlog, Todo, In Progress, Done
|
|
100
|
+
The tool will match column names case-insensitively.`,{title:a.string().describe("Card title (max 200 chars)"),column:a.string().describe("Column name (e.g., 'Backlog', 'Todo', 'In Progress', 'Done')"),description:a.string().optional().describe("Card description (max 2000 chars)"),priority:a.enum(["Low","Medium","High","Urgent"]).optional().describe("Priority level (default: Medium)"),dueDate:a.string().optional().describe("Due date in ISO format (e.g., '2024-12-31')")},async({title:e,column:t,description:r,priority:s,dueDate:o})=>{if(n("create_kanban_card called",{title:e,column:t,projectId:i||"(not set)"}),!i)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let c=await p(`/api/projects/${i}/kanban/cards`,{method:"POST",body:JSON.stringify({title:e,column:t,description:r,priority:s||"Medium",dueDate:o?new Date(o).toISOString():void 0})}),d=await c.json();return!c.ok||!d.success?(n("create_kanban_card failed:",{result:d}),{content:[{type:"text",text:`\u274C ${d.error||"Failed to create card"}`}]}):(n("create_kanban_card succeeded:",{id:d.card.id}),{content:[{type:"text",text:`\u2705 Created card "${d.card.title}" in ${d.card.columnName} (id: ${d.card.id})`}]})}catch(c){return n("create_kanban_card error:",c instanceof Error?c.message:String(c)),{content:[{type:"text",text:`\u274C Error: ${c instanceof Error?c.message:String(c)}`}]}}});u.tool("update_kanban_card",`Update an existing kanban card's properties.
|
|
101
|
+
Use get_kanban_board first to get the card ID.`,{cardId:a.string().describe("Card ID (GUID from get_kanban_board)"),title:a.string().optional().describe("New title"),description:a.string().optional().describe("New description"),priority:a.enum(["Low","Medium","High","Urgent"]).optional().describe("New priority"),dueDate:a.string().optional().describe("New due date in ISO format")},async({cardId:e,title:t,description:r,priority:s,dueDate:o})=>{if(n("update_kanban_card called",{cardId:e,projectId:i||"(not set)"}),!i)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let c=await p(`/api/projects/${i}/kanban/cards/${e}`,{method:"PUT",body:JSON.stringify({title:t,description:r,priority:s,dueDate:o?new Date(o).toISOString():void 0})}),d=await c.json();return!c.ok||!d.success?(n("update_kanban_card failed:",{result:d}),{content:[{type:"text",text:`\u274C ${d.error||"Failed to update card"}`}]}):(n("update_kanban_card succeeded:",{id:d.card.id}),{content:[{type:"text",text:`\u2705 Updated card "${d.card.title}"`}]})}catch(c){return n("update_kanban_card error:",c instanceof Error?c.message:String(c)),{content:[{type:"text",text:`\u274C Error: ${c instanceof Error?c.message:String(c)}`}]}}});u.tool("move_kanban_card",`Move a card to a different column.
|
|
102
|
+
Use get_kanban_board first to get the card ID.
|
|
103
|
+
Column names (EXACT, case-sensitive): "Backlog", "Todo", "In Progress", "Done"`,{cardId:a.string().describe("Card ID (GUID from get_kanban_board)"),column:a.string().describe("Target column name"),position:a.number().optional().describe("Position in column (0 = top, omit for bottom)")},async({cardId:e,column:t,position:r})=>{if(n("move_kanban_card called",{cardId:e,column:t,projectId:i||"(not set)"}),!i)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let s=await p(`/api/projects/${i}/kanban/cards/${e}/move`,{method:"PUT",body:JSON.stringify({column:t,position:r})}),o=await s.json();return!s.ok||!o.success?(n("move_kanban_card failed:",{result:o}),{content:[{type:"text",text:`\u274C ${o.error||"Failed to move card"}`}]}):(n("move_kanban_card succeeded:",{id:o.card.id,column:o.card.columnName}),{content:[{type:"text",text:`\u2705 Moved card "${o.card.title}" to ${o.card.columnName}`}]})}catch(s){return n("move_kanban_card error:",s instanceof Error?s.message:String(s)),{content:[{type:"text",text:`\u274C Error: ${s instanceof Error?s.message:String(s)}`}]}}});u.tool("delete_kanban_card",`Delete a kanban card.
|
|
104
|
+
Use get_kanban_board first to get the card ID.`,{cardId:a.string().describe("Card ID (GUID from get_kanban_board)")},async({cardId:e})=>{if(n("delete_kanban_card called",{cardId:e,projectId:i||"(not set)"}),!i)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let t=await p(`/api/projects/${i}/kanban/cards/${e}`,{method:"DELETE"}),r=await t.json();return!t.ok||!r.success?(n("delete_kanban_card failed:",{result:r}),{content:[{type:"text",text:`\u274C ${r.error||"Failed to delete card"}`}]}):(n("delete_kanban_card succeeded:",{cardId:e}),{content:[{type:"text",text:"\u{1F5D1}\uFE0F Deleted card"}]})}catch(t){return n("delete_kanban_card error:",t instanceof Error?t.message:String(t)),{content:[{type:"text",text:`\u274C Error: ${t instanceof Error?t.message:String(t)}`}]}}});u.tool("create_subtask",`Add a subtask to a kanban card.
|
|
105
|
+
Use get_kanban_board first to get the card ID.`,{cardId:a.string().describe("Parent card ID (GUID from get_kanban_board)"),title:a.string().describe("Subtask title (max 200 chars)")},async({cardId:e,title:t})=>{if(n("create_subtask called",{cardId:e,title:t,projectId:i||"(not set)"}),!i)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let r=await p(`/api/projects/${i}/kanban/cards/${e}/subtasks`,{method:"POST",body:JSON.stringify({title:t})}),s=await r.json();return!r.ok||!s.success?(n("create_subtask failed:",{result:s}),{content:[{type:"text",text:`\u274C ${s.error||"Failed to create subtask"}`}]}):(n("create_subtask succeeded:",{id:s.subtask.id}),{content:[{type:"text",text:`\u2705 Created subtask "${s.subtask.title}" (id: ${s.subtask.id})`}]})}catch(r){return n("create_subtask error:",r instanceof Error?r.message:String(r)),{content:[{type:"text",text:`\u274C Error: ${r instanceof Error?r.message:String(r)}`}]}}});u.tool("toggle_subtask",`Toggle a subtask's completion status.
|
|
106
|
+
Use get_kanban_board first to get the subtask ID.`,{subtaskId:a.string().describe("Subtask ID (GUID from get_kanban_board)")},async({subtaskId:e})=>{if(n("toggle_subtask called",{subtaskId:e,projectId:i||"(not set)"}),!i)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let t=await p(`/api/projects/${i}/kanban/subtasks/${e}/toggle`,{method:"PUT"}),r=await t.json();if(!t.ok||!r.success)return n("toggle_subtask failed:",{result:r}),{content:[{type:"text",text:`\u274C ${r.error||"Failed to toggle subtask"}`}]};let s=r.subtask.isCompleted?"completed \u2705":"uncompleted \u2B1C";return n("toggle_subtask succeeded:",{id:r.subtask.id,isCompleted:r.subtask.isCompleted}),{content:[{type:"text",text:`\u2705 Subtask "${r.subtask.title}" marked as ${s}`}]}}catch(t){return n("toggle_subtask error:",t instanceof Error?t.message:String(t)),{content:[{type:"text",text:`\u274C Error: ${t instanceof Error?t.message:String(t)}`}]}}});u.tool("delete_subtask",`Delete a subtask.
|
|
107
|
+
Use get_kanban_board first to get the subtask ID.`,{subtaskId:a.string().describe("Subtask ID (GUID from get_kanban_board)")},async({subtaskId:e})=>{if(n("delete_subtask called",{subtaskId:e,projectId:i||"(not set)"}),!i)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let t=await p(`/api/projects/${i}/kanban/subtasks/${e}`,{method:"DELETE"}),r=await t.json();return!t.ok||!r.success?(n("delete_subtask failed:",{result:r}),{content:[{type:"text",text:`\u274C ${r.error||"Failed to delete subtask"}`}]}):(n("delete_subtask succeeded:",{subtaskId:e}),{content:[{type:"text",text:"\u{1F5D1}\uFE0F Deleted subtask"}]})}catch(t){return n("delete_subtask error:",t instanceof Error?t.message:String(t)),{content:[{type:"text",text:`\u274C Error: ${t instanceof Error?t.message:String(t)}`}]}}});u.tool("get_test_suites",`Get all test suites for the current project.
|
|
108
|
+
Returns a list of test suites with their names, descriptions, and test counts.
|
|
109
|
+
Use this to find test suites before running them.`,{},async()=>{if(n("get_test_suites called",{projectId:i||"(not set)"}),!i)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let e=await p(`/api/projects/${i}/tests/suites`),t=await e.json();if(!e.ok||!t.success)return n("get_test_suites failed:",{result:t}),{content:[{type:"text",text:`\u274C ${t.error||"Failed to get test suites"}`}]};let r=t.suites||[];return r.length===0?{content:[{type:"text",text:"\u{1F4CB} No test suites found for this project."}]}:{content:[{type:"text",text:`\u{1F4CB} Test Suites:
|
|
110
|
+
|
|
111
|
+
${r.map(o=>`\u2022 ${o.name} (${o.testCount} tests) - ID: ${o.id}${o.lastRunStatus?` [Last: ${o.lastRunStatus}]`:""}${o.description?`
|
|
112
|
+
${o.description}`:""}`).join(`
|
|
113
|
+
`)}`}]}}catch(e){return n("get_test_suites error:",e instanceof Error?e.message:String(e)),{content:[{type:"text",text:`\u274C Error: ${e instanceof Error?e.message:String(e)}`}]}}});u.tool("get_test_suite_details",`Get detailed information about a test suite including all its tests.
|
|
114
|
+
Returns the suite with all test definitions ordered by position.
|
|
115
|
+
Use this to get the tests before running a suite.`,{suiteId:a.string().describe("Test suite ID (GUID)")},async({suiteId:e})=>{if(n("get_test_suite_details called",{suiteId:e,projectId:i||"(not set)"}),!i)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let t=await p(`/api/projects/${i}/tests/suites/${e}`),r=await t.json();if(!t.ok||!r.success)return n("get_test_suite_details failed:",{result:r}),{content:[{type:"text",text:`\u274C ${r.error||"Failed to get test suite"}`}]};let s=r.suite,o=s.tests||[],c=o.map((d,l)=>`${l+1}. ${d.name}${d.lastRunStatus?` [${d.lastRunStatus}]`:""}
|
|
116
|
+
ID: ${d.id}
|
|
117
|
+
Instructions: ${d.instructions.substring(0,200)}${d.instructions.length>200?"...":""}`).join(`
|
|
118
|
+
|
|
119
|
+
`);return{content:[{type:"text",text:`\u{1F4CB} Test Suite: ${s.name}
|
|
120
|
+
${s.description?`Description: ${s.description}
|
|
121
|
+
`:""}
|
|
122
|
+
Tests (${o.length}):
|
|
123
|
+
|
|
124
|
+
${c}`}]}}catch(t){return n("get_test_suite_details error:",t instanceof Error?t.message:String(t)),{content:[{type:"text",text:`\u274C Error: ${t instanceof Error?t.message:String(t)}`}]}}});u.tool("get_test_details",`Get detailed information about a single test.
|
|
125
|
+
Returns the test name and full instructions for the AI to execute.`,{testId:a.string().describe("Test ID (GUID)")},async({testId:e})=>{if(n("get_test_details called",{testId:e,projectId:i||"(not set)"}),!i)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let t=await p(`/api/projects/${i}/tests/${e}`),r=await t.json();if(!t.ok||!r.success)return n("get_test_details failed:",{result:r}),{content:[{type:"text",text:`\u274C ${r.error||"Failed to get test"}`}]};let s=r.test;return{content:[{type:"text",text:`\u{1F4DD} Test: ${s.name}
|
|
126
|
+
Suite: ${s.testSuiteName}
|
|
127
|
+
|
|
128
|
+
Instructions:
|
|
129
|
+
${s.instructions}`}]}}catch(t){return n("get_test_details error:",t instanceof Error?t.message:String(t)),{content:[{type:"text",text:`\u274C Error: ${t instanceof Error?t.message:String(t)}`}]}}});u.tool("run_test",`Start a test run for a single test.
|
|
130
|
+
Creates a new test run record and returns the test details for execution.
|
|
131
|
+
After calling this, execute the test using Playwright MCP tools, then call report_test_status to update the result.
|
|
132
|
+
|
|
133
|
+
The test instructions contain human-readable steps that you should follow using
|
|
134
|
+
Playwright MCP tools (browser_navigate, browser_click, browser_type, browser_snapshot, etc.).`,{testId:a.string().describe("Test ID to run (GUID)")},async({testId:e})=>{if(n("run_test called",{testId:e,projectId:i||"(not set)"}),!i)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let t=await p(`/api/projects/${i}/tests/runs`,{method:"POST",body:JSON.stringify({testId:e})}),r=await t.json();if(!t.ok||!r.success)return n("run_test failed:",{result:r}),{content:[{type:"text",text:`\u274C ${r.error||"Failed to start test run"}`}]};let s=r.test;return{content:[{type:"text",text:`\u{1F680} Started test run!
|
|
135
|
+
|
|
136
|
+
Run ID: ${r.runId}
|
|
137
|
+
Test: ${s.name}
|
|
138
|
+
|
|
139
|
+
Instructions to execute:
|
|
140
|
+
${s.instructions}
|
|
141
|
+
|
|
142
|
+
\u26A0\uFE0F Remember to call report_test_status with run ID "${r.runId}" when done.`}]}}catch(t){return n("run_test error:",t instanceof Error?t.message:String(t)),{content:[{type:"text",text:`\u274C Error: ${t instanceof Error?t.message:String(t)}`}]}}});u.tool("run_test_suite",`Start a test run for an entire test suite.
|
|
143
|
+
Creates a suite-level test run record and returns all tests to execute in order.
|
|
144
|
+
Execute each test using Playwright MCP tools, call report_test_status for each test result,
|
|
145
|
+
then call report_test_status with the suite summary at the end.`,{suiteId:a.string().describe("Test suite ID to run (GUID)")},async({suiteId:e})=>{if(n("run_test_suite called",{suiteId:e,projectId:i||"(not set)"}),!i)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let t=await p(`/api/projects/${i}/tests/runs`,{method:"POST",body:JSON.stringify({testSuiteId:e})}),r=await t.json();if(!t.ok||!r.success)return n("run_test_suite failed:",{result:r}),{content:[{type:"text",text:`\u274C ${r.error||"Failed to start test suite run"}`}]};let o=(r.tests||[]).map((c,d)=>`${d+1}. ${c.name}
|
|
146
|
+
ID: ${c.id}
|
|
147
|
+
Instructions: ${c.instructions}`).join(`
|
|
148
|
+
|
|
149
|
+
`);return{content:[{type:"text",text:`\u{1F680} Started test suite run!
|
|
150
|
+
|
|
151
|
+
Run ID: ${r.runId}
|
|
152
|
+
Suite: ${r.suiteName}
|
|
153
|
+
Total Tests: ${r.totalTests}
|
|
154
|
+
|
|
155
|
+
Tests to execute:
|
|
156
|
+
|
|
157
|
+
${o}
|
|
158
|
+
|
|
159
|
+
\u26A0\uFE0F Execute each test in order, then call report_test_status with run ID "${r.runId}" to update progress.`}]}}catch(t){return n("run_test_suite error:",t instanceof Error?t.message:String(t)),{content:[{type:"text",text:`\u274C Error: ${t instanceof Error?t.message:String(t)}`}]}}});u.tool("report_test_status",`Report the status of a test run.
|
|
160
|
+
Call this after executing a test to update its status.
|
|
161
|
+
For suite runs, call with passed/failed counts as you complete each test.
|
|
162
|
+
|
|
163
|
+
Status values:
|
|
164
|
+
- "running": Test is in progress
|
|
165
|
+
- "passed": Test completed successfully
|
|
166
|
+
- "failed": Test failed
|
|
167
|
+
- "cancelled": Test was cancelled`,{runId:a.string().describe("Test run ID (GUID from run_test or run_test_suite)"),status:a.enum(["running","passed","failed","cancelled"]).describe("New status"),resultSummary:a.string().optional().describe("Summary of test results (what was verified, any issues)"),errorMessage:a.string().optional().describe("Short error message if test failed"),errorDetails:a.string().optional().describe("Full error details/stack trace if failed"),passedTests:a.number().optional().describe("For suite runs: count of passed tests so far"),failedTests:a.number().optional().describe("For suite runs: count of failed tests so far")},async({runId:e,status:t,resultSummary:r,errorMessage:s,errorDetails:o,passedTests:c,failedTests:d})=>{if(n("report_test_status called",{runId:e,status:t,projectId:i||"(not set)"}),!i)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let l=await p(`/api/projects/${i}/tests/runs/${e}`,{method:"PUT",body:JSON.stringify({status:t,resultSummary:r,errorMessage:s,errorDetails:o,passedTests:c,failedTests:d})}),g=await l.json();return!l.ok||!g.success?(n("report_test_status failed:",{result:g}),{content:[{type:"text",text:`\u274C ${g.error||"Failed to update test status"}`}]}):{content:[{type:"text",text:`${t==="passed"?"\u2705":t==="failed"?"\u274C":t==="cancelled"?"\u{1F6AB}":"\u{1F504}"} Test run updated: ${t.toUpperCase()}${g.endedAt?`
|
|
168
|
+
Completed at: ${g.endedAt}`:""}`}]}}catch(l){return n("report_test_status error:",l instanceof Error?l.message:String(l)),{content:[{type:"text",text:`\u274C Error: ${l instanceof Error?l.message:String(l)}`}]}}});u.tool("create_test_suite",`Create a new test suite for organizing tests.
|
|
169
|
+
A test suite is a logical grouping of related tests (e.g., "Login Tests", "Checkout Flow").
|
|
170
|
+
|
|
171
|
+
Use this to create a container for related tests before adding individual tests to it.`,{name:a.string().describe("Name of the test suite (e.g., 'Login Tests', 'Checkout Flow')"),description:a.string().optional().describe("Optional description of what this suite tests")},async({name:e,description:t})=>{if(n("create_test_suite called",{name:e,projectId:i||"(not set)"}),!i)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let r=await p(`/api/projects/${i}/tests/suites`,{method:"POST",body:JSON.stringify({name:e,description:t})}),s=await r.json();return!r.ok||!s.success?(n("create_test_suite failed:",{result:s}),{content:[{type:"text",text:`\u274C ${s.error||"Failed to create test suite"}`}]}):{content:[{type:"text",text:`\u2705 Created test suite "${s.name}"
|
|
172
|
+
|
|
173
|
+
Suite ID: ${s.suiteId}
|
|
174
|
+
|
|
175
|
+
You can now add tests to this suite using create_test.`}]}}catch(r){return n("create_test_suite error:",r instanceof Error?r.message:String(r)),{content:[{type:"text",text:`\u274C Error: ${r instanceof Error?r.message:String(r)}`}]}}});u.tool("create_test",`Create a new test within a test suite.
|
|
176
|
+
The test should have clear, step-by-step instructions that can be executed using Playwright.
|
|
177
|
+
|
|
178
|
+
Write instructions that are:
|
|
179
|
+
- Clear and specific (mention exact selectors, text, or elements)
|
|
180
|
+
- Step-by-step (numbered steps work well)
|
|
181
|
+
- Verifiable (include what to check/assert at the end)
|
|
182
|
+
|
|
183
|
+
Example instructions:
|
|
184
|
+
1. Navigate to /login
|
|
185
|
+
2. Enter "test@example.com" in the email field
|
|
186
|
+
3. Enter "password123" in the password field
|
|
187
|
+
4. Click the "Sign In" button
|
|
188
|
+
5. Verify the URL changes to /dashboard
|
|
189
|
+
6. Verify a welcome message is displayed`,{suiteId:a.string().describe("Test suite ID to add the test to (GUID)"),name:a.string().describe("Name of the test (e.g., 'Login with valid credentials')"),instructions:a.string().describe("Step-by-step instructions for the AI to execute this test using Playwright")},async({suiteId:e,name:t,instructions:r})=>{if(n("create_test called",{suiteId:e,name:t,projectId:i||"(not set)"}),!i)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let s=await p(`/api/projects/${i}/tests/suites/${e}/tests`,{method:"POST",body:JSON.stringify({name:t,instructions:r})}),o=await s.json();return!s.ok||!o.success?(n("create_test failed:",{result:o}),{content:[{type:"text",text:`\u274C ${o.error||"Failed to create test"}`}]}):{content:[{type:"text",text:`\u2705 Created test "${o.name}" in suite "${o.suiteName}"
|
|
190
|
+
|
|
191
|
+
Test ID: ${o.testId}
|
|
192
|
+
|
|
193
|
+
You can run this test using run_test with the test ID.`}]}}catch(s){return n("create_test error:",s instanceof Error?s.message:String(s)),{content:[{type:"text",text:`\u274C Error: ${s instanceof Error?s.message:String(s)}`}]}}});u.tool("update_test",`Update an existing test's name or instructions.
|
|
194
|
+
Use this to fix test instructions that are outdated or incorrect.
|
|
195
|
+
|
|
196
|
+
Common reasons to update:
|
|
197
|
+
- Selector changed (element moved or renamed)
|
|
198
|
+
- Flow changed (new steps added, steps removed)
|
|
199
|
+
- Clarify ambiguous instructions`,{testId:a.string().describe("Test ID to update (GUID)"),name:a.string().optional().describe("New name for the test (optional)"),instructions:a.string().optional().describe("New instructions for the test (optional)")},async({testId:e,name:t,instructions:r})=>{if(n("update_test called",{testId:e,projectId:i||"(not set)"}),!i)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};if(!t&&!r)return{content:[{type:"text",text:"\u274C Provide at least name or instructions to update"}]};try{let s=await p(`/api/projects/${i}/tests/${e}`,{method:"PUT",body:JSON.stringify({name:t,instructions:r})}),o=await s.json();return!s.ok||!o.success?(n("update_test failed:",{result:o}),{content:[{type:"text",text:`\u274C ${o.error||"Failed to update test"}`}]}):{content:[{type:"text",text:`\u2705 Updated test "${o.name}"
|
|
200
|
+
|
|
201
|
+
Test ID: ${o.testId}`}]}}catch(s){return n("update_test error:",s instanceof Error?s.message:String(s)),{content:[{type:"text",text:`\u274C Error: ${s instanceof Error?s.message:String(s)}`}]}}});u.tool("create_command",`Create a reusable command/prompt that can be saved to the user's command library.
|
|
202
|
+
Commands are reusable prompts or instructions that users can quickly insert into chat.
|
|
203
|
+
|
|
204
|
+
Use this when the user asks you to:
|
|
205
|
+
- Create a new command
|
|
206
|
+
- Save a prompt as a command
|
|
207
|
+
- Add a reusable instruction to their library
|
|
208
|
+
|
|
209
|
+
The command will be saved to the user's personal command library.`,{name:a.string().describe("Command name (short, descriptive)"),content:a.string().describe("The actual prompt/instruction content"),description:a.string().optional().describe("Brief description of what the command does"),category:a.string().optional().describe("Category (e.g., 'Code Review', 'Refactoring', 'Testing')"),tags:a.string().optional().describe("Comma-separated tags for searchability")},async({name:e,content:t,description:r,category:s,tags:o})=>{n("create_command called",{name:e,projectId:i||"(not set)"});try{let c=await p("/api/commands",{method:"POST",body:JSON.stringify({name:e,content:t,description:r,category:s,tags:o})});if(!c.ok){let l=await c.text();return n("create_command failed:",{status:c.status,error:l}),{content:[{type:"text",text:`\u274C Failed to create command: ${l}`}]}}let d=await c.json();return n("create_command succeeded:",{id:d.id,name:d.name}),{content:[{type:"text",text:`\u2705 Created command "${d.name}" (id: ${d.id})
|
|
210
|
+
|
|
211
|
+
You can find it in the Commands section or use "/" in chat to access it.`}]}}catch(c){return n("create_command error:",c instanceof Error?c.message:String(c)),{content:[{type:"text",text:`\u274C Error: ${c instanceof Error?c.message:String(c)}`}]}}});u.tool("get_project_prompt",`Get the current project prompt/description that defines this project's context.
|
|
212
|
+
Returns the project-specific prompt that gets injected into your system prompt.
|
|
213
|
+
This describes what the project is about, its tech stack, patterns, and key rules.`,{},async()=>{if(n("get_project_prompt called",{projectId:i||"(not set)"}),!i)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let e=await p(`/api/projects/${i}/prompt`,{method:"GET"}),t=await e.json();return e.ok?(n("get_project_prompt succeeded:",{hasPrompt:!!t.prompt}),t.prompt?{content:[{type:"text",text:`\u{1F4CB} **Project Prompt:**
|
|
214
|
+
|
|
215
|
+
${m(t.prompt)}`}]}:{content:[{type:"text",text:"\u{1F4DD} No project prompt set. Use update_project_prompt to set one."}]}):(n("get_project_prompt failed:",{result:t}),{content:[{type:"text",text:`\u274C ${t.error||"Failed to get project prompt"}`}]})}catch(e){return n("get_project_prompt error:",e instanceof Error?e.message:String(e)),{content:[{type:"text",text:`\u274C Error: ${e instanceof Error?e.message:String(e)}`}]}}});u.tool("update_project_prompt",`Update the project prompt/description. Use this to define or refine the project context.
|
|
216
|
+
The prompt should describe:
|
|
217
|
+
- What the project is about (overview)
|
|
218
|
+
- Technology stack
|
|
219
|
+
- Architecture patterns
|
|
220
|
+
- Key features
|
|
221
|
+
- Critical rules and gotchas
|
|
222
|
+
|
|
223
|
+
This prompt will be injected into your system prompt for all future conversations.
|
|
224
|
+
Changes are synced in real-time via SignalR.`,{prompt:a.string().describe("The new project prompt content. Use XML-style sections for organization.")},async({prompt:e})=>{if(n("update_project_prompt called",{projectId:i||"(not set)",promptLength:e.length}),!i)return{content:[{type:"text",text:"\u274C PROJECT_ID not set"}]};try{let t=await p(`/api/projects/${i}/prompt`,{method:"PUT",body:JSON.stringify({prompt:e})}),r=await t.json();return t.ok?(n("update_project_prompt succeeded:",{updatedAt:r.updatedAt}),{content:[{type:"text",text:`\u2705 Project prompt updated (${e.length} chars). It will be used in your next conversation.`}]}):(n("update_project_prompt failed:",{result:r}),{content:[{type:"text",text:`\u274C ${r.error||"Failed to update project prompt"}`}]})}catch(t){return n("update_project_prompt error:",t instanceof Error?t.message:String(t)),{content:[{type:"text",text:`\u274C Error: ${t instanceof Error?t.message:String(t)}`}]}}});async function T(){try{n("Initializing transport...");let e=new I;n("Connecting to transport..."),await u.connect(e),n("Started successfully")}catch(e){n("Fatal error during startup:",e instanceof Error?e.message:String(e),e),process.exit(1)}}T().catch(e=>{n("Unhandled error:",e instanceof Error?e.message:String(e),e),process.exit(1)});
|