glenn-code 1.0.16 → 1.0.18
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/signalr/index.js +163 -86
- package/dist/cli.js +163 -86
- package/dist/core.js +167 -90
- package/dist/index.js +168 -91
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Glenn Code - Bundled and minified
|
|
2
2
|
// (c) DNM Lab - All rights reserved
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import q from"inquirer";import*as M from"fs";import*as Y from"path";import*as Qt from"os";import{fileURLToPath as En}from"url";import{createSdkMcpServer as Zt}from"@anthropic-ai/claude-agent-sdk";import{tool as fe}from"@anthropic-ai/claude-agent-sdk";import{z as ge}from"zod";import*as me from"fs";import*as wt from"path";var K=null;function ie(n){K=n}function He(n){return n.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}var Bn=fe("save_specification",`Save a specification to the database.
|
|
5
5
|
|
|
6
6
|
IMPORTANT: First write the specification content to a file using the Write tool, then call this with the file path.
|
|
7
7
|
|
|
@@ -12,17 +12,17 @@ Workflow:
|
|
|
12
12
|
|
|
13
13
|
Example:
|
|
14
14
|
1. Write tool \u2192 save to /tmp/user-auth-spec.md
|
|
15
|
-
2. save_specification(name: "User Authentication", filePath: "/tmp/user-auth-spec.md")`,{name:ge.string().describe("Specification name (e.g., 'User Authentication')"),filePath:ge.string().describe("Path to the file containing the specification content (written via Write tool)")},async n=>{if(!
|
|
15
|
+
2. save_specification(name: "User Authentication", filePath: "/tmp/user-auth-spec.md")`,{name:ge.string().describe("Specification name (e.g., 'User Authentication')"),filePath:ge.string().describe("Path to the file containing the specification content (written via Write tool)")},async n=>{if(!K)return{content:[{type:"text",text:"\u274C Planning transport not initialized"}]};let e=wt.resolve(n.filePath);if(!me.existsSync(e))return{content:[{type:"text",text:`\u274C File not found: ${n.filePath}. Use Write tool first to create the file.`}]};let t=me.readFileSync(e,"utf-8"),o=He(n.name);return await K.saveSpecification(o,t),{content:[{type:"text",text:`\u2705 Saved specification: ${o}.spec.md`}]}}),qn=fe("read_specification","Read the content of a specification.",{name:ge.string().describe("Name of the specification to read")},async n=>{if(!K)return{content:[{type:"text",text:"\u274C Planning transport not initialized"}]};let e=He(n.name),t=await K.getSpecification(e);return t?{content:[{type:"text",text:t}]}:{content:[{type:"text",text:`\u274C Specification "${n.name}" not found.`}]}}),Gn=fe("list_specifications","List all specifications.",{},async()=>{if(!K)return{content:[{type:"text",text:"\u274C Planning transport not initialized"}]};let n=await K.listSpecifications();return n.length===0?{content:[{type:"text",text:"\u{1F4CB} No specifications found."}]}:{content:[{type:"text",text:`\u{1F4CB} Specifications:
|
|
16
16
|
|
|
17
17
|
${n.map(t=>`- ${t.name} (${t.slug}.spec.md)`).join(`
|
|
18
|
-
`)}`}]}}),
|
|
18
|
+
`)}`}]}}),Kn=fe("delete_specification","Delete a specification.",{name:ge.string().describe("Name of the specification to delete")},async n=>{if(!K)return{content:[{type:"text",text:"\u274C Planning transport not initialized"}]};let e=He(n.name);return await K.deleteSpecification(e)?{content:[{type:"text",text:`\u{1F5D1}\uFE0F Deleted: ${e}.spec.md`}]}:{content:[{type:"text",text:`\u274C Specification "${n.name}" not found.`}]}});import{tool as z}from"@anthropic-ai/claude-agent-sdk";import{z as ne}from"zod";var R=null;function he(n){R=n}var kt=z("restart_services",`Rebuild and restart services. Call this after making backend changes (.cs files) to apply them.
|
|
19
19
|
|
|
20
20
|
Parameters:
|
|
21
21
|
- target: "backend" (default) | "frontend" | "both"
|
|
22
22
|
|
|
23
23
|
Use target="backend" after .cs file changes (faster, only restarts .NET).
|
|
24
24
|
Use target="frontend" if Vite is stuck (rare, HMR usually works).
|
|
25
|
-
Use target="both" if unsure or both need restart.`,{target:ne.enum(["backend","frontend","both"]).default("backend").describe("Which service to restart")},async n=>{if(!
|
|
25
|
+
Use target="both" if unsure or both need restart.`,{target:ne.enum(["backend","frontend","both"]).default("backend").describe("Which service to restart")},async n=>{if(!R)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let e=n.target||"backend";console.log(`[MCP] restart_services called (target: ${e})`);let t=await R.restartServices(e);if(!t.success){let s=[];return t.backendError&&s.push(`Backend: ${t.backendError}`),t.frontendError&&s.push(`Frontend: ${t.frontendError}`),{content:[{type:"text",text:`\u274C Restart failed:
|
|
26
26
|
${s.join(`
|
|
27
27
|
|
|
28
28
|
`)}`}]}}return{content:[{type:"text",text:`\u2705 ${e==="both"?"Backend and frontend":e==="backend"?"Backend":"Frontend"} restarted successfully.`}]}}),vt=z("stop_services",`Stop running services. Call this BEFORE running scaffold to prevent port conflicts and ensure clean migrations.
|
|
@@ -30,61 +30,84 @@ ${s.join(`
|
|
|
30
30
|
Parameters:
|
|
31
31
|
- target: "backend" (default) | "frontend" | "both"
|
|
32
32
|
|
|
33
|
-
Use target="backend" before running scaffold (required for migrations).`,{target:ne.enum(["backend","frontend","both"]).default("backend").describe("Which service to stop")},async n=>{if(!
|
|
33
|
+
Use target="backend" before running scaffold (required for migrations).`,{target:ne.enum(["backend","frontend","both"]).default("backend").describe("Which service to stop")},async n=>{if(!R)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let e=n.target||"backend";console.log(`[MCP] stop_services called (target: ${e})`);let t=e==="backend"||e==="both",o=e==="frontend"||e==="both";t&&await R.stopBackend(),o&&await R.stopFrontend();let s=5e3,r=Date.now(),i=!t,a=!o;for(;Date.now()-r<s;){let d=await R.checkHealth();if(t&&!d.api&&(i=!0),o&&!d.web&&(a=!0),i&&a)break;await new Promise(u=>setTimeout(u,500))}let c=e==="both"?"Backend and frontend":e==="backend"?"Backend":"Frontend";return{content:[{type:"text",text:i&&a?`\u2705 ${c} stopped successfully.`:`\u26A0\uFE0F ${c} stop requested but health check still shows running. Proceeding anyway.`}]}}),Ct=z("start_services",`Start services. Call this AFTER running scaffold to bring services back up.
|
|
34
34
|
|
|
35
35
|
Parameters:
|
|
36
36
|
- target: "backend" (default) | "frontend" | "both"
|
|
37
37
|
|
|
38
|
-
Use target="backend" after running scaffold.`,{target:ne.enum(["backend","frontend","both"]).default("backend").describe("Which service to start")},async n=>{if(!
|
|
39
|
-
Note: This only checks if ports are responding, NOT build/type errors. Use check_backend_build or check_frontend_types for that.`,{},async()=>{if(!
|
|
38
|
+
Use target="backend" after running scaffold.`,{target:ne.enum(["backend","frontend","both"]).default("backend").describe("Which service to start")},async n=>{if(!R)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let e=n.target||"backend";console.log(`[MCP] start_services called (target: ${e})`);let t=e==="backend"||e==="both",o=e==="frontend"||e==="both";t&&await R.startBackend(),o&&await R.startFrontend();let s=await R.waitForHealth(15e3),r=!t||s.api,i=!o||s.web,a=r&&i,c=e==="both"?"Backend and frontend":e==="backend"?"Backend":"Frontend";if(!a){let l=[];return t&&!s.api&&l.push("Backend failed to start"),o&&!s.web&&l.push("Frontend failed to start"),{content:[{type:"text",text:`\u274C ${c} failed to start: ${l.join(", ")}`}]}}return{content:[{type:"text",text:`\u2705 ${c} started successfully.`}]}}),_t=z("check_service_health",`Check if services are running and healthy. Returns current status of backend API and frontend.
|
|
39
|
+
Note: This only checks if ports are responding, NOT build/type errors. Use check_backend_build or check_frontend_types for that.`,{},async()=>{if(!R)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let n=await R.checkHealth();return{content:[{type:"text",text:`Service Health:
|
|
40
40
|
\u{1F5A5}\uFE0F Backend API: ${n.api?"\u2705 running":"\u274C not running"}
|
|
41
|
-
\u{1F310} Frontend: ${n.web?"\u2705 running":"\u274C not running"}`}]}}),Pt=z("check_backend_build","Run dotnet build to check for C# compilation errors. Use this after fixing backend code to verify the fix works.",{},async()=>{if(!
|
|
41
|
+
\u{1F310} Frontend: ${n.web?"\u2705 running":"\u274C not running"}`}]}}),Pt=z("check_backend_build","Run dotnet build to check for C# compilation errors. Use this after fixing backend code to verify the fix works.",{},async()=>{if(!R)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};console.log("[MCP] check_backend_build called");let n=await R.checkBackendBuild();return n.success?{content:[{type:"text",text:"\u2705 Backend build succeeded - no errors"}]}:{content:[{type:"text",text:`\u274C Backend build failed:
|
|
42
42
|
|
|
43
|
-
${n.errors}`}]}}),
|
|
43
|
+
${n.errors}`}]}}),Rt=z("check_frontend_types","Run TypeScript type check (tsc) on frontend code. Use this after fixing frontend code to verify the fix works.",{},async()=>{if(!R)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};console.log("[MCP] check_frontend_types called");let n=await R.checkFrontendTypes();return n.success?{content:[{type:"text",text:"\u2705 Frontend type check passed - no errors"}]}:{content:[{type:"text",text:`\u274C Frontend type errors:
|
|
44
44
|
|
|
45
45
|
${n.errors}`}]}}),zn=z("tail_service_log",`Get the last N lines of service logs. Use this to debug startup failures or verify that the application is running correctly.
|
|
46
46
|
|
|
47
47
|
Parameters:
|
|
48
48
|
- target: "backend" | "frontend"
|
|
49
|
-
- lines: number (default 50)`,{target:ne.enum(["backend","frontend"]).describe("Which log to read"),lines:ne.number().default(50).describe("Number of lines to read")},async n=>{if(!
|
|
49
|
+
- lines: number (default 50)`,{target:ne.enum(["backend","frontend"]).describe("Which log to read"),lines:ne.number().default(50).describe("Number of lines to read")},async n=>{if(!R)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let e=await R.tailLogs(n.target,n.lines);return{content:[{type:"text",text:`Last ${n.lines} lines of ${n.target} log:
|
|
50
50
|
|
|
51
51
|
${e.join(`
|
|
52
|
-
`)}`}]}}),
|
|
52
|
+
`)}`}]}}),Vn=z("check_swagger_endpoints",`Search for endpoints in the generated swagger.json. Use this to verify that new backend endpoints are correctly exposed.
|
|
53
53
|
|
|
54
54
|
Parameters:
|
|
55
|
-
- pattern: string (e.g., "users", "api/reports")`,{pattern:ne.string().describe("Text to search for in endpoint paths")},async n=>{if(!
|
|
55
|
+
- pattern: string (e.g., "users", "api/reports")`,{pattern:ne.string().describe("Text to search for in endpoint paths")},async n=>{if(!R)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let e=await R.checkSwaggerEndpoints(n.pattern);return e.foundEndpoints.length===0?{content:[{type:"text",text:`\u274C No endpoints found matching "${n.pattern}" (Total endpoints: ${e.totalEndpoints})`}]}:{content:[{type:"text",text:`\u2705 Found ${e.foundEndpoints.length} matching endpoints:
|
|
56
56
|
|
|
57
57
|
${e.foundEndpoints.join(`
|
|
58
|
-
`)}`}]}});import*as ae from"fs";import*as Xt from"path";import*as A from"fs";import*as ye from"path";function ce(n){let e=ye.join(n,".sdd","specifications");function t(){A.existsSync(e)||A.mkdirSync(e,{recursive:!0})}function o(s){return ye.join(e,`${s}.spec.md`)}return{async saveSpecification(s,r){t(),A.writeFileSync(o(s),r,"utf-8")},async getSpecification(s){t();let r=o(s);return A.existsSync(r)?A.readFileSync(r,"utf-8"):null},async listSpecifications(){return t(),A.readdirSync(e).filter(r=>r.endsWith(".spec.md")).map(r=>{let i=A.readFileSync(ye.join(e,r),"utf-8"),a=i.match(/name: "([^"]+)"/),c=i.match(/status: (\w+)/),l=i.match(/version: "([^"]+)"/);return{slug:r.replace(".spec.md",""),name:a?.[1]||r,status:c?.[1]||"unknown",version:l?.[1]||"1.0.0"}})},async deleteSpecification(s){t();let r=o(s);return A.existsSync(r)?(A.unlinkSync(r),!0):!1},async specificationExists(s){return t(),A.existsSync(o(s))}}}function Be(){return Zt({name:"agent-insights",version:"1.0.0",tools:[kt,vt,Ct,_t,Pt,
|
|
59
|
-
1.
|
|
60
|
-
2.
|
|
61
|
-
3.
|
|
62
|
-
4.
|
|
63
|
-
5.
|
|
58
|
+
`)}`}]}});import*as ae from"fs";import*as Xt from"path";import*as A from"fs";import*as ye from"path";function ce(n){let e=ye.join(n,".sdd","specifications");function t(){A.existsSync(e)||A.mkdirSync(e,{recursive:!0})}function o(s){return ye.join(e,`${s}.spec.md`)}return{async saveSpecification(s,r){t(),A.writeFileSync(o(s),r,"utf-8")},async getSpecification(s){t();let r=o(s);return A.existsSync(r)?A.readFileSync(r,"utf-8"):null},async listSpecifications(){return t(),A.readdirSync(e).filter(r=>r.endsWith(".spec.md")).map(r=>{let i=A.readFileSync(ye.join(e,r),"utf-8"),a=i.match(/name: "([^"]+)"/),c=i.match(/status: (\w+)/),l=i.match(/version: "([^"]+)"/);return{slug:r.replace(".spec.md",""),name:a?.[1]||r,status:c?.[1]||"unknown",version:l?.[1]||"1.0.0"}})},async deleteSpecification(s){t();let r=o(s);return A.existsSync(r)?(A.unlinkSync(r),!0):!1},async specificationExists(s){return t(),A.existsSync(o(s))}}}function Be(){return Zt({name:"agent-insights",version:"1.0.0",tools:[kt,vt,Ct,_t,Pt,Rt]})}import{query as bn}from"@anthropic-ai/claude-agent-sdk";var qe={description:"Delegate to this agent when scaffolding new entities or creating CRUD features. This agent handles entity generation using the scaffold CLI with multi-entity support.",model:"inherit",prompt:`You are a scaffolding specialist. Your ONLY job is:
|
|
59
|
+
1. Read the spec if referenced in the card description
|
|
60
|
+
2. Check existing features (quick ls)
|
|
61
|
+
3. Write ALL entities to ONE /tmp/scaffold.json (big bang, no phasing!)
|
|
62
|
+
4. Run scaffold CLI ONCE
|
|
63
|
+
5. Restart backend
|
|
64
|
+
6. STOP IMMEDIATELY
|
|
64
65
|
|
|
65
66
|
**\u26A1 ONE-SHOT RULE: Put ALL entities in ONE JSON. The CLI handles dependencies.**
|
|
66
67
|
|
|
68
|
+
## \u26A0\uFE0F SPEC AWARENESS (READ THIS FIRST!)
|
|
69
|
+
|
|
70
|
+
**If the card description contains "\u{1F4CB} Spec:" or mentions a specification:**
|
|
71
|
+
1. Extract the spec name/slug from the card description
|
|
72
|
+
2. Call \`read_specification("{spec-slug}")\` to get the FULL specification
|
|
73
|
+
3. Use BOTH the card description AND the spec to understand what to build
|
|
74
|
+
4. The spec contains the complete context - don't miss any fields or relations!
|
|
75
|
+
|
|
76
|
+
**Example:** If card says "\u{1F4CB} Spec: time-off-management", run:
|
|
77
|
+
\`\`\`
|
|
78
|
+
read_specification("time-off-management")
|
|
79
|
+
\`\`\`
|
|
80
|
+
|
|
67
81
|
## WORKFLOW
|
|
68
82
|
|
|
69
|
-
### Step 0:
|
|
83
|
+
### Step 0: Read Spec (IF REFERENCED)
|
|
84
|
+
If the card mentions a spec, read it first to get full context.
|
|
85
|
+
|
|
86
|
+
### Step 1: Check Existing Features (ALWAYS DO THIS)
|
|
70
87
|
\`\`\`bash
|
|
71
88
|
ls /workspace/packages/dotnet-api/Source/Features/
|
|
72
89
|
\`\`\`
|
|
73
90
|
This shows what already exists. **DO NOT scaffold entities that already exist!**
|
|
74
91
|
Common existing features: Users (auth), Authentication, KanbanBoard, Document, etc.
|
|
75
92
|
|
|
76
|
-
### Step
|
|
93
|
+
### Step 2: Write Schema
|
|
77
94
|
\`\`\`bash
|
|
78
95
|
# Use Write tool to create /tmp/scaffold.json
|
|
79
96
|
\`\`\`
|
|
80
97
|
|
|
81
|
-
### Step
|
|
98
|
+
### Step 3: Run Scaffold
|
|
82
99
|
\`\`\`bash
|
|
83
100
|
scaffold --schema /tmp/scaffold.json --output /workspace --force --full
|
|
84
101
|
\`\`\`
|
|
85
102
|
|
|
86
|
-
### Step
|
|
87
|
-
|
|
103
|
+
### Step 4: Restart Backend
|
|
104
|
+
\`\`\`bash
|
|
105
|
+
mcp__agent-insights__restart_services
|
|
106
|
+
\`\`\`
|
|
107
|
+
This regenerates swagger and applies migrations.
|
|
108
|
+
|
|
109
|
+
### Step 5: Report & STOP
|
|
110
|
+
If everything succeeded, respond with:
|
|
88
111
|
- Entities created: [names from output]
|
|
89
112
|
- Routes: /super-admin/[plural] for each
|
|
90
113
|
- DONE
|
|
@@ -239,7 +262,7 @@ DO NOT phase/split into multiple runs - that's slower and error-prone.
|
|
|
239
262
|
- Enrollment depends on Student + CourseOffering - CLI handles it
|
|
240
263
|
- ONE scaffold run creates ALL 6 entities with correct FK relationships
|
|
241
264
|
|
|
242
|
-
REMEMBER: ls \u2192 Write \u2192 Run \u2192 Restart \u2192 STOP. No verification needed.`};var be={description:"Delegate to this agent to fix errors, type issues, or bugs in the code",model:"inherit",prompt:`You are a bug-fixing specialist. Your job is to FIX errors, not just report them.
|
|
265
|
+
REMEMBER: Read spec (if referenced) \u2192 ls \u2192 Write \u2192 Run \u2192 Restart \u2192 STOP. No verification needed.`};var be={description:"Delegate to this agent to fix errors, type issues, or bugs in the code",model:"inherit",prompt:`You are a bug-fixing specialist. Your job is to FIX errors, not just report them.
|
|
243
266
|
|
|
244
267
|
## Process
|
|
245
268
|
1. Read the error message carefully - understand WHAT and WHERE
|
|
@@ -428,7 +451,7 @@ Save your spec. The spec will appear in the user's UI.
|
|
|
428
451
|
- Ask ONE question at a time (not all at once!)
|
|
429
452
|
- STOP asking if user says "start implementing"
|
|
430
453
|
- NEVER output the spec as text - use save_specification
|
|
431
|
-
- Maximum 7 questions - don't exhaust the user!`};var
|
|
454
|
+
- Maximum 7 questions - don't exhaust the user!`};var Ge={description:"Delegate to this agent for writing new backend features (non-scaffold). Custom endpoints, queries, commands, business logic.",model:"inherit",prompt:`You are a backend specialist for .NET 9 / ASP.NET Core.
|
|
432
455
|
|
|
433
456
|
## YOUR ROLE
|
|
434
457
|
Write custom backend code that goes BEYOND what scaffold generates:
|
|
@@ -437,7 +460,27 @@ Write custom backend code that goes BEYOND what scaffold generates:
|
|
|
437
460
|
- Business logic
|
|
438
461
|
- Lookup queries for dropdowns
|
|
439
462
|
|
|
463
|
+
## \u26A0\uFE0F SPEC AWARENESS (READ THIS FIRST!)
|
|
464
|
+
|
|
465
|
+
**If the card description contains "\u{1F4CB} Spec:" or mentions a specification:**
|
|
466
|
+
1. Extract the spec name/slug from the card description
|
|
467
|
+
2. Call \`read_specification("{spec-slug}")\` to get the FULL specification
|
|
468
|
+
3. Use BOTH the card description AND the spec to understand:
|
|
469
|
+
- Business logic rules
|
|
470
|
+
- Validation requirements
|
|
471
|
+
- Edge cases and special considerations
|
|
472
|
+
- How this feature fits into the bigger picture
|
|
473
|
+
|
|
474
|
+
**Example:** If card says "\u{1F4CB} Spec: time-off-management", run:
|
|
475
|
+
\`\`\`
|
|
476
|
+
read_specification("time-off-management")
|
|
477
|
+
\`\`\`
|
|
478
|
+
|
|
479
|
+
The spec often contains crucial details about business rules that may not fit in the card description!
|
|
480
|
+
|
|
440
481
|
## BEFORE YOU START
|
|
482
|
+
1. **Read the spec** (if referenced in card)
|
|
483
|
+
2. **Check existing features:**
|
|
441
484
|
\`\`\`bash
|
|
442
485
|
ls /workspace/packages/dotnet-api/Source/Features/
|
|
443
486
|
\`\`\`
|
|
@@ -500,7 +543,7 @@ public class Get{EntityB}LookupHandler : IQueryHandler<Get{EntityB}LookupQuery,
|
|
|
500
543
|
2. If build succeeds, generate swagger: \`mcp__agent-insights__restart_services\` (this regenerates swagger)
|
|
501
544
|
3. Report what you created and STOP
|
|
502
545
|
|
|
503
|
-
**\u26A0\uFE0F CRITICAL:** Always generate swagger before frontend work begins!`};var
|
|
546
|
+
**\u26A0\uFE0F CRITICAL:** Always generate swagger before frontend work begins!`};var Ke={description:"Delegate to this agent for writing frontend features. Components, hooks, pages using generated API hooks.",model:"inherit",prompt:`You are a frontend specialist for React 19 + TypeScript + MUI.
|
|
504
547
|
|
|
505
548
|
## YOUR ROLE
|
|
506
549
|
Write frontend code using GENERATED API hooks (never manual fetch):
|
|
@@ -508,28 +551,46 @@ Write frontend code using GENERATED API hooks (never manual fetch):
|
|
|
508
551
|
- Custom hooks for state management
|
|
509
552
|
- Forms with validation
|
|
510
553
|
|
|
554
|
+
## \u26A0\uFE0F SPEC AWARENESS (READ THIS FIRST!)
|
|
555
|
+
|
|
556
|
+
**If the card description contains "\u{1F4CB} Spec:" or mentions a specification:**
|
|
557
|
+
1. Extract the spec name/slug from the card description
|
|
558
|
+
2. Call \`read_specification("{spec-slug}")\` to get the FULL specification
|
|
559
|
+
3. Use BOTH the card description AND the spec to understand:
|
|
560
|
+
- User workflows and interactions
|
|
561
|
+
- UI/UX requirements
|
|
562
|
+
- What data needs to be displayed
|
|
563
|
+
- How components should behave
|
|
564
|
+
|
|
565
|
+
**Example:** If card says "\u{1F4CB} Spec: time-off-management", run:
|
|
566
|
+
\`\`\`
|
|
567
|
+
read_specification("time-off-management")
|
|
568
|
+
\`\`\`
|
|
569
|
+
|
|
570
|
+
The spec contains the full user story and UX requirements that help you build the right UI!
|
|
571
|
+
|
|
511
572
|
## BEFORE YOU START
|
|
512
|
-
1.
|
|
573
|
+
1. **Read the spec** (if referenced in card)
|
|
574
|
+
|
|
575
|
+
2. Check existing features for patterns:
|
|
513
576
|
\`\`\`bash
|
|
514
577
|
ls /workspace/packages/backoffice-web/src/applications/super-admin/features/
|
|
515
578
|
\`\`\`
|
|
516
579
|
|
|
517
|
-
|
|
580
|
+
3. Check generated API hooks exist:
|
|
518
581
|
\`\`\`bash
|
|
519
582
|
grep -l "{EntityName}" /workspace/packages/backoffice-web/src/generated/queries-commands.ts
|
|
520
583
|
\`\`\`
|
|
521
584
|
|
|
522
|
-
|
|
523
|
-
Scaffold output includes
|
|
585
|
+
4. MAKE SURE FUNCTIONALITY YOU ARE ABOUT TO BUILD DOES NOT ALREADY EXIST!
|
|
586
|
+
Scaffold output includes:
|
|
524
587
|
- {Entity}Page (full CRUD list view)
|
|
525
588
|
- {Entity}Table (table component)
|
|
526
589
|
- {Entity}CreateDialog (add form)
|
|
527
590
|
- {Entity}DetailsDialog (edit/detail view)
|
|
528
591
|
- use{Entity}Management hook (custom logic)
|
|
529
592
|
\u2192 These are COMPLETE and reusable. Never rebuild
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
.
|
|
593
|
+
Check /workspace/packages/backoffice-web/src/applications/super-admin/features/{entity}/
|
|
533
594
|
|
|
534
595
|
**\u26A0\uFE0F If hooks don't exist, STOP and tell the main agent to run backend first!**
|
|
535
596
|
|
|
@@ -771,7 +832,7 @@ Before saving, verify your context:
|
|
|
771
832
|
5. **Keep it scannable** - Use bullets, clear sections
|
|
772
833
|
6. **Update, don't replace** - If context exists, improve it rather than starting fresh`};import*as oe from"fs";import*as Et from"path";var ke=class{buffer=[];flushInterval=null;sessionId=null;outputDir;flushIntervalMs;constructor(e){this.outputDir=e.outputDir||"/tmp/agent-debug",this.flushIntervalMs=e.flushIntervalMs||3e3,e.enabled&&(this.ensureOutputDir(),this.startFlushInterval())}ensureOutputDir(){oe.existsSync(this.outputDir)||oe.mkdirSync(this.outputDir,{recursive:!0})}startFlushInterval(){this.flushInterval=setInterval(()=>{this.flush()},this.flushIntervalMs)}setSessionId(e){this.sessionId=e}log(e){let o={timestamp:new Date().toISOString(),message:e};this.buffer.push(JSON.stringify(o,null,2)+`
|
|
773
834
|
---
|
|
774
|
-
`)}getLogFilePath(){let t=(this.sessionId||"unknown").replace(/[^a-zA-Z0-9-]/g,"_").slice(0,50),o=new Date().toISOString().split("T")[0];return Et.join(this.outputDir,`session-${o}-${t}.log`)}flush(){if(this.buffer.length===0)return;let e=this.buffer.join("");this.buffer=[];let t=this.getLogFilePath();oe.appendFileSync(t,e)}stop(){this.flushInterval&&(clearInterval(this.flushInterval),this.flushInterval=null),this.flush()}};function At(n){return n?typeof n=="boolean"?n?new ke({enabled:!0}):null:n.enabled?new ke(n):null:null}import{z as h}from"zod";import*as Ce from"fs/promises";import*as
|
|
835
|
+
`)}getLogFilePath(){let t=(this.sessionId||"unknown").replace(/[^a-zA-Z0-9-]/g,"_").slice(0,50),o=new Date().toISOString().split("T")[0];return Et.join(this.outputDir,`session-${o}-${t}.log`)}flush(){if(this.buffer.length===0)return;let e=this.buffer.join("");this.buffer=[];let t=this.getLogFilePath();oe.appendFileSync(t,e)}stop(){this.flushInterval&&(clearInterval(this.flushInterval),this.flushInterval=null),this.flush()}};function At(n){return n?typeof n=="boolean"?n?new ke({enabled:!0}):null:n.enabled?new ke(n):null:null}import{z as h}from"zod";import*as Ce from"fs/promises";import*as V from"path";var xt=h.object({port:h.number(),startCommand:h.string(),buildCommand:h.string().optional(),typecheckCommand:h.string().optional(),logFile:h.string().optional(),healthEndpoint:h.string().optional(),extensions:h.array(h.string())}),en=h.object({port:h.number(),type:h.enum(["postgres","mysql","sqlite","mongodb"])}),tn=h.object({command:h.string(),schemaPath:h.string().optional()}),nn=h.object({email:h.string().optional(),name:h.string().optional(),commitPrefix:h.string().optional()}),on=h.object({enabled:h.boolean(),port:h.number().optional()}),ve=h.object({version:h.literal("1.0"),paths:h.object({workspace:h.string(),backend:h.string().optional(),frontend:h.string().optional(),features:h.object({backend:h.string().optional(),frontend:h.string().optional()}).optional()}),services:h.object({backend:xt.optional(),frontend:xt.optional(),database:en.optional()}).optional(),scaffold:tn.optional(),git:nn.optional(),techStack:h.object({backend:h.array(h.string()).optional(),frontend:h.array(h.string()).optional(),patterns:h.array(h.string()).optional()}).optional(),tunnel:on.optional()});function W(n){return{version:"1.0",paths:{workspace:n||process.env.WORKSPACE_DIR||"/workspace",backend:"packages/dotnet-api",frontend:"packages/backoffice-web",features:{backend:"Source/Features",frontend:"src/applications/super-admin/features"}},services:{backend:{port:5338,startCommand:"dotnet run",buildCommand:"dotnet build",typecheckCommand:"dotnet build --no-restore",logFile:"/tmp/api.log",healthEndpoint:"http://localhost:5338",extensions:[".cs",".csproj"]},frontend:{port:5173,startCommand:"npm run dev",typecheckCommand:"npx tsc -p tsconfig.app.json --noEmit",logFile:"/tmp/web.log",healthEndpoint:"http://localhost:5173",extensions:[".ts",".tsx",".json"]},database:{port:43594,type:"postgres"}},scaffold:{command:"scaffold --schema /tmp/scaffold.json --output /workspace --force --full",schemaPath:"/tmp/scaffold.json"},git:{email:"agent@dotnetmentor.se",name:"Agent",commitPrefix:"[agent]"},techStack:{backend:[".NET 9","PostgreSQL","MediatR (CQRS)","EF Core"],frontend:["React 19","TypeScript","Vite","MUI","TanStack Query"],patterns:["Vertical slices","CQRS","Soft delete","Timestamps"]},tunnel:{enabled:!0,port:5173}}}var sn="project-config.json",rn=".sdd";async function _e(n){let{workspaceDir:e,providedConfig:t,requireConfig:o}=n;if(t)return{config:ve.parse(t),source:"provided"};let s=V.join(e,rn,sn);try{let r=await Ce.readFile(s,"utf-8"),i=JSON.parse(r);return{config:ve.parse(i),source:"file",configPath:s}}catch(r){r instanceof Error&&"code"in r&&r.code!=="ENOENT"&&console.warn(`[Config] Warning: Invalid project-config.json at ${s}:`,r instanceof h.ZodError?r.errors:r.message)}if(o)throw new Error(`No project configuration found at ${s} and requireConfig is true`);return{config:W(e),source:"default"}}function Pe(n,e){if(e)return V.isAbsolute(e)?e:V.join(n.paths.workspace,e)}function H(n){return Pe(n,n.paths.backend)}function B(n){return Pe(n,n.paths.frontend)}function Re(n){let e=H(n);if(!(!e||!n.paths.features?.backend))return V.join(e,n.paths.features.backend)}function le(n){let e=B(n);if(!(!e||!n.paths.features?.frontend))return V.join(e,n.paths.features.frontend)}function Te(n){let e=[];return n.techStack?.backend?.length&&e.push(`**Backend:** ${n.techStack.backend.join(", ")}`),n.techStack?.frontend?.length&&e.push(`**Frontend:** ${n.techStack.frontend.join(", ")}`),n.techStack?.patterns?.length&&e.push(`**Patterns:** ${n.techStack.patterns.join(", ")}`),e.join(`
|
|
775
836
|
`)}function Ie(n){let e=[];return n.services?.backend&&e.push(`| Backend | ${H(n)} | ${n.services.backend.port} | ${n.services.backend.logFile||"N/A"} |`),n.services?.frontend&&e.push(`| Frontend | ${B(n)} | ${n.services.frontend.port} | ${n.services.frontend.logFile||"N/A"} |`),n.services?.database&&e.push(`| Database (${n.services.database.type}) | localhost | ${n.services.database.port} | - |`),e.length===0?"":`| Service | Location | Port | Logs |
|
|
776
837
|
|---------|----------|------|------|
|
|
777
838
|
${e.join(`
|
|
@@ -964,7 +1025,7 @@ ${v}
|
|
|
964
1025
|
${g}
|
|
965
1026
|
|
|
966
1027
|
${w}
|
|
967
|
-
`}function un(n){let e=
|
|
1028
|
+
`}function un(n){let e=Te(n),t=H(n),o=B(n),s=Re(n),r=le(n),i=[];s&&i.push(`- Backend: "${s}/{Entity}/"`),r&&i.push(`- Frontend: "${r}/{entity}/"`);let a=n.techStack?.patterns?.length?`
|
|
968
1029
|
**Key patterns:**
|
|
969
1030
|
${n.techStack.patterns.map(d=>`- ${d}`).join(`
|
|
970
1031
|
`)}`:"",l=n.techStack?.backend?.some(d=>d.includes(".NET")||d.includes("C#"))??!1?`
|
|
@@ -1006,19 +1067,21 @@ User sees app through **Cloudflare tunnel** (live preview in browser).
|
|
|
1006
1067
|
| Trigger | Subagent | Example |
|
|
1007
1068
|
|---------|----------|---------|
|
|
1008
1069
|
| Any new feature request | @planning | "build a golf app", "add tasks" |
|
|
1070
|
+
| Create tasks from spec | @kanban | "create tasks for time-off spec" |
|
|
1009
1071
|
| Create entity, CRUD, scaffold | @scaffolding | "add task management" |
|
|
1010
1072
|
| Custom backend code | @backend | "add lookup query" |
|
|
1011
1073
|
| Frontend UI code | @frontend | "create the UI" |
|
|
1012
1074
|
| Build error, bug, fix | @debugger | "error CS0246" |
|
|
1013
1075
|
|
|
1014
|
-
**Flow:** @planning \u2192
|
|
1076
|
+
**Flow:** @planning \u2192 @kanban (creates detailed tasks) \u2192 @scaffolding \u2192 @backend \u2192 @frontend \u2192 @debugger`:`**\u2705 ALWAYS delegate to these subagents:**
|
|
1015
1077
|
|
|
1016
1078
|
| Trigger | Subagent | Example |
|
|
1017
1079
|
|---------|----------|---------|
|
|
1018
1080
|
| Any new feature request | @planning | "build a feature", "add tasks" |
|
|
1081
|
+
| Create tasks from spec | @kanban | "create tasks for the spec" |
|
|
1019
1082
|
| Build error, bug, fix | @debugger | "fix this error" |
|
|
1020
1083
|
|
|
1021
|
-
**Flow:** @planning \u2192
|
|
1084
|
+
**Flow:** @planning \u2192 @kanban (creates detailed tasks) \u2192 Execute work \u2192 @debugger (if issues)
|
|
1022
1085
|
|
|
1023
1086
|
Note: For this project, you implement the code directly after planning (no stack-specific subagents).
|
|
1024
1087
|
Check \`.claude/skills/\` for project-specific patterns and conventions.`}function fn(n){return n?`<workflow>
|
|
@@ -1026,42 +1089,42 @@ Check \`.claude/skills/\` for project-specific patterns and conventions.`}functi
|
|
|
1026
1089
|
1. **@planning** - Gather requirements, design spec, saves it.
|
|
1027
1090
|
2. **\u23F8\uFE0F STOP & WAIT** - Tell user: "Spec is ready! Review it and tell me when to implement."
|
|
1028
1091
|
3. **User approves** - User says "looks good", "let's build".
|
|
1029
|
-
4. **KANBAN CREATION** -
|
|
1092
|
+
4. **KANBAN CREATION** - Delegate to @kanban agent OR create cards yourself:
|
|
1030
1093
|
|
|
1031
|
-
|
|
1094
|
+
**Option A: Delegate to @kanban** (recommended)
|
|
1095
|
+
- "@kanban, create tasks for the {spec-name} specification"
|
|
1096
|
+
- The kanban agent reads the spec and creates detailed cards automatically
|
|
1032
1097
|
|
|
1033
|
-
**
|
|
1098
|
+
**Option B: Create cards yourself** (if @kanban unavailable)
|
|
1099
|
+
- Read the spec with \`read_specification("{spec-slug}")\`
|
|
1100
|
+
- Create detailed cards following the format below
|
|
1034
1101
|
|
|
1035
|
-
|
|
1102
|
+
**\u26A0\uFE0F CRITICAL: Cards MUST include spec details AND a spec reference!**
|
|
1036
1103
|
|
|
1037
|
-
|
|
1038
|
-
- Entity name and all its fields/properties from the spec
|
|
1039
|
-
- Relationships to other entities (e.g., "belongs to Project", "has many Tasks")
|
|
1040
|
-
- Any enum types or special field types
|
|
1104
|
+
**WHY THIS MATTERS:** Subagents (@scaffolding, @backend, @frontend) receive ONLY the card title and description as context. They do NOT automatically see the original specification.
|
|
1041
1105
|
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
- Business rules or validation requirements
|
|
1046
|
-
- Example: "Implement GetTasksByStatus query that filters by status enum and orders by dueDate"
|
|
1106
|
+
**CARD DESCRIPTION FORMAT:**
|
|
1107
|
+
\`\`\`
|
|
1108
|
+
\u{1F4CB} Spec: {spec-slug}
|
|
1047
1109
|
|
|
1048
|
-
|
|
1049
|
-
- Specific UI components needed from the spec
|
|
1050
|
-
- User interactions and workflows
|
|
1051
|
-
- Which data needs to be displayed and how
|
|
1052
|
-
- Example: "Create TaskBoard component with drag-drop between status columns"
|
|
1110
|
+
{Specific requirements extracted from the spec}
|
|
1053
1111
|
|
|
1054
|
-
|
|
1112
|
+
---
|
|
1113
|
+
\u{1F4A1} Read full spec: read_specification("{spec-slug}")
|
|
1114
|
+
\`\`\`
|
|
1055
1115
|
|
|
1056
|
-
**
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1116
|
+
**This format ensures:**
|
|
1117
|
+
1. Subagents have immediate context (extracted details)
|
|
1118
|
+
2. Subagents can read the full spec if they need more detail
|
|
1119
|
+
3. There's a single source of truth (the spec)
|
|
1060
1120
|
|
|
1061
|
-
**
|
|
1121
|
+
**For Scaffold cards** - include: entity name, ALL fields with types, ALL relations, features
|
|
1122
|
+
**For Backend cards** - include: endpoints, business rules, validation, inputs/outputs
|
|
1123
|
+
**For Frontend cards** - include: components, user workflows, data to display
|
|
1124
|
+
|
|
1125
|
+
**GOOD example:**
|
|
1062
1126
|
- Title: "Scaffold Task Feature"
|
|
1063
|
-
- Description: "
|
|
1064
|
-
- Subtasks: "Add title field", "Add status enum", "Add Project relationship", etc.
|
|
1127
|
+
- Description: "\u{1F4CB} Spec: task-management\\n\\nCreate Task entity with fields: title (string, required), description (text), status (enum: Todo/InProgress/Done), dueDate (datetime), priority (enum: Low/Medium/High). Belongs to Project (required). Has many Comments.\\n\\n---\\n\u{1F4A1} Read full spec: read_specification(\\"task-management\\")"
|
|
1065
1128
|
|
|
1066
1129
|
5. **EXECUTION LOOP**:
|
|
1067
1130
|
- "get_kanban_board"
|
|
@@ -1082,18 +1145,26 @@ Check \`.claude/skills/\` for project-specific patterns and conventions.`}functi
|
|
|
1082
1145
|
1. **@planning** - Gather requirements, design spec, saves it.
|
|
1083
1146
|
2. **\u23F8\uFE0F STOP & WAIT** - Tell user: "Spec is ready! Review it and tell me when to implement."
|
|
1084
1147
|
3. **User approves** - User says "looks good", "let's build".
|
|
1085
|
-
4. **KANBAN CREATION** -
|
|
1148
|
+
4. **KANBAN CREATION** - Delegate to @kanban OR create cards yourself:
|
|
1149
|
+
|
|
1150
|
+
**\u26A0\uFE0F CRITICAL: Cards MUST include spec reference AND extracted details!**
|
|
1086
1151
|
|
|
1087
|
-
|
|
1152
|
+
**CARD DESCRIPTION FORMAT:**
|
|
1153
|
+
\`\`\`
|
|
1154
|
+
\u{1F4CB} Spec: {spec-slug}
|
|
1088
1155
|
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1156
|
+
{Specific requirements extracted from the spec}
|
|
1157
|
+
|
|
1158
|
+
---
|
|
1159
|
+
\u{1F4A1} Read full spec: read_specification("{spec-slug}")
|
|
1160
|
+
\`\`\`
|
|
1161
|
+
|
|
1162
|
+
This ensures you can read the full spec for context when picking up the task.
|
|
1093
1163
|
|
|
1094
1164
|
5. **EXECUTION LOOP**:
|
|
1095
1165
|
- "get_kanban_board"
|
|
1096
1166
|
- Pick top "Todo" card \u2192 Move to "In Progress"
|
|
1167
|
+
- **If card references a spec, read it first** for full context
|
|
1097
1168
|
- Implement the feature (check \`.claude/skills/\` for project patterns)
|
|
1098
1169
|
- Move card to "Done"
|
|
1099
1170
|
- Repeat until board is empty OR if you need user input.
|
|
@@ -1102,6 +1173,7 @@ Check \`.claude/skills/\` for project-specific patterns and conventions.`}functi
|
|
|
1102
1173
|
|
|
1103
1174
|
**Rules:**
|
|
1104
1175
|
- After @planning \u2192 STOP and wait for user to approve the spec
|
|
1176
|
+
- Always read the referenced spec before starting work on a card
|
|
1105
1177
|
- Check project's existing code for patterns before implementing
|
|
1106
1178
|
- Use @debugger if you encounter build errors or bugs
|
|
1107
1179
|
</workflow>`}function mn(n,e){let t=`**@planning** - Design before building:
|
|
@@ -1109,6 +1181,11 @@ Check \`.claude/skills/\` for project-specific patterns and conventions.`}functi
|
|
|
1109
1181
|
- Asks questions, designs spec
|
|
1110
1182
|
- **Saves spec using save_specification MCP tool**
|
|
1111
1183
|
- **After @planning completes: STOP and wait for user approval!**
|
|
1184
|
+
|
|
1185
|
+
**@kanban** - Break down specs into tasks:
|
|
1186
|
+
- Reads the specification and creates detailed Kanban cards
|
|
1187
|
+
- Cards include spec reference + extracted details
|
|
1188
|
+
- Subagents can work independently with full context
|
|
1112
1189
|
`;if(e){let o=!!n.scaffold?.command,s=!!n.services?.backend,r=!!n.services?.frontend;o&&(t+=`
|
|
1113
1190
|
**@scaffolding** - Create CRUD entities, database, backend & frontend:
|
|
1114
1191
|
- "Add X management" \u2192 one-shot all entities, everything from entity creation to frontend hooks and pages!
|
|
@@ -1257,14 +1334,14 @@ You have access to **Playwright MCP** tools to verify the UI works correctly aft
|
|
|
1257
1334
|
- **Multi-tab apps**: Use \`browser_tabs\` to manage multiple windows/tabs in legacy applications
|
|
1258
1335
|
- **Large snapshots**: For pages with huge dropdowns (e.g., country lists), use \`browser_evaluate\` to collapse them first
|
|
1259
1336
|
- ${e?"In local mode, the browser window will be visible to you":"In container mode, browser runs headless"}
|
|
1260
|
-
</ui-verification>`}function pe(n){return n&&n.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g,"\uFFFD")}var Ee=null;function We(n){Ee=pe(n),console.log(`[ProjectPrompt] Updated (${Ee?Ee.length:0} chars)`)}function jt(){return pe(Ee)}import*as Z from"path";import*as Qe from"fs";import{fileURLToPath as Sn}from"url";var wn=Be(),Ye=Z.dirname(Sn(import.meta.url)),Ot=[Z.resolve(Ye,"../mcps/stdio-server.js"),Z.resolve(Ye,"./adapters/mcps/stdio-server.js")],ue=Ot.find(n=>Qe.existsSync(n))||Ot[0];console.log("[MCP] Stdio server path resolved:",ue);console.log("[MCP] __dirname:",Ye);console.log("[MCP] File exists:",Qe.existsSync(ue));async function Ae(n,e={}){let{model:t,sessionId:o=null,projectId:s=null,apiUrl:r=null,callbacks:i={},debugLog:a,abortController:c,debugMode:l=!1,isLocal:d=process.env.LOCAL_MODE==="true",projectConfig:u=W(process.env.WORKSPACE_DIR||process.cwd()),useDefaultStack:g=!0}=e,w=At(a),v=new Set,b=o||"",D,j,f,O=!1,
|
|
1261
|
-
`).filter(Boolean);console.log(`[Services] Killing ${S.length} process(es) on port ${y}: ${S.join(", ")}`);for(let _ of S)try{process.kill(parseInt(_,10),"SIGKILL")}catch{}}}catch{try{ee(`fuser -k ${y}/tcp 2>/dev/null || true`,{encoding:"utf-8"})}catch{}}},p=(y,m)=>new Promise(S=>{if(!y||!y.pid){S();return}console.log(`[Services] Stopping ${m} (PID: ${y.pid})...`);try{process.kill(-y.pid,"SIGTERM")}catch{try{y.kill("SIGTERM")}catch{}}setTimeout(S,500)}),C=async()=>{if(!l||!
|
|
1262
|
-
`);x=L.pop()||"";for(let $ of L)$&&vn($)&&s&&s($)};f.stdout?.on("data",E),f.stderr?.on("data",E),f.on("exit",P=>{m.end(),P!==0&&P!==null&&s&&s(`Process exited with code ${P}`)}),f.unref()},de=async()=>{if(!d||!
|
|
1337
|
+
</ui-verification>`}function pe(n){return n&&n.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g,"\uFFFD")}var Ee=null;function We(n){Ee=pe(n),console.log(`[ProjectPrompt] Updated (${Ee?Ee.length:0} chars)`)}function jt(){return pe(Ee)}import*as Z from"path";import*as Qe from"fs";import{fileURLToPath as Sn}from"url";var wn=Be(),Ye=Z.dirname(Sn(import.meta.url)),Ot=[Z.resolve(Ye,"../mcps/stdio-server.js"),Z.resolve(Ye,"./adapters/mcps/stdio-server.js")],ue=Ot.find(n=>Qe.existsSync(n))||Ot[0];console.log("[MCP] Stdio server path resolved:",ue);console.log("[MCP] __dirname:",Ye);console.log("[MCP] File exists:",Qe.existsSync(ue));async function Ae(n,e={}){let{model:t,sessionId:o=null,projectId:s=null,apiUrl:r=null,callbacks:i={},debugLog:a,abortController:c,debugMode:l=!1,isLocal:d=process.env.LOCAL_MODE==="true",projectConfig:u=W(process.env.WORKSPACE_DIR||process.cwd()),useDefaultStack:g=!0}=e,w=At(a),v=new Set,b=o||"",D,j,f,O=!1,G=!1,F=process.env.WORKSPACE_DIR||process.cwd();console.log("[MCP] Configuring agent-planning stdio server:"),console.log("[MCP] Command: node"),console.log("[MCP] Args:",[ue]),console.log("[MCP] Env: API_URL=",process.env.API_URL||"http://localhost:5338",", PROJECT_ID=",s||process.env.PROJECT_ID||"",", PROJECT_KEY=",process.env.PROJECT_KEY?"***set***":"(not set)",", WORKSPACE_DIR=",F),console.log("[MCP] File exists check:",ue);try{for await(let p of bn({prompt:n,options:{abortController:c,cwd:F,resume:o??void 0,allowedTools:["Bash","Read","Write","Edit","MultiEdit","Glob","Grep","LS","WebFetch","NotebookRead","NotebookEdit","Skill","mcp__agent-insights__check_backend_build","mcp__agent-insights__check_frontend_types","mcp__agent-insights__check_service_health",...d?[]:["mcp__agent-insights__stop_services","mcp__agent-insights__start_services","mcp__agent-insights__restart_services"],"mcp__agent-planning__start_question_session","mcp__agent-planning__ask_question","mcp__agent-planning__end_question_session","mcp__agent-planning__save_specification","mcp__agent-planning__list_specifications","mcp__agent-planning__read_specification","mcp__agent-planning__delete_specification","mcp__agent-planning__report_learning","mcp__agent-planning__report_bug","mcp__agent-planning__report_debug_insight","mcp__agent-planning__get_kanban_board","mcp__agent-planning__get_column_cards","mcp__agent-planning__get_card_details","mcp__agent-planning__create_kanban_card","mcp__agent-planning__update_kanban_card","mcp__agent-planning__move_kanban_card","mcp__agent-planning__delete_kanban_card","mcp__agent-planning__create_subtask","mcp__agent-planning__toggle_subtask","mcp__agent-planning__delete_subtask","mcp__agent-planning__get_project_prompt","mcp__agent-planning__update_project_prompt","mcp__agent-planning__create_command","mcp__agent-planning__get_test_suites","mcp__agent-planning__get_test_suite_details","mcp__agent-planning__get_test_details","mcp__agent-planning__run_test","mcp__agent-planning__run_test_suite","mcp__agent-planning__report_test_status","mcp__agent-planning__create_test_suite","mcp__agent-planning__create_test","mcp__agent-planning__update_test","mcp__agent-planning__reference_projects_list","mcp__agent-planning__reference_project_tree","mcp__agent-planning__reference_project_read_file","mcp__agent-planning__reference_project_download","mcp__agent-planning__reference_project_update_status","mcp__playwright__browser_navigate","mcp__playwright__browser_snapshot","mcp__playwright__browser_take_screenshot","mcp__playwright__browser_click","mcp__playwright__browser_type","mcp__playwright__browser_scroll_down","mcp__playwright__browser_scroll_up","mcp__playwright__browser_go_back","mcp__playwright__browser_go_forward","mcp__playwright__browser_wait","mcp__playwright__browser_fill_form","mcp__playwright__browser_select_option","mcp__playwright__browser_press_key","mcp__playwright__browser_hover","mcp__playwright__browser_drag","mcp__playwright__browser_file_upload","mcp__playwright__browser_handle_dialog","mcp__playwright__browser_close","mcp__playwright__browser_resize","mcp__playwright__browser_console_messages","mcp__playwright__browser_network_requests","mcp__playwright__browser_screen_capture","mcp__playwright__browser_screen_move_mouse","mcp__playwright__browser_screen_click","mcp__playwright__browser_screen_drag","mcp__playwright__browser_screen_type","mcp__playwright__browser_evaluate","mcp__playwright__browser_run_code","mcp__playwright__browser_tabs","mcp__playwright__browser_tab_list","mcp__playwright__browser_tab_new","mcp__playwright__browser_tab_select","mcp__playwright__browser_tab_close"],model:t,mcpServers:{"agent-insights":wn,"agent-planning":{type:"stdio",command:"node",args:[ue],env:{WORKSPACE_DIR:F,API_URL:r||process.env.API_URL||process.env.MASTER_URL||"http://localhost:5338",PROJECT_ID:s||process.env.PROJECT_ID||"",PROJECT_KEY:process.env.PROJECT_KEY||""}},playwright:{type:"stdio",command:"npx",args:["@playwright/mcp@latest","--caps=vision",`--user-data-dir=${Z.join(F,".playwright-data")}`,...d?[]:["--headless"]]}},settingSources:["project"],disallowedTools:["AskUserQuestion","TodoWrite","EnterPlanMode"],agents:g?{scaffolding:qe,debugger:be,planning:Se,backend:Ge,frontend:Ke,"project-context":we}:{debugger:be,planning:Se,"project-context":we},systemPrompt:Dt({debugMode:l,projectPrompt:jt(),isLocal:d,projectConfig:u,useDefaultStack:g})}}))if(!(!p.uuid||v.has(p.uuid))){switch(v.add(p.uuid),w?.log(p),i.onRawMessage?.(p),p.type){case"assistant":if(p.message?.content)for(let C of p.message.content)C.type==="text"?i.onAssistantText?.(C.text):C.type==="tool_use"?i.onAssistantToolUse?.(C.name,C.input):C.type==="tool_result"&&i.onAssistantToolResult?.(C.tool_use_id,C.content);break;case"user":i.onUserMessage?.(p);break;case"result":if(p.subtype==="success")D=p.result,j=p.total_cost_usd,i.onResult?.(p.result,p.total_cost_usd);else{let C=`Agent error: ${p.subtype}`;f=C,i.onError?.(C)}G=!0;break;case"system":switch(p.subtype){case"init":b=p.session_id,w?.setSessionId(p.session_id),i.onSystemInit?.(p.session_id);break;case"status":i.onSystemStatus?.(JSON.stringify(p));break;case"compact_boundary":break;case"hook_response":break}break;case"stream_event":i.onStreamEvent?.(p);break;case"tool_progress":i.onToolProgress?.(p);break;case"auth_status":i.onAuthStatus?.(p);break}if(G)break}}catch(p){p instanceof Error&&p.name==="AbortError"||p instanceof Error&&p.message.includes("aborted by user")?(O=!0,i.onAborted?.()):(f=p instanceof Error?p.message:String(p),i.onError?.(f))}finally{w?.stop()}return console.log("after try"),{sessionId:b,result:D,cost:j,error:f,aborted:O}}import{spawn as Ut,execSync as ee}from"child_process";import*as T from"fs";import*as Nt from"http";import*as J from"path";function xe(n){let{workspaceDir:e,apiPort:t,webPort:o,onBackendError:s,onFrontendError:r,projectConfig:i=W(e)}=n,a=t??i.services?.backend?.port??5338,c=o??i.services?.frontend?.port??5173,l=H(i),d=B(i),u=i.services?.backend?.startCommand??"dotnet run",g=i.services?.backend?.buildCommand??"dotnet build",w=i.services?.backend?.typecheckCommand??"dotnet build --no-restore",v=i.services?.frontend?.startCommand??"npm run dev",b=i.services?.frontend?.typecheckCommand??"npx tsc -p tsconfig.app.json --noEmit",D=i.services?.backend?.logFile??"/tmp/api.log",j=i.services?.frontend?.logFile??"/tmp/web.log",f=null,O=null,G=y=>new Promise(m=>{let S=setTimeout(()=>m(!1),3e3);Nt.get(`http://localhost:${y}`,x=>{clearTimeout(S),m(x.statusCode!==void 0&&x.statusCode<500)}).on("error",()=>{clearTimeout(S),m(!1)})}),F=y=>{try{let m=ee(`lsof -ti:${y} 2>/dev/null || true`,{encoding:"utf-8"}).trim();if(m){let S=m.split(`
|
|
1338
|
+
`).filter(Boolean);console.log(`[Services] Killing ${S.length} process(es) on port ${y}: ${S.join(", ")}`);for(let _ of S)try{process.kill(parseInt(_,10),"SIGKILL")}catch{}}}catch{try{ee(`fuser -k ${y}/tcp 2>/dev/null || true`,{encoding:"utf-8"})}catch{}}},p=(y,m)=>new Promise(S=>{if(!y||!y.pid){S();return}console.log(`[Services] Stopping ${m} (PID: ${y.pid})...`);try{process.kill(-y.pid,"SIGTERM")}catch{try{y.kill("SIGTERM")}catch{}}setTimeout(S,500)}),C=async()=>{if(!l||!T.existsSync(l)){console.log("[Services] No backend found, skipping backend start");return}F(a),console.log(`[Services] Starting backend with: ${u}...`);let y=J.dirname(D);T.existsSync(y)||T.mkdirSync(y,{recursive:!0});let m=T.createWriteStream(D,{flags:"a"}),[S,..._]=u.split(" ");f=Ut(S,_,{cwd:l,stdio:["ignore","pipe","pipe"],detached:!0,shell:!0});let x="",E=P=>{let Q=P.toString();m.write(Q);let L=(x+Q).split(`
|
|
1339
|
+
`);x=L.pop()||"";for(let $ of L)$&&vn($)&&s&&s($)};f.stdout?.on("data",E),f.stderr?.on("data",E),f.on("exit",P=>{m.end(),P!==0&&P!==null&&s&&s(`Process exited with code ${P}`)}),f.unref()},de=async()=>{if(!d||!T.existsSync(J.join(d,"package.json"))){console.log("[Services] No frontend found, skipping frontend start");return}F(c),console.log(`[Services] Starting frontend with: ${v}...`);let y=J.dirname(j);T.existsSync(y)||T.mkdirSync(y,{recursive:!0});let[m,...S]=v.split(" ");O=Ut(m,S,{cwd:d,stdio:["ignore",T.openSync(j,"w"),T.openSync(j,"w")],detached:!0,shell:!0}),O.unref()},ft=async()=>{await p(f,"backend"),f=null,F(a)},mt=async()=>{await p(O,"frontend"),O=null,F(c)},$e=async()=>{let[y,m]=await Promise.all([G(a),G(c)]);return{api:y,web:m}},ht=async(y=15e3)=>{let m=Date.now(),S=1e3,_=0,x=null,E=null;for(console.log(`[Services] Waiting for health (timeout: ${y}ms)...`);Date.now()-m<y;){_++;let L=await $e(),$=Date.now()-m;if(L.web&&E===null&&(E=$,console.log(`[Services] \u2713 Frontend (Vite) ready after ${$}ms`)),L.api&&x===null&&(x=$,console.log(`[Services] \u2713 Backend (dotnet) ready after ${$}ms`)),console.log(`[Services] Health poll #${_} (${$}ms): api=${L.api}, web=${L.web}`),L.api&&L.web)return console.log(`[Services] \u2713 Both services healthy after ${$}ms (${_} polls)`),L;await new Promise(Jt=>setTimeout(Jt,S))}let P=await $e(),Q=Date.now()-m;return P.web&&E===null&&console.log(`[Services] \u2713 Frontend (Vite) ready after ${Q}ms`),P.api&&x===null&&console.log(`[Services] \u2713 Backend (dotnet) ready after ${Q}ms`),console.log(`[Services] \u26A0 Health timeout after ${Q}ms: api=${P.api}, web=${P.web}`),P},zt=async y=>{console.log(`[Services] Restarting ${y}...`);let m={success:!0},S=y==="backend"||y==="both",_=y==="frontend"||y==="both";if(S&&await ft(),_&&await mt(),S&&l&&T.existsSync(l)){console.log(`[Services] Building backend with: ${g}...`);try{ee(g,{cwd:l,stdio:"pipe",timeout:12e4,encoding:"utf-8"}),console.log("[Services] \u2713 Backend build succeeded")}catch(E){let P=E;m.success=!1,m.backendError=P.stderr||P.stdout||"Build failed",console.error("[Services] \u2717 Backend build failed")}}if(_&&d&&T.existsSync(J.join(d,"package.json"))){console.log(`[Services] Type-checking frontend with: ${b}...`);try{ee(b,{cwd:d,stdio:"pipe",timeout:6e4,encoding:"utf-8"}),console.log("[Services] \u2713 Frontend types OK")}catch(E){let P=E;m.success=!1,m.frontendError=P.stderr||P.stdout||"Type check failed",console.error("[Services] \u2717 Frontend type check failed")}}console.log(`[Services] Starting ${y}...`),S&&await C(),_&&await de();let x=await ht(1e4);if(S&&!x.api){m.success=!1;let E=await St("backend",20),P=E.length>0?`
|
|
1263
1340
|
Recent logs:
|
|
1264
1341
|
${E.join(`
|
|
1265
|
-
`)}`:"";m.backendError||(m.backendError=`Backend failed to start.${P}`)}return _&&!x.web&&(m.success=!1,m.frontendError||(m.frontendError="Frontend failed to start")),m},yt=async()=>{if(!l||!
|
|
1266
|
-
`).filter(Boolean)}catch(_){return[`Error reading logs: ${_ instanceof Error?_.message:String(_)}`]}};return{startBackend:C,startFrontend:de,stopBackend:ft,stopFrontend:mt,restartServices:zt,checkHealth:$e,waitForHealth:ht,getProcesses:()=>({backend:f,frontend:O}),checkBackendBuild:yt,checkFrontendTypes:bt,runTypeChecks:Jt,tailLogs:St,checkSwaggerEndpoints:async y=>{let m=V.join(e,"swagger.json");if(!R.existsSync(m))return{foundEndpoints:["Error: swagger.json not found"],totalEndpoints:0};try{let S=JSON.parse(R.readFileSync(m,"utf-8")),_=Object.keys(S.paths||{}),x=[];for(let E of _)if(E.toLowerCase().includes(y.toLowerCase())){let P=Object.keys(S.paths[E]);for(let Q of P)x.push(`${Q.toUpperCase()} ${E}`)}return{foundEndpoints:x,totalEndpoints:_.length}}catch(S){return{foundEndpoints:[`Error parsing swagger.json: ${S instanceof Error?S.message:String(S)}`],totalEndpoints:0}}}}}function vn(n){let e=n.toLowerCase();return e.includes("exception")||e.includes("fail:")||e.includes("[err]")||e.includes("[error]")}import{execSync as te}from"child_process";function De(n){let e=async()=>{try{return te("git status --porcelain",{cwd:n,encoding:"utf-8"}).trim().length>0}catch{return!1}},t=async()=>{try{return te("git branch --show-current",{cwd:n,encoding:"utf-8"}).trim()}catch{return"unknown"}};return{hasChanges:e,commitAndPush:async(s,r)=>{try{if(!await e())return{success:!0,noChanges:!0};let i=s.length>60?s.substring(0,60)+"...":s,c=`${r?"[agent] (partial)":"[agent]"} ${i}`;te("git add -A",{cwd:n}),te(`git commit -m "${c.replace(/"/g,'\\"')}"`,{cwd:n,encoding:"utf-8"});let l=te("git rev-parse --short HEAD",{cwd:n,encoding:"utf-8"}).trim();try{te("git push",{cwd:n,stdio:"pipe"})}catch(d){let u=d instanceof Error?d.message:String(d);if(u.includes("no upstream branch")){let g=await t();te(`git push --set-upstream origin ${g}`,{cwd:n,stdio:"pipe"})}else return console.error("[Git] Push failed:",u),{success:!0,commitHash:l,error:`Committed but push failed: ${u}`}}return{success:!0,commitHash:l}}catch(i){let a=i instanceof Error?i.message:String(i);return console.error("[Git] Error:",a),{success:!1,error:a}}},getCurrentBranch:t}}import*as se from"@microsoft/signalr";var X=class{constructor(e,t){this.connection=e;this.receiverMethod=t}dispose=()=>{for(let e of this.receiverMethod)this.connection.off(e.methodName,e.method)}},Nt=n=>{if(n==="IDeploymentHub")return ze.Instance;if(n==="IKanbanHub")return Ve.Instance;if(n==="IAgentHub")return Ze.Instance;if(n==="ISlaveHub")return tt.Instance;if(n==="ITestHub")return ot.Instance},Ft=n=>{if(n==="IDeploymentHubClient")return rt.Instance;if(n==="IKanbanBoardClient")return it.Instance;if(n==="IAgentReceiver")return at.Instance;if(n==="IBackofficeReceiver")return ct.Instance;if(n==="ISlaveHubClient")return lt.Instance;if(n==="ITestHubClient")return pt.Instance},ze=class n{static Instance=new n;constructor(){}createHubProxy=e=>new Je(e)},Je=class{constructor(e){this.connection=e}registerMachine=async(e,t)=>await this.connection.invoke("RegisterMachine",e,t);reportDeploymentStatus=async e=>await this.connection.invoke("ReportDeploymentStatus",e);reportHealth=async e=>await this.connection.invoke("ReportHealth",e);reportMachineStatus=async e=>await this.connection.invoke("ReportMachineStatus",e);sendDeploymentLog=async e=>await this.connection.invoke("SendDeploymentLog",e)},Ve=class n{static Instance=new n;constructor(){}createHubProxy=e=>new Xe(e)},Xe=class{constructor(e){this.connection=e}joinBoard=async e=>await this.connection.invoke("JoinBoard",e);leaveBoard=async e=>await this.connection.invoke("LeaveBoard",e)},Ze=class n{static Instance=new n;constructor(){}createHubProxy=e=>new et(e)},et=class{constructor(e){this.connection=e}registerAgent=async e=>await this.connection.invoke("RegisterAgent",e);getSecrets=async()=>await this.connection.invoke("GetSecrets");reportStatus=async(e,t)=>await this.connection.invoke("ReportStatus",e,t);reportSessionOutput=async(e,t,o)=>await this.connection.invoke("ReportSessionOutput",e,t,o);reportSessionCost=async(e,t,o)=>await this.connection.invoke("ReportSessionCost",e,t,o);reportInsight=async(e,t)=>await this.connection.invoke("ReportInsight",e,t);setClaudeSessionId=async(e,t)=>await this.connection.invoke("SetClaudeSessionId",e,t);sessionCompleted=async(e,t,o)=>await this.connection.invoke("SessionCompleted",e,t,o);initSessionCompleted=async(e,t,o,s)=>await this.connection.invoke("InitSessionCompleted",e,t,o,s);reportPreviewBuild=async(e,t,o)=>await this.connection.invoke("ReportPreviewBuild",e,t,o);saveSpecification=async(e,t,o,s)=>await this.connection.invoke("SaveSpecification",e,t,o,s);getSpecification=async(e,t)=>await this.connection.invoke("GetSpecification",e,t);listSpecifications=async e=>await this.connection.invoke("ListSpecifications",e);deleteSpecification=async(e,t)=>await this.connection.invoke("DeleteSpecification",e,t);watchContainer=async e=>await this.connection.invoke("WatchContainer",e);watchSession=async e=>await this.connection.invoke("WatchSession",e);watchSlaves=async()=>await this.connection.invoke("WatchSlaves");unwatchSlaves=async()=>await this.connection.invoke("UnwatchSlaves");sendPrompt=async(e,t,o,s)=>await this.connection.invoke("SendPrompt",e,t,o,s);stopSession=async e=>await this.connection.invoke("StopSession",e);switchSession=async(e,t)=>await this.connection.invoke("SwitchSession",e,t);createSession=async e=>await this.connection.invoke("CreateSession",e)},tt=class n{static Instance=new n;constructor(){}createHubProxy=e=>new nt(e)},nt=class{constructor(e){this.connection=e}register=async(e,t)=>await this.connection.invoke("Register",e,t);getSecrets=async()=>await this.connection.invoke("GetSecrets");containerCreated=async e=>await this.connection.invoke("ContainerCreated",e);containerReady=async e=>await this.connection.invoke("ContainerReady",e);reportCapacity=async e=>await this.connection.invoke("ReportCapacity",e)},ot=class n{static Instance=new n;constructor(){}createHubProxy=e=>new st(e)},st=class{constructor(e){this.connection=e}joinProject=async e=>await this.connection.invoke("JoinProject",e);leaveProject=async e=>await this.connection.invoke("LeaveProject",e)},rt=class n{static Instance=new n;constructor(){}register=(e,t)=>{let o=(...i)=>t.deploy(...i),s=(...i)=>t.stop(...i);e.on("Deploy",o),e.on("Stop",s);let r=[{methodName:"Deploy",method:o},{methodName:"Stop",method:s}];return new X(e,r)}},it=class n{static Instance=new n;constructor(){}register=(e,t)=>{let o=(...f)=>t.boardUpdated(...f),s=(...f)=>t.boardDeleted(...f),r=(...f)=>t.columnCreated(...f),i=(...f)=>t.columnUpdated(...f),a=(...f)=>t.columnDeleted(...f),c=(...f)=>t.cardCreated(...f),l=(...f)=>t.cardUpdated(...f),d=(...f)=>t.cardDeleted(...f),u=(...f)=>t.subtaskCreated(...f),g=(...f)=>t.subtaskUpdated(...f),w=(...f)=>t.subtaskDeleted(...f),v=(...f)=>t.noteCreated(...f),b=(...f)=>t.noteUpdated(...f),D=(...f)=>t.noteDeleted(...f);e.on("BoardUpdated",o),e.on("BoardDeleted",s),e.on("ColumnCreated",r),e.on("ColumnUpdated",i),e.on("ColumnDeleted",a),e.on("CardCreated",c),e.on("CardUpdated",l),e.on("CardDeleted",d),e.on("SubtaskCreated",u),e.on("SubtaskUpdated",g),e.on("SubtaskDeleted",w),e.on("NoteCreated",v),e.on("NoteUpdated",b),e.on("NoteDeleted",D);let j=[{methodName:"BoardUpdated",method:o},{methodName:"BoardDeleted",method:s},{methodName:"ColumnCreated",method:r},{methodName:"ColumnUpdated",method:i},{methodName:"ColumnDeleted",method:a},{methodName:"CardCreated",method:c},{methodName:"CardUpdated",method:l},{methodName:"CardDeleted",method:d},{methodName:"SubtaskCreated",method:u},{methodName:"SubtaskUpdated",method:g},{methodName:"SubtaskDeleted",method:w},{methodName:"NoteCreated",method:v},{methodName:"NoteUpdated",method:b},{methodName:"NoteDeleted",method:D}];return new X(e,j)}},at=class n{static Instance=new n;constructor(){}register=(e,t)=>{let o=(...u)=>t.initSession(...u),s=(...u)=>t.runPrompt(...u),r=(...u)=>t.setModel(...u),i=()=>t.stopAgent(),a=()=>t.ping(),c=(...u)=>t.switchSession(...u),l=(...u)=>t.projectPromptUpdated(...u);e.on("InitSession",o),e.on("RunPrompt",s),e.on("SetModel",r),e.on("StopAgent",i),e.on("Ping",a),e.on("SwitchSession",c),e.on("ProjectPromptUpdated",l);let d=[{methodName:"InitSession",method:o},{methodName:"RunPrompt",method:s},{methodName:"SetModel",method:r},{methodName:"StopAgent",method:i},{methodName:"Ping",method:a},{methodName:"SwitchSession",method:c},{methodName:"ProjectPromptUpdated",method:l}];return new X(e,d)}},ct=class n{static Instance=new n;constructor(){}register=(e,t)=>{let o=(...b)=>t.sessionOutput(...b),s=(...b)=>t.sessionCost(...b),r=(...b)=>t.sessionCompleted(...b),i=(...b)=>t.containerStatus(...b),a=(...b)=>t.slaveCapacityUpdate(...b),c=(...b)=>t.previewBuildStatus(...b),l=(...b)=>t.previewReload(...b),d=(...b)=>t.questionsReceived(...b),u=(...b)=>t.specificationUpdated(...b),g=(...b)=>t.singleQuestionReceived(...b),w=(...b)=>t.questionSessionEnded(...b);e.on("SessionOutput",o),e.on("SessionCost",s),e.on("SessionCompleted",r),e.on("ContainerStatus",i),e.on("SlaveCapacityUpdate",a),e.on("PreviewBuildStatus",c),e.on("PreviewReload",l),e.on("QuestionsReceived",d),e.on("SpecificationUpdated",u),e.on("SingleQuestionReceived",g),e.on("QuestionSessionEnded",w);let v=[{methodName:"SessionOutput",method:o},{methodName:"SessionCost",method:s},{methodName:"SessionCompleted",method:r},{methodName:"ContainerStatus",method:i},{methodName:"SlaveCapacityUpdate",method:a},{methodName:"PreviewBuildStatus",method:c},{methodName:"PreviewReload",method:l},{methodName:"QuestionsReceived",method:d},{methodName:"SpecificationUpdated",method:u},{methodName:"SingleQuestionReceived",method:g},{methodName:"QuestionSessionEnded",method:w}];return new X(e,v)}},lt=class n{static Instance=new n;constructor(){}register=(e,t)=>{let o=(...a)=>t.startContainer(...a),s=(...a)=>t.terminateContainer(...a),r=()=>t.reconnect();e.on("StartContainer",o),e.on("TerminateContainer",s),e.on("Reconnect",r);let i=[{methodName:"StartContainer",method:o},{methodName:"TerminateContainer",method:s},{methodName:"Reconnect",method:r}];return new X(e,i)}},pt=class n{static Instance=new n;constructor(){}register=(e,t)=>{let o=(...l)=>t.testRunCreated(...l),s=(...l)=>t.testRunUpdated(...l),r=(...l)=>t.testSuiteCreated(...l),i=(...l)=>t.testCreated(...l),a=(...l)=>t.testUpdated(...l);e.on("TestRunCreated",o),e.on("TestRunUpdated",s),e.on("TestSuiteCreated",r),e.on("TestCreated",i),e.on("TestUpdated",a);let c=[{methodName:"TestRunCreated",method:o},{methodName:"TestRunUpdated",method:s},{methodName:"TestSuiteCreated",method:r},{methodName:"TestCreated",method:i},{methodName:"TestUpdated",method:a}];return new X(e,c)}};var je=class{constructor(e){this.config=e;this.connection=new se.HubConnectionBuilder().withUrl(`${e.masterUrl}/api/hubs/agent`,{headers:{"X-Container-Id":e.containerId,"X-Project-Key":e.projectKey}}).withAutomaticReconnect({nextRetryDelayInMilliseconds:t=>{let o=t.previousRetryCount+1,s=Math.min(1e3*Math.pow(2,t.previousRetryCount),6e4);return console.log(`[SignalR] Reconnect attempt ${o} (will retry in ${s}ms)`),s}}).withServerTimeout(3e5).withKeepAliveInterval(1e4).configureLogging(se.LogLevel.Information).build(),this.hubProxy=Nt("IAgentHub").createHubProxy(this.connection),this.connection.onreconnected(async()=>{console.log("[SignalR] Reconnected to gateway");try{await this.hubProxy.registerAgent(this.config.containerId),console.log(`[SignalR] Re-registered as agent for container: ${this.config.containerId}`),await this.reportStatus(this.currentStatus,void 0,this.currentSessionId??void 0,this.currentProjectId??void 0)}catch(t){console.error("[SignalR] Failed to re-register agent after reconnect:",t instanceof Error?t.message:t)}}),this.connection.onclose(t=>{console.error("[SignalR] Connection closed:",t?.message),process.exit(1)})}connection;hubProxy;receiverSubscription=null;currentStatus="WarmingUp";currentSessionId=null;currentProjectId=null;tunnelUrl=null;async connect(){console.log(`[SignalR] Connecting to gateway: ${this.config.masterUrl}/api/hubs/agent`),await this.connection.start(),console.log("[SignalR] Connected to gateway!"),await this.hubProxy.registerAgent(this.config.containerId),console.log(`[SignalR] Registered as agent for container: ${this.config.containerId}`)}registerReceiver(e){this.receiverSubscription&&this.receiverSubscription.dispose(),this.receiverSubscription=Ft("IAgentReceiver").register(this.connection,e)}getHubProxy(){return this.hubProxy}getMasterUrl(){return this.config.masterUrl}getProjectKey(){return this.config.projectKey}getConnection(){return this.connection}setTunnelUrl(e){this.tunnelUrl=e}async reportStatus(e,t,o,s){if(this.currentStatus=e,o&&(this.currentSessionId=o),s&&(this.currentProjectId=s),this.connection.state===se.HubConnectionState.Connected)try{let r={status:e,error:t,sessionId:o,projectId:s,tunnelUrl:this.tunnelUrl??void 0};await this.hubProxy.reportStatus(this.config.containerId,r),console.log(`[Status] ${e}${t?` (${t})`:""}${this.tunnelUrl?` [${this.tunnelUrl}]`:""}${o?` [session: ${o}]`:""}`)}catch(r){console.error("[SignalR] Failed to report status:",r)}}async getSecrets(){let e=await this.hubProxy.getSecrets();if(!e.success)throw new Error(`Failed to get secrets: ${e.error}`);return e.secrets||{}}};function Cn(n){let e=n.match(/name:\s*"([^"]+)"/);if(e)return e[1];let t=n.match(/^#\s+(.+)$/m);return t?t[1].trim():"Untitled Specification"}var Oe=class{constructor(e,t){this.hubProxy=e;this.projectId=t}async saveSpecification(e,t){let o=Cn(t),s=await this.hubProxy.saveSpecification(this.projectId,e,o,t);if(!s.success)throw new Error(s.error||"Failed to save specification")}async getSpecification(e){let t=await this.hubProxy.getSpecification(this.projectId,e);return t.success&&t.content||null}async listSpecifications(){let e=await this.hubProxy.listSpecifications(this.projectId);return!e.success||!e.specifications?[]:e.specifications.map(t=>({slug:t.slug,name:t.name,status:t.status,version:"1.0.0"}))}async deleteSpecification(e){return(await this.hubProxy.deleteSpecification(this.projectId,e)).success}async specificationExists(e){let t=await this.hubProxy.getSpecification(this.projectId,e);return t.success&&t.content!=null}};import{exec as _n,execSync as Pn,spawn as Tn}from"child_process";import{promisify as Rn}from"util";import{createHash as Lt}from"crypto";import*as k from"fs";import*as M from"path";var Ue=class{domain;accountId;tunnelId;apiToken;zoneId;tunnelApiBase;constructor(){this.domain=process.env.CLOUDFLARE_DEPLOY_DOMAIN||process.env.DEPLOY_DOMAIN||"vibecodementor.net",this.accountId=process.env.CLOUDFLARE_ACCOUNT_ID||"",this.tunnelId=process.env.CLOUDFLARE_TUNNEL_ID||"",this.apiToken=process.env.CLOUDFLARE_API_TOKEN||"",this.zoneId=process.env.CLOUDFLARE_ZONE_ID||"",this.tunnelApiBase=`https://api.cloudflare.com/client/v4/accounts/${this.accountId}/cfd_tunnel/${this.tunnelId}/configurations`}isConfigured(){return!!(this.accountId&&this.tunnelId&&this.apiToken&&this.zoneId)}buildHostname(e){let t=this.domain.split("."),o=t.length>2?t.slice(-2).join("."):this.domain;return`${e}.${o}`}async addRoute(e,t){let o=this.buildHostname(e);if(console.log(`[TunnelManager] Adding route: ${o} -> localhost:${t}`),!this.isConfigured())return console.log("[TunnelManager] Not configured, skipping route addition"),`https://${o}`;try{let s=await this.getConfig(),r=s.ingress.findIndex(a=>a.hostname===o);if(r!==-1)s.ingress[r].service=`http://localhost:${t}`;else{let a=s.ingress.findIndex(l=>!l.hostname),c={hostname:o,service:`http://localhost:${t}`};a!==-1?s.ingress.splice(a,0,c):(s.ingress.push(c),s.ingress.push({service:"http_status:404"}))}await this.putConfig(s),await this.ensureDnsPointsToTunnel(o);let i=`https://${o}`;return console.log(`[TunnelManager] \u2713 Route added: ${i}`),i}catch(s){throw console.error("[TunnelManager] Failed to add route:",s),s}}async removeRoute(e){let t=this.buildHostname(e);if(console.log(`[TunnelManager] Removing route: ${t}`),!this.isConfigured()){console.log("[TunnelManager] Not configured, skipping route removal");return}try{let o=await this.getConfig();o.ingress=o.ingress.filter(s=>s.hostname!==t),o.ingress.some(s=>!s.hostname)||o.ingress.push({service:"http_status:404"}),await this.putConfig(o),console.log(`[TunnelManager] \u2713 Route removed: ${t}`)}catch(o){throw console.error("[TunnelManager] Failed to remove route:",o),o}}async getConfig(){let t=await(await fetch(this.tunnelApiBase,{method:"GET",headers:{Authorization:`Bearer ${this.apiToken}`,"Content-Type":"application/json"}})).json();if(!t.success)throw new Error(`Cloudflare API error: ${t.errors.map(o=>o.message).join(", ")}`);return t.result?.config||{ingress:[{service:"http_status:404"}]}}async putConfig(e){let o=await(await fetch(this.tunnelApiBase,{method:"PUT",headers:{Authorization:`Bearer ${this.apiToken}`,"Content-Type":"application/json"},body:JSON.stringify({config:e})})).json();if(!o.success)throw new Error(`Cloudflare API error: ${o.errors.map(s=>s.message).join(", ")}`);console.log("[TunnelManager] Configuration updated via Cloudflare API")}async ensureDnsPointsToTunnel(e){let t=`${this.tunnelId}.cfargotunnel.com`,o=`https://api.cloudflare.com/client/v4/zones/${this.zoneId}/dns_records`;try{let r=await(await fetch(`${o}?name=${e}&type=CNAME`,{method:"GET",headers:{Authorization:`Bearer ${this.apiToken}`,"Content-Type":"application/json"}})).json();if(!r.success||!r.result?.length){console.log(`[TunnelManager] No DNS record found for ${e}, skipping DNS update`);return}let i=r.result[0];if(i.content===t){console.log(`[TunnelManager] DNS already points to correct tunnel: ${e}`);return}console.log(`[TunnelManager] Updating DNS: ${e} from ${i.content} to ${t}`);let c=await(await fetch(`${o}/${i.id}`,{method:"PATCH",headers:{Authorization:`Bearer ${this.apiToken}`,"Content-Type":"application/json"},body:JSON.stringify({type:"CNAME",name:e,content:t,proxied:!0})})).json();c.success?console.log(`[TunnelManager] \u2713 DNS updated: ${e} -> ${t}`):console.error(`[TunnelManager] Failed to update DNS: ${c.errors.map(l=>l.message).join(", ")}`)}catch(s){console.error(`[TunnelManager] Error updating DNS for ${e}:`,s)}}};var $t=Rn(_n),I=class{start;label;constructor(e){this.label=e,this.start=Date.now(),console.log(`[TIMING] \u23F1 START: ${e}`)}stop(){let e=Date.now()-this.start,t=(e/1e3).toFixed(2);return console.log(`[TIMING] \u2713 DONE: ${this.label} (${t}s)`),e}};function U(n,e={}){return new Promise((t,o)=>{let s=Tn(n,[],{shell:!0,stdio:"inherit",cwd:e.cwd}),r=null;e.timeout&&(r=setTimeout(()=>{s.kill(),o(new Error(`Command timed out: ${n}`))},e.timeout)),s.on("close",i=>{r&&clearTimeout(r),i===0?t():o(new Error(`Command failed with code ${i}: ${n}`))}),s.on("error",i=>{r&&clearTimeout(r),o(i)})})}var Ht="main",Me=class{constructor(e,t,o,s){this.connection=e;this.serviceManager=t;this.workspaceDir=o;this.gitHubPat=s}currentBranchName=Ht;currentRepoUrl="";lastLockfileHash=null;tunnelManager=null;currentPreviewSubdomain=null;getTunnelManager(){return this.tunnelManager||(this.tunnelManager=new Ue),this.tunnelManager}async prewarmWorkspace(){let e=new I("TOTAL INIT"),t=process.env.REPO_URL,o=process.env.BRANCH_NAME||Ht,s=process.env.PREVIEW_SUBDOMAIN,r=process.env.PREVIEW_HOSTNAME,i=process.env.PROJECT_GITHUB_PAT;if(console.log("[Init] Starting single-phase init..."),console.log(`[Init] Repo: ${t?this.extractRepoPath(t):"NOT SET"}`),console.log(`[Init] Branch: ${o}`),console.log(`[Init] Preview: ${s||"none"}`),!t)throw console.error("[Init] \u274C REPO_URL not set - cannot initialize workspace"),new Error("REPO_URL environment variable is required");i&&(this.gitHubPat=i);let a=new I("Clean workspace");Pn("rm -rf /workspace/* /workspace/.* 2>/dev/null || true",{stdio:"inherit"}),a.stop();let c=new I("Clone repository");await this.cloneRepository(t,o),this.currentRepoUrl=t,this.currentBranchName=o,c.stop(),await this.installDependencies(),this.lastLockfileHash=this.getLockfileHash(),console.log("[Init] Starting services...");let l=new I("Start backend (dotnet)");await this.serviceManager.startBackend(),l.stop();let d=new I("Start frontend (vite)");await this.serviceManager.startFrontend(),d.stop();let u=new I("Wait for health check");await this.serviceManager.waitForHealth(3e4),u.stop(),s&&r&&await this.setupPreviewSubdomainFromEnv(s,r),e.stop(),console.log("[Init] \u2713 Workspace ready")}async handleInitSession(e){console.log(`[InitSession] Session ${e.sessionId} starting...`);let t=this.extractRepoPath(e.repoUrl),o=this.extractRepoPath(this.currentRepoUrl),s=t!==o,r=e.branchName!==this.currentBranchName;s||r?(console.log("[InitSession] Repo/branch mismatch - updating workspace..."),console.log(`[InitSession] Current: ${o}@${this.currentBranchName}`),console.log(`[InitSession] Requested: ${t}@${e.branchName}`),await this.connection.reportStatus("Ready",void 0,e.sessionId,e.projectId),await this.connection.getHubProxy().initSessionCompleted(process.env.CONTAINER_ID||"unknown",e.sessionId,!0,""),this.handleRepoChange(e).catch(async a=>{let c=a instanceof Error?a.message:"Unknown error";console.error("[InitSession] Background update failed:",c),await this.connection.reportStatus("Error",c,e.sessionId,e.projectId)})):(console.log("[InitSession] Workspace already matches - ready immediately"),await this.connection.reportStatus("Ready",void 0,e.sessionId,e.projectId),await this.connection.getHubProxy().initSessionCompleted(process.env.CONTAINER_ID||"unknown",e.sessionId,!0,"")),console.log(`[InitSession] \u2713 Ready for session ${e.sessionId}`)}async setupPreviewSubdomainFromEnv(e,t){let o=new I("Setup preview subdomain");console.log(`[Preview] Setting up preview subdomain: ${e} (${t})`);let s=this.getTunnelManager();if(!s){console.log("[Preview] TunnelManager not available, skipping subdomain setup"),o.stop();return}if(!s.isConfigured()){console.log("[Preview] Cloudflare not configured, skipping subdomain setup"),o.stop();return}try{let r=parseInt(process.env.ALLOCATED_HOST_PORT||"5173"),i=await s.addRoute(e,r);this.currentPreviewSubdomain=e,this.connection.setTunnelUrl(i),o.stop(),console.log(`[Preview] \u2713 Preview subdomain configured: ${i}`)}catch(r){o.stop(),console.error("[Preview] Failed to set up subdomain route:",r)}}async handleRepoChange(e){let t=new I("TOTAL REPO CHANGE");e.gitHubPat&&e.gitHubPat!==this.gitHubPat&&(console.log("[RepoChange] Updating git credentials"),this.gitHubPat=e.gitHubPat,await U(`echo "https://${e.gitHubPat}@github.com" > ~/.git-credentials`));let o=this.extractRepoPath(e.repoUrl),s=this.extractRepoPath(this.currentRepoUrl);if(o!==s){let i=new I("Clone new repository");await this.cloneRepository(e.repoUrl,e.branchName),this.currentRepoUrl=e.repoUrl,this.currentBranchName=e.branchName,i.stop(),await this.installDependencies();let a=new I("Restart services");await this.serviceManager.restartServices("both"),a.stop()}else{let i=new I(`Switch branch to ${e.branchName}`);await this.switchBranch(e.branchName),i.stop(),await this.checkNeedsReinstall()&&await this.installDependencies();let c=new I("Restart services");await this.serviceManager.restartServices("both"),c.stop()}e.previewSubdomain&&e.previewHostname&&await this.setupPreviewSubdomainFromEnv(e.previewSubdomain,e.previewHostname),t.stop(),console.log("[RepoChange] \u2713 Workspace updated")}extractRepoPath(e){let t=e.match(/github\.com[:/]([^/]+\/[^/]+?)(?:\.git)?$/);return t?t[1]:e}async cloneRepository(e,t){console.log(`[Clone] Clearing workspace and cloning ${this.extractRepoPath(e)} @ ${t}`),await U("rm -rf /workspace/* /workspace/.* 2>/dev/null || true"),await U(`git clone -b ${t} "${e}" /workspace`,{timeout:12e4}),await U('cd /workspace && git config user.email "agent@dotnetmentor.se" && git config user.name "Agent"'),console.log("[Clone] \u2713 Repository cloned")}async switchBranch(e){if(console.log(`[Branch] Switching from ${this.currentBranchName} to ${e}`),e===this.currentBranchName)console.log(`[Branch] Already on ${e}, pulling latest...`),await U(`git pull origin ${e}`,{cwd:this.workspaceDir,timeout:6e4});else{await U("git fetch origin",{cwd:this.workspaceDir,timeout:6e4});let{stdout:t}=await $t(`git ls-remote --heads origin ${e}`,{cwd:this.workspaceDir,timeout:3e4});t.trim().length>0?await U(`git checkout -B ${e} origin/${e}`,{cwd:this.workspaceDir,timeout:3e4}):(console.log(`[Branch] Branch ${e} doesn't exist on remote, creating from main...`),await U(`git checkout -b ${e}`,{cwd:this.workspaceDir,timeout:3e4})),this.currentBranchName=e}console.log(`[Branch] \u2713 Now on ${e}`)}getLockfileHash(){let e=M.join(this.workspaceDir,"packages/backoffice-web/package-lock.json");return k.existsSync(e)?Lt("sha256").update(k.readFileSync(e)).digest("hex"):null}async checkNeedsReinstall(){let e=this.getLockfileHash();return!e||!this.lastLockfileHash?!0:e!==this.lastLockfileHash}async checkCodeDiff(e,t){if(e===t)return!1;try{let{stdout:o}=await $t(`git diff ${e}..HEAD --name-only`,{cwd:this.workspaceDir,timeout:3e4}),s=o.trim();if(!s)return console.log(`[CodeDiff] No file differences between ${e} and ${t}`),!1;let r=s.split(`
|
|
1267
|
-
`).filter(c=>c.length>0);console.log(`[CodeDiff] ${r.length} files changed between ${e} and ${t}`);let i=[".cs",".tsx",".ts",".json",".csproj"],a=r.some(c=>i.some(l=>c.endsWith(l)));return a&&console.log("[CodeDiff] Found code changes that require service restart"),a}catch(o){return console.error("[CodeDiff] Failed to check diff:",o instanceof Error?o.message:o),!0}}async installDependencies(){let e=new I("TOTAL INSTALL DEPENDENCIES");if(k.existsSync(
|
|
1342
|
+
`)}`:"";m.backendError||(m.backendError=`Backend failed to start.${P}`)}return _&&!x.web&&(m.success=!1,m.frontendError||(m.frontendError="Frontend failed to start")),m},yt=async()=>{if(!l||!T.existsSync(l))return{success:!0};try{return ee(w,{cwd:l,stdio:"pipe",timeout:12e4,encoding:"utf-8"}),{success:!0}}catch(y){let m=y;return{success:!1,errors:m.stdout||m.stderr||"Build failed"}}},bt=async()=>{if(!d||!T.existsSync(J.join(d,"package.json")))return{success:!0};try{return ee(b,{cwd:d,stdio:"pipe",timeout:6e4,encoding:"utf-8"}),{success:!0}}catch(y){let m=y;return{success:!1,errors:m.stdout||m.stderr||"Type check failed"}}},Vt=async()=>{let[y,m]=await Promise.all([yt(),bt()]);return{backend:y,frontend:m}},St=async(y,m)=>{let S=y==="backend"?D:j;if(!T.existsSync(S))return[`Log file not found: ${S}`];try{return ee(`tail -n ${m} ${S}`,{encoding:"utf-8"}).split(`
|
|
1343
|
+
`).filter(Boolean)}catch(_){return[`Error reading logs: ${_ instanceof Error?_.message:String(_)}`]}};return{startBackend:C,startFrontend:de,stopBackend:ft,stopFrontend:mt,restartServices:zt,checkHealth:$e,waitForHealth:ht,getProcesses:()=>({backend:f,frontend:O}),checkBackendBuild:yt,checkFrontendTypes:bt,runTypeChecks:Vt,tailLogs:St,checkSwaggerEndpoints:async y=>{let m=J.join(e,"swagger.json");if(!T.existsSync(m))return{foundEndpoints:["Error: swagger.json not found"],totalEndpoints:0};try{let S=JSON.parse(T.readFileSync(m,"utf-8")),_=Object.keys(S.paths||{}),x=[];for(let E of _)if(E.toLowerCase().includes(y.toLowerCase())){let P=Object.keys(S.paths[E]);for(let Q of P)x.push(`${Q.toUpperCase()} ${E}`)}return{foundEndpoints:x,totalEndpoints:_.length}}catch(S){return{foundEndpoints:[`Error parsing swagger.json: ${S instanceof Error?S.message:String(S)}`],totalEndpoints:0}}}}}function vn(n){let e=n.toLowerCase();return e.includes("exception")||e.includes("fail:")||e.includes("[err]")||e.includes("[error]")}import{execSync as te}from"child_process";function De(n){let e=async()=>{try{return te("git status --porcelain",{cwd:n,encoding:"utf-8"}).trim().length>0}catch{return!1}},t=async()=>{try{return te("git branch --show-current",{cwd:n,encoding:"utf-8"}).trim()}catch{return"unknown"}};return{hasChanges:e,commitAndPush:async(s,r)=>{try{if(!await e())return{success:!0,noChanges:!0};let i=s.length>60?s.substring(0,60)+"...":s,c=`${r?"[agent] (partial)":"[agent]"} ${i}`;te("git add -A",{cwd:n}),te(`git commit -m "${c.replace(/"/g,'\\"')}"`,{cwd:n,encoding:"utf-8"});let l=te("git rev-parse --short HEAD",{cwd:n,encoding:"utf-8"}).trim();try{te("git push",{cwd:n,stdio:"pipe"})}catch(d){let u=d instanceof Error?d.message:String(d);if(u.includes("no upstream branch")){let g=await t();te(`git push --set-upstream origin ${g}`,{cwd:n,stdio:"pipe"})}else return console.error("[Git] Push failed:",u),{success:!0,commitHash:l,error:`Committed but push failed: ${u}`}}return{success:!0,commitHash:l}}catch(i){let a=i instanceof Error?i.message:String(i);return console.error("[Git] Error:",a),{success:!1,error:a}}},getCurrentBranch:t}}import*as se from"@microsoft/signalr";var X=class{constructor(e,t){this.connection=e;this.receiverMethod=t}dispose=()=>{for(let e of this.receiverMethod)this.connection.off(e.methodName,e.method)}},Mt=n=>{if(n==="IDeploymentHub")return ze.Instance;if(n==="IKanbanHub")return Je.Instance;if(n==="IAgentHub")return Ze.Instance;if(n==="ISlaveHub")return tt.Instance;if(n==="ITestHub")return ot.Instance},Ft=n=>{if(n==="IDeploymentHubClient")return rt.Instance;if(n==="IKanbanBoardClient")return it.Instance;if(n==="IAgentReceiver")return at.Instance;if(n==="IBackofficeReceiver")return ct.Instance;if(n==="ISlaveHubClient")return lt.Instance;if(n==="ITestHubClient")return pt.Instance},ze=class n{static Instance=new n;constructor(){}createHubProxy=e=>new Ve(e)},Ve=class{constructor(e){this.connection=e}registerMachine=async(e,t)=>await this.connection.invoke("RegisterMachine",e,t);reportDeploymentStatus=async e=>await this.connection.invoke("ReportDeploymentStatus",e);reportHealth=async e=>await this.connection.invoke("ReportHealth",e);reportMachineStatus=async e=>await this.connection.invoke("ReportMachineStatus",e);sendDeploymentLog=async e=>await this.connection.invoke("SendDeploymentLog",e)},Je=class n{static Instance=new n;constructor(){}createHubProxy=e=>new Xe(e)},Xe=class{constructor(e){this.connection=e}joinBoard=async e=>await this.connection.invoke("JoinBoard",e);leaveBoard=async e=>await this.connection.invoke("LeaveBoard",e)},Ze=class n{static Instance=new n;constructor(){}createHubProxy=e=>new et(e)},et=class{constructor(e){this.connection=e}registerAgent=async e=>await this.connection.invoke("RegisterAgent",e);getSecrets=async()=>await this.connection.invoke("GetSecrets");reportStatus=async(e,t)=>await this.connection.invoke("ReportStatus",e,t);reportSessionOutput=async(e,t,o)=>await this.connection.invoke("ReportSessionOutput",e,t,o);reportSessionCost=async(e,t,o)=>await this.connection.invoke("ReportSessionCost",e,t,o);reportInsight=async(e,t)=>await this.connection.invoke("ReportInsight",e,t);setClaudeSessionId=async(e,t)=>await this.connection.invoke("SetClaudeSessionId",e,t);sessionCompleted=async(e,t,o)=>await this.connection.invoke("SessionCompleted",e,t,o);initSessionCompleted=async(e,t,o,s)=>await this.connection.invoke("InitSessionCompleted",e,t,o,s);reportPreviewBuild=async(e,t,o)=>await this.connection.invoke("ReportPreviewBuild",e,t,o);saveSpecification=async(e,t,o,s)=>await this.connection.invoke("SaveSpecification",e,t,o,s);getSpecification=async(e,t)=>await this.connection.invoke("GetSpecification",e,t);listSpecifications=async e=>await this.connection.invoke("ListSpecifications",e);deleteSpecification=async(e,t)=>await this.connection.invoke("DeleteSpecification",e,t);watchContainer=async e=>await this.connection.invoke("WatchContainer",e);watchSession=async e=>await this.connection.invoke("WatchSession",e);watchSlaves=async()=>await this.connection.invoke("WatchSlaves");unwatchSlaves=async()=>await this.connection.invoke("UnwatchSlaves");sendPrompt=async(e,t,o,s)=>await this.connection.invoke("SendPrompt",e,t,o,s);stopSession=async e=>await this.connection.invoke("StopSession",e);switchSession=async(e,t)=>await this.connection.invoke("SwitchSession",e,t);createSession=async e=>await this.connection.invoke("CreateSession",e)},tt=class n{static Instance=new n;constructor(){}createHubProxy=e=>new nt(e)},nt=class{constructor(e){this.connection=e}register=async(e,t)=>await this.connection.invoke("Register",e,t);getSecrets=async()=>await this.connection.invoke("GetSecrets");containerCreated=async e=>await this.connection.invoke("ContainerCreated",e);containerReady=async e=>await this.connection.invoke("ContainerReady",e);reportCapacity=async e=>await this.connection.invoke("ReportCapacity",e)},ot=class n{static Instance=new n;constructor(){}createHubProxy=e=>new st(e)},st=class{constructor(e){this.connection=e}joinProject=async e=>await this.connection.invoke("JoinProject",e);leaveProject=async e=>await this.connection.invoke("LeaveProject",e)},rt=class n{static Instance=new n;constructor(){}register=(e,t)=>{let o=(...i)=>t.deploy(...i),s=(...i)=>t.stop(...i);e.on("Deploy",o),e.on("Stop",s);let r=[{methodName:"Deploy",method:o},{methodName:"Stop",method:s}];return new X(e,r)}},it=class n{static Instance=new n;constructor(){}register=(e,t)=>{let o=(...f)=>t.boardUpdated(...f),s=(...f)=>t.boardDeleted(...f),r=(...f)=>t.columnCreated(...f),i=(...f)=>t.columnUpdated(...f),a=(...f)=>t.columnDeleted(...f),c=(...f)=>t.cardCreated(...f),l=(...f)=>t.cardUpdated(...f),d=(...f)=>t.cardDeleted(...f),u=(...f)=>t.subtaskCreated(...f),g=(...f)=>t.subtaskUpdated(...f),w=(...f)=>t.subtaskDeleted(...f),v=(...f)=>t.noteCreated(...f),b=(...f)=>t.noteUpdated(...f),D=(...f)=>t.noteDeleted(...f);e.on("BoardUpdated",o),e.on("BoardDeleted",s),e.on("ColumnCreated",r),e.on("ColumnUpdated",i),e.on("ColumnDeleted",a),e.on("CardCreated",c),e.on("CardUpdated",l),e.on("CardDeleted",d),e.on("SubtaskCreated",u),e.on("SubtaskUpdated",g),e.on("SubtaskDeleted",w),e.on("NoteCreated",v),e.on("NoteUpdated",b),e.on("NoteDeleted",D);let j=[{methodName:"BoardUpdated",method:o},{methodName:"BoardDeleted",method:s},{methodName:"ColumnCreated",method:r},{methodName:"ColumnUpdated",method:i},{methodName:"ColumnDeleted",method:a},{methodName:"CardCreated",method:c},{methodName:"CardUpdated",method:l},{methodName:"CardDeleted",method:d},{methodName:"SubtaskCreated",method:u},{methodName:"SubtaskUpdated",method:g},{methodName:"SubtaskDeleted",method:w},{methodName:"NoteCreated",method:v},{methodName:"NoteUpdated",method:b},{methodName:"NoteDeleted",method:D}];return new X(e,j)}},at=class n{static Instance=new n;constructor(){}register=(e,t)=>{let o=(...u)=>t.initSession(...u),s=(...u)=>t.runPrompt(...u),r=(...u)=>t.setModel(...u),i=()=>t.stopAgent(),a=()=>t.ping(),c=(...u)=>t.switchSession(...u),l=(...u)=>t.projectPromptUpdated(...u);e.on("InitSession",o),e.on("RunPrompt",s),e.on("SetModel",r),e.on("StopAgent",i),e.on("Ping",a),e.on("SwitchSession",c),e.on("ProjectPromptUpdated",l);let d=[{methodName:"InitSession",method:o},{methodName:"RunPrompt",method:s},{methodName:"SetModel",method:r},{methodName:"StopAgent",method:i},{methodName:"Ping",method:a},{methodName:"SwitchSession",method:c},{methodName:"ProjectPromptUpdated",method:l}];return new X(e,d)}},ct=class n{static Instance=new n;constructor(){}register=(e,t)=>{let o=(...b)=>t.sessionOutput(...b),s=(...b)=>t.sessionCost(...b),r=(...b)=>t.sessionCompleted(...b),i=(...b)=>t.containerStatus(...b),a=(...b)=>t.slaveCapacityUpdate(...b),c=(...b)=>t.previewBuildStatus(...b),l=(...b)=>t.previewReload(...b),d=(...b)=>t.questionsReceived(...b),u=(...b)=>t.specificationUpdated(...b),g=(...b)=>t.singleQuestionReceived(...b),w=(...b)=>t.questionSessionEnded(...b);e.on("SessionOutput",o),e.on("SessionCost",s),e.on("SessionCompleted",r),e.on("ContainerStatus",i),e.on("SlaveCapacityUpdate",a),e.on("PreviewBuildStatus",c),e.on("PreviewReload",l),e.on("QuestionsReceived",d),e.on("SpecificationUpdated",u),e.on("SingleQuestionReceived",g),e.on("QuestionSessionEnded",w);let v=[{methodName:"SessionOutput",method:o},{methodName:"SessionCost",method:s},{methodName:"SessionCompleted",method:r},{methodName:"ContainerStatus",method:i},{methodName:"SlaveCapacityUpdate",method:a},{methodName:"PreviewBuildStatus",method:c},{methodName:"PreviewReload",method:l},{methodName:"QuestionsReceived",method:d},{methodName:"SpecificationUpdated",method:u},{methodName:"SingleQuestionReceived",method:g},{methodName:"QuestionSessionEnded",method:w}];return new X(e,v)}},lt=class n{static Instance=new n;constructor(){}register=(e,t)=>{let o=(...a)=>t.startContainer(...a),s=(...a)=>t.terminateContainer(...a),r=()=>t.reconnect();e.on("StartContainer",o),e.on("TerminateContainer",s),e.on("Reconnect",r);let i=[{methodName:"StartContainer",method:o},{methodName:"TerminateContainer",method:s},{methodName:"Reconnect",method:r}];return new X(e,i)}},pt=class n{static Instance=new n;constructor(){}register=(e,t)=>{let o=(...l)=>t.testRunCreated(...l),s=(...l)=>t.testRunUpdated(...l),r=(...l)=>t.testSuiteCreated(...l),i=(...l)=>t.testCreated(...l),a=(...l)=>t.testUpdated(...l);e.on("TestRunCreated",o),e.on("TestRunUpdated",s),e.on("TestSuiteCreated",r),e.on("TestCreated",i),e.on("TestUpdated",a);let c=[{methodName:"TestRunCreated",method:o},{methodName:"TestRunUpdated",method:s},{methodName:"TestSuiteCreated",method:r},{methodName:"TestCreated",method:i},{methodName:"TestUpdated",method:a}];return new X(e,c)}};var je=class{constructor(e){this.config=e;this.connection=new se.HubConnectionBuilder().withUrl(`${e.masterUrl}/api/hubs/agent`,{headers:{"X-Container-Id":e.containerId,"X-Project-Key":e.projectKey}}).withAutomaticReconnect({nextRetryDelayInMilliseconds:t=>{let o=t.previousRetryCount+1,s=Math.min(1e3*Math.pow(2,t.previousRetryCount),6e4);return console.log(`[SignalR] Reconnect attempt ${o} (will retry in ${s}ms)`),s}}).withServerTimeout(3e5).withKeepAliveInterval(1e4).configureLogging(se.LogLevel.Information).build(),this.hubProxy=Mt("IAgentHub").createHubProxy(this.connection),this.connection.onreconnected(async()=>{console.log("[SignalR] Reconnected to gateway");try{await this.hubProxy.registerAgent(this.config.containerId),console.log(`[SignalR] Re-registered as agent for container: ${this.config.containerId}`),await this.reportStatus(this.currentStatus,void 0,this.currentSessionId??void 0,this.currentProjectId??void 0)}catch(t){console.error("[SignalR] Failed to re-register agent after reconnect:",t instanceof Error?t.message:t)}}),this.connection.onclose(t=>{console.error("[SignalR] Connection closed:",t?.message),process.exit(1)})}connection;hubProxy;receiverSubscription=null;currentStatus="WarmingUp";currentSessionId=null;currentProjectId=null;tunnelUrl=null;async connect(){console.log(`[SignalR] Connecting to gateway: ${this.config.masterUrl}/api/hubs/agent`),await this.connection.start(),console.log("[SignalR] Connected to gateway!"),await this.hubProxy.registerAgent(this.config.containerId),console.log(`[SignalR] Registered as agent for container: ${this.config.containerId}`)}registerReceiver(e){this.receiverSubscription&&this.receiverSubscription.dispose(),this.receiverSubscription=Ft("IAgentReceiver").register(this.connection,e)}getHubProxy(){return this.hubProxy}getMasterUrl(){return this.config.masterUrl}getProjectKey(){return this.config.projectKey}getConnection(){return this.connection}setTunnelUrl(e){this.tunnelUrl=e}async reportStatus(e,t,o,s){if(this.currentStatus=e,o&&(this.currentSessionId=o),s&&(this.currentProjectId=s),this.connection.state===se.HubConnectionState.Connected)try{let r={status:e,error:t,sessionId:o,projectId:s,tunnelUrl:this.tunnelUrl??void 0};await this.hubProxy.reportStatus(this.config.containerId,r),console.log(`[Status] ${e}${t?` (${t})`:""}${this.tunnelUrl?` [${this.tunnelUrl}]`:""}${o?` [session: ${o}]`:""}`)}catch(r){console.error("[SignalR] Failed to report status:",r)}}async getSecrets(){let e=await this.hubProxy.getSecrets();if(!e.success)throw new Error(`Failed to get secrets: ${e.error}`);return e.secrets||{}}};function Cn(n){let e=n.match(/name:\s*"([^"]+)"/);if(e)return e[1];let t=n.match(/^#\s+(.+)$/m);return t?t[1].trim():"Untitled Specification"}var Oe=class{constructor(e,t){this.hubProxy=e;this.projectId=t}async saveSpecification(e,t){let o=Cn(t),s=await this.hubProxy.saveSpecification(this.projectId,e,o,t);if(!s.success)throw new Error(s.error||"Failed to save specification")}async getSpecification(e){let t=await this.hubProxy.getSpecification(this.projectId,e);return t.success&&t.content||null}async listSpecifications(){let e=await this.hubProxy.listSpecifications(this.projectId);return!e.success||!e.specifications?[]:e.specifications.map(t=>({slug:t.slug,name:t.name,status:t.status,version:"1.0.0"}))}async deleteSpecification(e){return(await this.hubProxy.deleteSpecification(this.projectId,e)).success}async specificationExists(e){let t=await this.hubProxy.getSpecification(this.projectId,e);return t.success&&t.content!=null}};import{exec as _n,execSync as Pn,spawn as Rn}from"child_process";import{promisify as Tn}from"util";import{createHash as Lt}from"crypto";import*as k from"fs";import*as N from"path";var Ue=class{domain;accountId;tunnelId;apiToken;zoneId;tunnelApiBase;constructor(){this.domain=process.env.CLOUDFLARE_DEPLOY_DOMAIN||process.env.DEPLOY_DOMAIN||"vibecodementor.net",this.accountId=process.env.CLOUDFLARE_ACCOUNT_ID||"",this.tunnelId=process.env.CLOUDFLARE_TUNNEL_ID||"",this.apiToken=process.env.CLOUDFLARE_API_TOKEN||"",this.zoneId=process.env.CLOUDFLARE_ZONE_ID||"",this.tunnelApiBase=`https://api.cloudflare.com/client/v4/accounts/${this.accountId}/cfd_tunnel/${this.tunnelId}/configurations`}isConfigured(){return!!(this.accountId&&this.tunnelId&&this.apiToken&&this.zoneId)}buildHostname(e){let t=this.domain.split("."),o=t.length>2?t.slice(-2).join("."):this.domain;return`${e}.${o}`}async addRoute(e,t){let o=this.buildHostname(e);if(console.log(`[TunnelManager] Adding route: ${o} -> localhost:${t}`),!this.isConfigured())return console.log("[TunnelManager] Not configured, skipping route addition"),`https://${o}`;try{let s=await this.getConfig(),r=s.ingress.findIndex(a=>a.hostname===o);if(r!==-1)s.ingress[r].service=`http://localhost:${t}`;else{let a=s.ingress.findIndex(l=>!l.hostname),c={hostname:o,service:`http://localhost:${t}`};a!==-1?s.ingress.splice(a,0,c):(s.ingress.push(c),s.ingress.push({service:"http_status:404"}))}await this.putConfig(s),await this.ensureDnsPointsToTunnel(o);let i=`https://${o}`;return console.log(`[TunnelManager] \u2713 Route added: ${i}`),i}catch(s){throw console.error("[TunnelManager] Failed to add route:",s),s}}async removeRoute(e){let t=this.buildHostname(e);if(console.log(`[TunnelManager] Removing route: ${t}`),!this.isConfigured()){console.log("[TunnelManager] Not configured, skipping route removal");return}try{let o=await this.getConfig();o.ingress=o.ingress.filter(s=>s.hostname!==t),o.ingress.some(s=>!s.hostname)||o.ingress.push({service:"http_status:404"}),await this.putConfig(o),console.log(`[TunnelManager] \u2713 Route removed: ${t}`)}catch(o){throw console.error("[TunnelManager] Failed to remove route:",o),o}}async getConfig(){let t=await(await fetch(this.tunnelApiBase,{method:"GET",headers:{Authorization:`Bearer ${this.apiToken}`,"Content-Type":"application/json"}})).json();if(!t.success)throw new Error(`Cloudflare API error: ${t.errors.map(o=>o.message).join(", ")}`);return t.result?.config||{ingress:[{service:"http_status:404"}]}}async putConfig(e){let o=await(await fetch(this.tunnelApiBase,{method:"PUT",headers:{Authorization:`Bearer ${this.apiToken}`,"Content-Type":"application/json"},body:JSON.stringify({config:e})})).json();if(!o.success)throw new Error(`Cloudflare API error: ${o.errors.map(s=>s.message).join(", ")}`);console.log("[TunnelManager] Configuration updated via Cloudflare API")}async ensureDnsPointsToTunnel(e){let t=`${this.tunnelId}.cfargotunnel.com`,o=`https://api.cloudflare.com/client/v4/zones/${this.zoneId}/dns_records`;try{let r=await(await fetch(`${o}?name=${e}&type=CNAME`,{method:"GET",headers:{Authorization:`Bearer ${this.apiToken}`,"Content-Type":"application/json"}})).json();if(!r.success||!r.result?.length){console.log(`[TunnelManager] No DNS record found for ${e}, skipping DNS update`);return}let i=r.result[0];if(i.content===t){console.log(`[TunnelManager] DNS already points to correct tunnel: ${e}`);return}console.log(`[TunnelManager] Updating DNS: ${e} from ${i.content} to ${t}`);let c=await(await fetch(`${o}/${i.id}`,{method:"PATCH",headers:{Authorization:`Bearer ${this.apiToken}`,"Content-Type":"application/json"},body:JSON.stringify({type:"CNAME",name:e,content:t,proxied:!0})})).json();c.success?console.log(`[TunnelManager] \u2713 DNS updated: ${e} -> ${t}`):console.error(`[TunnelManager] Failed to update DNS: ${c.errors.map(l=>l.message).join(", ")}`)}catch(s){console.error(`[TunnelManager] Error updating DNS for ${e}:`,s)}}};var $t=Tn(_n),I=class{start;label;constructor(e){this.label=e,this.start=Date.now(),console.log(`[TIMING] \u23F1 START: ${e}`)}stop(){let e=Date.now()-this.start,t=(e/1e3).toFixed(2);return console.log(`[TIMING] \u2713 DONE: ${this.label} (${t}s)`),e}};function U(n,e={}){return new Promise((t,o)=>{let s=Rn(n,[],{shell:!0,stdio:"inherit",cwd:e.cwd}),r=null;e.timeout&&(r=setTimeout(()=>{s.kill(),o(new Error(`Command timed out: ${n}`))},e.timeout)),s.on("close",i=>{r&&clearTimeout(r),i===0?t():o(new Error(`Command failed with code ${i}: ${n}`))}),s.on("error",i=>{r&&clearTimeout(r),o(i)})})}var Ht="main",Ne=class{constructor(e,t,o,s){this.connection=e;this.serviceManager=t;this.workspaceDir=o;this.gitHubPat=s}currentBranchName=Ht;currentRepoUrl="";lastLockfileHash=null;tunnelManager=null;currentPreviewSubdomain=null;getTunnelManager(){return this.tunnelManager||(this.tunnelManager=new Ue),this.tunnelManager}async prewarmWorkspace(){let e=new I("TOTAL INIT"),t=process.env.REPO_URL,o=process.env.BRANCH_NAME||Ht,s=process.env.PREVIEW_SUBDOMAIN,r=process.env.PREVIEW_HOSTNAME,i=process.env.PROJECT_GITHUB_PAT;if(console.log("[Init] Starting single-phase init..."),console.log(`[Init] Repo: ${t?this.extractRepoPath(t):"NOT SET"}`),console.log(`[Init] Branch: ${o}`),console.log(`[Init] Preview: ${s||"none"}`),!t)throw console.error("[Init] \u274C REPO_URL not set - cannot initialize workspace"),new Error("REPO_URL environment variable is required");i&&(this.gitHubPat=i);let a=new I("Clean workspace");Pn("rm -rf /workspace/* /workspace/.* 2>/dev/null || true",{stdio:"inherit"}),a.stop();let c=new I("Clone repository");await this.cloneRepository(t,o),this.currentRepoUrl=t,this.currentBranchName=o,c.stop(),await this.installDependencies(),this.lastLockfileHash=this.getLockfileHash(),console.log("[Init] Starting services...");let l=new I("Start backend (dotnet)");await this.serviceManager.startBackend(),l.stop();let d=new I("Start frontend (vite)");await this.serviceManager.startFrontend(),d.stop();let u=new I("Wait for health check");await this.serviceManager.waitForHealth(3e4),u.stop(),s&&r&&await this.setupPreviewSubdomainFromEnv(s,r),e.stop(),console.log("[Init] \u2713 Workspace ready")}async handleInitSession(e){console.log(`[InitSession] Session ${e.sessionId} starting...`);let t=this.extractRepoPath(e.repoUrl),o=this.extractRepoPath(this.currentRepoUrl),s=t!==o,r=e.branchName!==this.currentBranchName;s||r?(console.log("[InitSession] Repo/branch mismatch - updating workspace..."),console.log(`[InitSession] Current: ${o}@${this.currentBranchName}`),console.log(`[InitSession] Requested: ${t}@${e.branchName}`),await this.connection.reportStatus("Ready",void 0,e.sessionId,e.projectId),await this.connection.getHubProxy().initSessionCompleted(process.env.CONTAINER_ID||"unknown",e.sessionId,!0,""),this.handleRepoChange(e).catch(async a=>{let c=a instanceof Error?a.message:"Unknown error";console.error("[InitSession] Background update failed:",c),await this.connection.reportStatus("Error",c,e.sessionId,e.projectId)})):(console.log("[InitSession] Workspace already matches - ready immediately"),await this.connection.reportStatus("Ready",void 0,e.sessionId,e.projectId),await this.connection.getHubProxy().initSessionCompleted(process.env.CONTAINER_ID||"unknown",e.sessionId,!0,"")),console.log(`[InitSession] \u2713 Ready for session ${e.sessionId}`)}async setupPreviewSubdomainFromEnv(e,t){let o=new I("Setup preview subdomain");console.log(`[Preview] Setting up preview subdomain: ${e} (${t})`);let s=this.getTunnelManager();if(!s){console.log("[Preview] TunnelManager not available, skipping subdomain setup"),o.stop();return}if(!s.isConfigured()){console.log("[Preview] Cloudflare not configured, skipping subdomain setup"),o.stop();return}try{let r=parseInt(process.env.ALLOCATED_HOST_PORT||"5173"),i=await s.addRoute(e,r);this.currentPreviewSubdomain=e,this.connection.setTunnelUrl(i),o.stop(),console.log(`[Preview] \u2713 Preview subdomain configured: ${i}`)}catch(r){o.stop(),console.error("[Preview] Failed to set up subdomain route:",r)}}async handleRepoChange(e){let t=new I("TOTAL REPO CHANGE");e.gitHubPat&&e.gitHubPat!==this.gitHubPat&&(console.log("[RepoChange] Updating git credentials"),this.gitHubPat=e.gitHubPat,await U(`echo "https://${e.gitHubPat}@github.com" > ~/.git-credentials`));let o=this.extractRepoPath(e.repoUrl),s=this.extractRepoPath(this.currentRepoUrl);if(o!==s){let i=new I("Clone new repository");await this.cloneRepository(e.repoUrl,e.branchName),this.currentRepoUrl=e.repoUrl,this.currentBranchName=e.branchName,i.stop(),await this.installDependencies();let a=new I("Restart services");await this.serviceManager.restartServices("both"),a.stop()}else{let i=new I(`Switch branch to ${e.branchName}`);await this.switchBranch(e.branchName),i.stop(),await this.checkNeedsReinstall()&&await this.installDependencies();let c=new I("Restart services");await this.serviceManager.restartServices("both"),c.stop()}e.previewSubdomain&&e.previewHostname&&await this.setupPreviewSubdomainFromEnv(e.previewSubdomain,e.previewHostname),t.stop(),console.log("[RepoChange] \u2713 Workspace updated")}extractRepoPath(e){let t=e.match(/github\.com[:/]([^/]+\/[^/]+?)(?:\.git)?$/);return t?t[1]:e}async cloneRepository(e,t){console.log(`[Clone] Clearing workspace and cloning ${this.extractRepoPath(e)} @ ${t}`),await U("rm -rf /workspace/* /workspace/.* 2>/dev/null || true"),await U(`git clone -b ${t} "${e}" /workspace`,{timeout:12e4}),await U('cd /workspace && git config user.email "agent@dotnetmentor.se" && git config user.name "Agent"'),console.log("[Clone] \u2713 Repository cloned")}async switchBranch(e){if(console.log(`[Branch] Switching from ${this.currentBranchName} to ${e}`),e===this.currentBranchName)console.log(`[Branch] Already on ${e}, pulling latest...`),await U(`git pull origin ${e}`,{cwd:this.workspaceDir,timeout:6e4});else{await U("git fetch origin",{cwd:this.workspaceDir,timeout:6e4});let{stdout:t}=await $t(`git ls-remote --heads origin ${e}`,{cwd:this.workspaceDir,timeout:3e4});t.trim().length>0?await U(`git checkout -B ${e} origin/${e}`,{cwd:this.workspaceDir,timeout:3e4}):(console.log(`[Branch] Branch ${e} doesn't exist on remote, creating from main...`),await U(`git checkout -b ${e}`,{cwd:this.workspaceDir,timeout:3e4})),this.currentBranchName=e}console.log(`[Branch] \u2713 Now on ${e}`)}getLockfileHash(){let e=N.join(this.workspaceDir,"packages/backoffice-web/package-lock.json");return k.existsSync(e)?Lt("sha256").update(k.readFileSync(e)).digest("hex"):null}async checkNeedsReinstall(){let e=this.getLockfileHash();return!e||!this.lastLockfileHash?!0:e!==this.lastLockfileHash}async checkCodeDiff(e,t){if(e===t)return!1;try{let{stdout:o}=await $t(`git diff ${e}..HEAD --name-only`,{cwd:this.workspaceDir,timeout:3e4}),s=o.trim();if(!s)return console.log(`[CodeDiff] No file differences between ${e} and ${t}`),!1;let r=s.split(`
|
|
1344
|
+
`).filter(c=>c.length>0);console.log(`[CodeDiff] ${r.length} files changed between ${e} and ${t}`);let i=[".cs",".tsx",".ts",".json",".csproj"],a=r.some(c=>i.some(l=>c.endsWith(l)));return a&&console.log("[CodeDiff] Found code changes that require service restart"),a}catch(o){return console.error("[CodeDiff] Failed to check diff:",o instanceof Error?o.message:o),!0}}async installDependencies(){let e=new I("TOTAL INSTALL DEPENDENCIES");if(k.existsSync(N.join(this.workspaceDir,"packages/dotnet-api"))){let t=new I("dotnet restore");await U("dotnet restore",{cwd:N.join(this.workspaceDir,"packages/dotnet-api"),timeout:12e4}),t.stop()}if(k.existsSync(N.join(this.workspaceDir,"packages/backoffice-web/package.json"))){let t=N.join(this.workspaceDir,"packages/backoffice-web"),o=N.join(t,"node_modules"),s=N.join(t,"package-lock.json"),r=N.join(o,".lockhash"),i="/opt/cache";k.mkdirSync(i,{recursive:!0});let a=k.existsSync(s)?Lt("sha256").update(k.readFileSync(s)).digest("hex"):null,c=k.existsSync(r)?k.readFileSync(r,"utf-8").trim():null,l=N.join(i,"node_modules","current_hash"),d=k.existsSync(l)?k.readFileSync(l,"utf-8").trim():null,u=d?N.join(i,"node_modules",d):null,g=a?N.join(i,"node_modules",a):null;if(console.log(`[Install] node_modules exists: ${k.existsSync(o)}`),console.log(`[Install] lockHash (current branch): ${a?.substring(0,8)||"null"}`),console.log(`[Install] installedHash: ${c?.substring(0,8)||"null"}`),console.log(`[Install] dockerCacheHash (from image): ${d?.substring(0,8)||"null"}`),console.log(`[Install] exactMatchCache exists: ${g?k.existsSync(g):!1}`),console.log(`[Install] dockerCachedNodeModules exists: ${u?k.existsSync(u):!1}`),!k.existsSync(o)){if(g&&k.existsSync(g)){let v=new I("Copy exact-match cached node_modules");console.log(`[Install] Copying exact-match cached node_modules (hash ${a?.substring(0,8)})...`),await U(`cp -r "${g}" "${o}"`),a&&k.writeFileSync(r,a),c=a,v.stop()}else if(u&&k.existsSync(u)){let v=new I("Copy Docker-cached node_modules (as base)");console.log(`[Install] Copying Docker-cached node_modules as base (hash ${d?.substring(0,8)}, need ${a?.substring(0,8)})...`),await U(`cp -r "${u}" "${o}"`),v.stop()}}let w=!k.existsSync(o)||a&&a!==c;if(console.log(`[Install] needsInstall: ${w}`),!w)console.log("[Install] npm install skipped (cache OK)");else{let v=new I("npm install --prefer-offline");await U("npm install --prefer-offline --no-progress --fund=false",{cwd:t,timeout:18e4}),v.stop(),a&&(k.mkdirSync(o,{recursive:!0}),k.writeFileSync(r,a))}this.lastLockfileHash=a}e.stop()}};function Bt(n,e,t){let o=n.getHubProxy(),s=process.env.CONTAINER_ID||"unknown";return{onAssistantText:async r=>{let i={type:"Text",content:r,isError:!1};await o.reportSessionOutput(s,e,i).catch(a=>{console.error("Failed to report assistant text:",a)})},onAssistantToolUse:async(r,i)=>{if(["Write","Edit","MultiEdit"].includes(r)){let c=i,l=c.file_path||c.target_file;l&&t.onFileModified(l)}let a={type:"ToolUse",toolName:r,toolInput:JSON.stringify(i),isError:!1};await o.reportSessionOutput(s,e,a).catch(c=>{console.error("Failed to report tool use:",c)})},onResult:async(r,i)=>{let a={type:"System",event:"completed",content:r,cost:i,isError:!1};await o.reportSessionOutput(s,e,a).catch(c=>{console.error("Failed to report result:",c)})},onError:async r=>{let i={type:"System",event:"error",content:r,isError:!0};await o.reportSessionOutput(s,e,i).catch(a=>{console.error("Failed to report error:",a)})},onAborted:async()=>{let r={type:"System",event:"aborted",content:"Agent stopped by user",isError:!1};await o.reportSessionOutput(s,e,r).catch(i=>{console.error("Failed to report abort:",i)})},onSystemInit:async r=>{console.log(`[Callbacks] Claude session initialized: ${r}`),t.onClaudeSessionId(r),await o.setClaudeSessionId(e,r).catch(i=>{console.error("Failed to set Claude session ID:",i)})},onRawMessage:r=>{if(r.type==="assistant"&&r.message?.usage){let i=r.message.usage,a={totalCostUsd:In(i),inputTokens:i.input_tokens||0,outputTokens:i.output_tokens||0,cacheCreationTokens:i.cache_creation_input_tokens||0,cacheReadTokens:i.cache_read_input_tokens||0};o.reportSessionCost(s,e,a).catch(c=>{console.error("Failed to report cost:",c)})}}}}var Me={inputPerMillion:3,outputPerMillion:15,cacheReadPerMillion:.3,cacheCreationPerMillion:3.75};function In(n){return(n.input_tokens||0)/1e6*Me.inputPerMillion+(n.output_tokens||0)/1e6*Me.outputPerMillion+(n.cache_read_input_tokens||0)/1e6*Me.cacheReadPerMillion+(n.cache_creation_input_tokens||0)/1e6*Me.cacheCreationPerMillion}import*as re from"fs";import*as qt from"path";function Gt(n,e){let t=qt.join(n,`.agent-questions-${e}.json`);if(!re.existsSync(t))return null;try{let o=re.readFileSync(t,"utf-8");return re.unlinkSync(t),JSON.parse(o)}catch(o){return console.error("Failed to read questions file:",o),null}}async function Kt(n,e,t,o){if(e.size===0)return console.log("[TypeCheck] No code changes, skipping type checks"),{passed:!0};let s=o?H(o):`${t}/packages/dotnet-api`,r=o?B(o):`${t}/packages/backoffice-web`,i=o?.services?.backend?.extensions??[".cs",".csproj"],a=o?.services?.frontend?.extensions??[".ts",".tsx"],c=s&&[...e].some(g=>g.includes(s)||i.some(w=>g.endsWith(w))),l=r&&[...e].some(g=>g.includes(r)||a.some(w=>g.endsWith(w)));if(!c&&!l)return console.log("[TypeCheck] No code changes, skipping type checks"),{passed:!0};let d=[];c&&d.push("backend"),l&&d.push("frontend"),console.log(`[TypeCheck] Checking ${d.join(" & ")}...`);let u=[];if(c){let g=await n.checkBackendBuild();!g.success&&g.errors&&u.push(`## Backend Build Errors (in ${s})
|
|
1268
1345
|
\`\`\`
|
|
1269
1346
|
${g.errors}
|
|
1270
1347
|
\`\`\``)}if(l){let g=await n.checkFrontendTypes();!g.success&&g.errors&&u.push(`## Frontend Type Errors (in ${r})
|
|
@@ -1278,22 +1355,22 @@ ${u.join(`
|
|
|
1278
1355
|
|
|
1279
1356
|
`)}
|
|
1280
1357
|
|
|
1281
|
-
File paths like "src/..." are relative to the package directory shown in parentheses.`}):(console.log("[TypeCheck] \u2713 All type checks passed"),{passed:!0})}async function ut(n,e,t,o,s){if(!await n.hasChanges()){console.log("[Git] No changes to commit");return}console.log("[Git] Committing changes...");let i=await n.commitAndPush(o,s);if(i.noChanges)console.log("[Git] No changes to commit");else if(i.success){let a=await n.getCurrentBranch();console.log(`[Git] \u2713 Committed ${i.commitHash||""}`),console.log(`[Git] \u2192 Pushed to ${a}`),await e.getHubProxy().reportSessionOutput(process.env.CONTAINER_ID||"unknown",t,{type:"System",event:"committed",content:`Changes committed to ${a}`,isError:!1}).catch(l=>{console.error("[Git] Failed to report commit:",l)})}else console.error(`[Git] \u274C Git error: ${i.error}`)}var Fe=class{constructor(e,t,o,s,r=!0,i=!0,a,c,l=!0){this.connection=e;this.lifecycle=t;this.serviceManager=o;this.workspaceDir=s;this.enableAutoTypecheck=r;this.enableAutoCommit=i;this.setupPlanningTransport=a;this.projectConfig=c;this.useDefaultStack=l;this.gitManager=De(s)}currentSession=null;claudeSessionIdMap=new Map;currentAbortController=null;gitManager;async initSession(e){console.log(`[Receiver] InitSession for session ${e.sessionId}`),this.setupPlanningTransport&&e.projectId&&this.setupPlanningTransport(e.projectId),e.projectId&&await this.fetchProjectPrompt(e.projectId),this.currentSession={sessionId:e.sessionId,projectId:e.projectId,branchName:e.branchName,model:e.model==="auto"?void 0:e.model||"claude-haiku-4-5-20251001",debugMode:e.debugMode},await this.lifecycle.handleInitSession(e),e.prompt&&await this.runPrompt(e.sessionId,e.prompt,e.claudeSessionId)}async runPrompt(e,t,o,s=!1){let r=pe(t);if(console.log(`[Receiver] RunPrompt for session ${e}${o?` (ClaudeSessionId: ${o})`:""}${s?" [DEBUG MODE]":""}: ${r.substring(0,50)}...`),(!this.currentSession||this.currentSession.sessionId!==e)&&(console.log(`[Receiver] Session mismatch: expected ${this.currentSession?.sessionId}, got ${e}, switching...`),await this.switchSession(e),!this.currentSession)){console.error("[Receiver] Failed to create session context");return}let i=this.claudeSessionIdMap.get(e);if(o&&i!==o){console.warn(`[Receiver] Claude session not found in container: ${o}`);let l=this.connection.getHubProxy();await l.reportSessionOutput(process.env.CONTAINER_ID||"unknown",e,{type:"System",event:"sessionNotFound",content:o,isError:!0}),await this.connection.reportStatus("Ready","Session lost - summary needed",e,this.currentSession.projectId),await l.sessionCompleted(process.env.CONTAINER_ID||"unknown",e,0);return}await this.connection.reportStatus("Active",void 0,e,this.currentSession.projectId),this.currentSession.debugMode=s,this.currentAbortController=new AbortController;let a=i||null,c=new Set;try{let l=await Ae(r,{model:this.currentSession.model,sessionId:a||null,projectId:this.currentSession.projectId||null,apiUrl:this.connection.getMasterUrl(),debugLog:!0,debugMode:s,abortController:this.currentAbortController,projectConfig:this.projectConfig,useDefaultStack:this.useDefaultStack,callbacks:Bt(this.connection,e,{onClaudeSessionId:g=>{this.claudeSessionIdMap.set(e,g)},onFileModified:g=>{c.add(g)}})}),d=
|
|
1358
|
+
File paths like "src/..." are relative to the package directory shown in parentheses.`}):(console.log("[TypeCheck] \u2713 All type checks passed"),{passed:!0})}async function ut(n,e,t,o,s){if(!await n.hasChanges()){console.log("[Git] No changes to commit");return}console.log("[Git] Committing changes...");let i=await n.commitAndPush(o,s);if(i.noChanges)console.log("[Git] No changes to commit");else if(i.success){let a=await n.getCurrentBranch();console.log(`[Git] \u2713 Committed ${i.commitHash||""}`),console.log(`[Git] \u2192 Pushed to ${a}`),await e.getHubProxy().reportSessionOutput(process.env.CONTAINER_ID||"unknown",t,{type:"System",event:"committed",content:`Changes committed to ${a}`,isError:!1}).catch(l=>{console.error("[Git] Failed to report commit:",l)})}else console.error(`[Git] \u274C Git error: ${i.error}`)}var Fe=class{constructor(e,t,o,s,r=!0,i=!0,a,c,l=!0){this.connection=e;this.lifecycle=t;this.serviceManager=o;this.workspaceDir=s;this.enableAutoTypecheck=r;this.enableAutoCommit=i;this.setupPlanningTransport=a;this.projectConfig=c;this.useDefaultStack=l;this.gitManager=De(s)}currentSession=null;claudeSessionIdMap=new Map;currentAbortController=null;gitManager;async initSession(e){console.log(`[Receiver] InitSession for session ${e.sessionId}`),this.setupPlanningTransport&&e.projectId&&this.setupPlanningTransport(e.projectId),e.projectId&&await this.fetchProjectPrompt(e.projectId),this.currentSession={sessionId:e.sessionId,projectId:e.projectId,branchName:e.branchName,model:e.model==="auto"?void 0:e.model||"claude-haiku-4-5-20251001",debugMode:e.debugMode},await this.lifecycle.handleInitSession(e),e.prompt&&await this.runPrompt(e.sessionId,e.prompt,e.claudeSessionId)}async runPrompt(e,t,o,s=!1){let r=pe(t);if(console.log(`[Receiver] RunPrompt for session ${e}${o?` (ClaudeSessionId: ${o})`:""}${s?" [DEBUG MODE]":""}: ${r.substring(0,50)}...`),(!this.currentSession||this.currentSession.sessionId!==e)&&(console.log(`[Receiver] Session mismatch: expected ${this.currentSession?.sessionId}, got ${e}, switching...`),await this.switchSession(e),!this.currentSession)){console.error("[Receiver] Failed to create session context");return}let i=this.claudeSessionIdMap.get(e);if(o&&i!==o){console.warn(`[Receiver] Claude session not found in container: ${o}`);let l=this.connection.getHubProxy();await l.reportSessionOutput(process.env.CONTAINER_ID||"unknown",e,{type:"System",event:"sessionNotFound",content:o,isError:!0}),await this.connection.reportStatus("Ready","Session lost - summary needed",e,this.currentSession.projectId),await l.sessionCompleted(process.env.CONTAINER_ID||"unknown",e,0);return}await this.connection.reportStatus("Active",void 0,e,this.currentSession.projectId),this.currentSession.debugMode=s,this.currentAbortController=new AbortController;let a=i||null,c=new Set;try{let l=await Ae(r,{model:this.currentSession.model,sessionId:a||null,projectId:this.currentSession.projectId||null,apiUrl:this.connection.getMasterUrl(),debugLog:!0,debugMode:s,abortController:this.currentAbortController,projectConfig:this.projectConfig,useDefaultStack:this.useDefaultStack,callbacks:Bt(this.connection,e,{onClaudeSessionId:g=>{this.claudeSessionIdMap.set(e,g)},onFileModified:g=>{c.add(g)}})}),d=Gt(this.workspaceDir,e);if(d&&d.length>0){console.log(`[Receiver] ${d.length} pending questions found, waiting for user answers`),await this.connection.reportStatus("Ready",void 0,e,this.currentSession.projectId),await this.connection.getHubProxy().sessionCompleted(process.env.CONTAINER_ID||"unknown",e,0);return}if(this.enableAutoTypecheck&&!l.error&&!l.aborted){let g=await Kt(this.serviceManager,c,this.workspaceDir,this.projectConfig);if(!g.passed&&g.errorPrompt){console.log("[Receiver] Type errors detected, auto-correcting..."),await this.runPrompt(e,g.errorPrompt,o);return}}this.enableAutoCommit&&!l.error&&!l.aborted?await ut(this.gitManager,this.connection,e,t,!1):(l.error||l.aborted)&&this.enableAutoCommit&&await ut(this.gitManager,this.connection,e,t,!0),await this.connection.reportStatus("Ready",void 0,e,this.currentSession.projectId),await this.connection.getHubProxy().sessionCompleted(process.env.CONTAINER_ID||"unknown",e,l.error?1:0)}catch(l){let d=l instanceof Error&&l.name==="AbortError",u=l instanceof Error?l.message:"Unknown error";d?console.log(`[Receiver] Agent aborted for session ${e}`):(console.error("[Receiver] Agent error:",u),await this.connection.reportStatus("Error",u,e,this.currentSession.projectId)),await this.connection.getHubProxy().sessionCompleted(process.env.CONTAINER_ID||"unknown",e,d?0:1)}finally{this.currentAbortController=null}}async setModel(e){console.log(`[Receiver] Model changed to: ${e}`),this.currentSession&&(this.currentSession.model=e==="auto"?void 0:e)}async stopAgent(){console.log("[Receiver] Stop command received"),this.currentAbortController&&this.currentAbortController.abort()}async ping(){await this.connection.getConnection().invoke("Pong",process.env.CONTAINER_ID||"unknown")}async switchSession(e){console.log(`[Receiver] SwitchSession to ${e}`),this.currentSession?this.currentSession.sessionId=e:(console.log(`[Receiver] Creating new session context for ${e}`),this.currentSession={sessionId:e,projectId:process.env.PROJECT_ID||"unknown",branchName:"main",model:void 0,debugMode:!1})}async projectPromptUpdated(e){console.log(`[Receiver] ProjectPromptUpdated received (${e.length} chars)`),We(e)}async fetchProjectPrompt(e){try{let t=this.connection.getMasterUrl(),o=this.connection.getProjectKey(),s=await fetch(`${t}/api/projects/${e}/prompt`,{method:"GET",headers:{"Content-Type":"application/json","X-Project-Key":o}});if(!s.ok){console.log(`[Receiver] Failed to fetch project prompt: ${s.status}`);return}let r=await s.json();r.prompt?(We(r.prompt),console.log(`[Receiver] Initial project prompt loaded (${r.prompt.length} chars)`)):console.log(`[Receiver] No project prompt set for project ${e}`)}catch(t){console.error("[Receiver] Error fetching project prompt:",t)}}};process.env.CLAUDE_CODE_STREAM_CLOSE_TIMEOUT=process.env.CLAUDE_CODE_STREAM_CLOSE_TIMEOUT||"60000";var Wt=Y.dirname(En(import.meta.url));function An(){let n=[Y.resolve(Wt,"../package.json"),Y.resolve(Wt,"../../../package.json")];for(let e of n)try{if(M.existsSync(e)){let t=JSON.parse(M.readFileSync(e,"utf-8"));if(t.name==="glenn-code")return t.version||"unknown"}}catch{}return"unknown"}function xn(n){let{useDefaultStack:e,projectConfig:t,configSource:o,tunnelPort:s,enableAutoTypecheck:r,enableAutoCommit:i}=n;if(console.log(`
|
|
1282
1359
|
\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557`),console.log("\u2551 AGENT CONFIGURATION \u2551"),console.log("\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"),e?(console.log("\u2551 Mode: DEFAULT STACK (.NET/React) \u2551"),console.log("\u2551 Agents: scaffolding, backend, frontend, debugger, \u2551"),console.log("\u2551 planning, project-context \u2551")):(console.log("\u2551 Mode: CUSTOM STACK (generic agents only) \u2551"),console.log("\u2551 Agents: debugger, planning, project-context \u2551")),console.log("\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"),console.log(`\u2551 Config source: ${o.padEnd(42)}\u2551`),console.log("\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"),console.log("\u2551 SERVICES: \u2551"),t.services?.backend){let a=t.services.backend;console.log(`\u2551 Backend: port ${a.port}, extensions: ${a.extensions?.join(", ")||"N/A"}`.padEnd(62)+"\u2551")}else console.log("\u2551 Backend: not configured \u2551");if(t.services?.frontend){let a=t.services.frontend;console.log(`\u2551 Frontend: port ${a.port}, extensions: ${a.extensions?.join(", ")||"N/A"}`.padEnd(62)+"\u2551")}else console.log("\u2551 Frontend: not configured \u2551");t.techStack&&(console.log("\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"),console.log("\u2551 TECH STACK: \u2551"),t.techStack.backend?.length&&console.log(`\u2551 Backend: ${t.techStack.backend.join(", ")}`.padEnd(62)+"\u2551"),t.techStack.frontend?.length&&console.log(`\u2551 Frontend: ${t.techStack.frontend.join(", ")}`.padEnd(62)+"\u2551"),t.techStack.patterns?.length&&console.log(`\u2551 Patterns: ${t.techStack.patterns.join(", ")}`.padEnd(62)+"\u2551")),console.log("\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"),console.log("\u2551 SETTINGS: \u2551"),console.log(`\u2551 Tunnel port: ${s}`.padEnd(62)+"\u2551"),console.log(`\u2551 Auto-typecheck: ${r?"enabled":"disabled"}`.padEnd(62)+"\u2551"),console.log(`\u2551 Auto-commit: ${i?"enabled":"disabled"}`.padEnd(62)+"\u2551"),console.log(`\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
1283
|
-
`)}var Dn=".sdd/local-signalr-config.json";function gt(n){return Y.join(n,Dn)}function jn(n){try{let e=gt(n);if(!
|
|
1360
|
+
`)}var Dn=".sdd/local-signalr-config.json";function gt(n){return Y.join(n,Dn)}function jn(n){try{let e=gt(n);if(!M.existsSync(e))return null;let t=M.readFileSync(e,"utf-8");return JSON.parse(t)}catch{return null}}function dt(n,e){let t=Y.dirname(gt(n));M.existsSync(t)||M.mkdirSync(t,{recursive:!0}),M.writeFileSync(gt(n),JSON.stringify(e,null,2))}function Le(){return`local-${Qt.hostname().toLowerCase().replace(/[^a-z0-9]/g,"-")}-${Date.now().toString(36)}`}function On(n){return!!(n.version||n.paths||n.services||n.techStack)}function Un(n,e){return On(n)?{version:n.version||"1.0",paths:n.paths||{workspace:e},services:n.services,techStack:n.techStack,tunnel:n.tunnel,scaffold:n.scaffold,git:n.git}:null}async function Nn(n){console.log(`
|
|
1284
1361
|
\u{1F527} Interactive Project Config Builder
|
|
1285
1362
|
`),console.log(" Answer the following questions to configure your project."),console.log(` Press Enter to skip optional questions.
|
|
1286
|
-
`);let{projectType:e}=await
|
|
1287
|
-
\u{1F4F1} Frontend Configuration:`);let s=await
|
|
1288
|
-
\u2699\uFE0F Backend Configuration:`);let s=await
|
|
1289
|
-
\u{1F4DA} Tech Stack (optional - helps AI understand your project):`);let{addTechStack:o}=await
|
|
1363
|
+
`);let{projectType:e}=await q.prompt([{type:"select",name:"projectType",message:"What type of project is this?",choices:[{name:"Frontend only (React, Next.js, Vue, etc.)",value:"frontend"},{name:"Backend only (Node, Python, Go, etc.)",value:"backend"},{name:"Fullstack (Frontend + Backend)",value:"fullstack"},{name:"Skip - use default .NET/React stack",value:"skip"}]}]);if(e==="skip")return null;let t={version:"1.0",paths:{workspace:"."}};if(e==="frontend"||e==="fullstack"){console.log(`
|
|
1364
|
+
\u{1F4F1} Frontend Configuration:`);let s=await q.prompt([{type:"input",name:"path",message:"Frontend path (relative to workspace):",default:e==="fullstack"?"./frontend":"."},{type:"number",name:"port",message:"Dev server port:",default:3e3},{type:"input",name:"startCommand",message:"Start command (or Enter to skip):",default:"npm run dev"},{type:"input",name:"typecheckCommand",message:"Typecheck command (or Enter to skip):",default:"npx tsc --noEmit"},{type:"input",name:"extensions",message:"File extensions (comma-separated):",default:".ts,.tsx,.js,.jsx"}]);t.paths={workspace:t.paths?.workspace||".",...t.paths,frontend:s.path||"."},t.services={...t.services,frontend:{port:s.port||3e3,startCommand:s.startCommand||"npm run dev",typecheckCommand:s.typecheckCommand||void 0,extensions:s.extensions?s.extensions.split(",").map(r=>r.trim()):[".ts",".tsx",".js",".jsx"]}},t.tunnel={enabled:!0,port:s.port||3e3}}if(e==="backend"||e==="fullstack"){console.log(`
|
|
1365
|
+
\u2699\uFE0F Backend Configuration:`);let s=await q.prompt([{type:"input",name:"path",message:"Backend path (relative to workspace):",default:e==="fullstack"?"./backend":"."},{type:"number",name:"port",message:"API server port:",default:8080},{type:"input",name:"startCommand",message:"Start command (or Enter to skip):",default:""},{type:"input",name:"buildCommand",message:"Build command (or Enter to skip):",default:""},{type:"input",name:"extensions",message:"File extensions (comma-separated):",default:".ts,.js"}]);t.paths={workspace:t.paths?.workspace||".",...t.paths,backend:s.path||"."},t.services={...t.services,backend:{port:s.port||8080,startCommand:s.startCommand||void 0,buildCommand:s.buildCommand||void 0,extensions:s.extensions?s.extensions.split(",").map(r=>r.trim()):[".ts",".js"]}},e==="backend"&&(t.tunnel={enabled:!0,port:s.port||8080})}console.log(`
|
|
1366
|
+
\u{1F4DA} Tech Stack (optional - helps AI understand your project):`);let{addTechStack:o}=await q.prompt([{type:"confirm",name:"addTechStack",message:"Add tech stack info?",default:!1}]);if(o){let s=await q.prompt([{type:"input",name:"frontend",message:"Frontend technologies (comma-separated, or Enter to skip):",default:""},{type:"input",name:"backend",message:"Backend technologies (comma-separated, or Enter to skip):",default:""},{type:"input",name:"patterns",message:"Patterns/practices (comma-separated, or Enter to skip):",default:""}]);t.techStack={},s.frontend&&(t.techStack.frontend=s.frontend.split(",").map(r=>r.trim())),s.backend&&(t.techStack.backend=s.backend.split(",").map(r=>r.trim())),s.patterns&&(t.techStack.patterns=s.patterns.split(",").map(r=>r.trim()))}return t}function Yt(n){try{let e={},t=n.match(/MASTER_URL=(\S+)/);t&&(e.masterUrl=t[1].trim());let o=n.match(/PROJECT_ID=(\S+)/);o&&(e.projectId=o[1].trim());let s=n.match(/PROJECT_KEY=(\S+)/);return s&&(e.projectKey=s[1].trim()),e.masterUrl&&e.projectId&&e.projectKey?e:null}catch{return null}}async function Mn(n){let e=jn(n);console.log(`
|
|
1290
1367
|
\u{1F3E0} Local SignalR Mode - Jack into the real system
|
|
1291
|
-
`);let t=e?.masterUrl&&e?.projectId&&e?.projectKey,{configMethod:o}=await
|
|
1292
|
-
\u{1F4CB} Using saved configuration:`),console.log(` Master URL: ${e.masterUrl}`),console.log(` Project ID: ${e.projectId}`),console.log(` Project Key: ${e.projectKey.substring(0,16)}...`),console.log(` Container ID: ${e.containerId}`),console.log(` Tunnel: ${e.enableTunnel!==!1?`enabled (port ${c})`:"disabled"}`),a&&console.log(" Project config: embedded in local-signalr-config.json"),{masterUrl:e.masterUrl,projectId:e.projectId,projectKey:e.projectKey,containerId:e.containerId||Le(),enableTunnel:e.enableTunnel!==!1,tunnelPort:c,embeddedProjectConfig:a}}if(o==="paste"){let{pastedConfig:a}=await
|
|
1368
|
+
`);let t=e?.masterUrl&&e?.projectId&&e?.projectKey,{configMethod:o}=await q.prompt([{type:"select",name:"configMethod",message:"How would you like to configure?",choices:[...t?[{name:`Use saved config (${e.projectId})`,value:"saved"}]:[],{name:"Paste config from frontend",value:"paste"},{name:"Enter manually",value:"manual"},{name:"Build project config interactively",value:"interactive"}]}]);if(o==="saved"&&t){let a=Un(e,n),c=a?.tunnel?.port||e.tunnelPort||5173;return console.log(`
|
|
1369
|
+
\u{1F4CB} Using saved configuration:`),console.log(` Master URL: ${e.masterUrl}`),console.log(` Project ID: ${e.projectId}`),console.log(` Project Key: ${e.projectKey.substring(0,16)}...`),console.log(` Container ID: ${e.containerId}`),console.log(` Tunnel: ${e.enableTunnel!==!1?`enabled (port ${c})`:"disabled"}`),a&&console.log(" Project config: embedded in local-signalr-config.json"),{masterUrl:e.masterUrl,projectId:e.projectId,projectKey:e.projectKey,containerId:e.containerId||Le(),enableTunnel:e.enableTunnel!==!1,tunnelPort:c,embeddedProjectConfig:a}}if(o==="paste"){let{pastedConfig:a}=await q.prompt([{type:"input",name:"pastedConfig",message:"Paste the config (MASTER_URL=... PROJECT_ID=... PROJECT_KEY=...):",validate:g=>Yt(g)?!0:"Could not parse config. Make sure it contains MASTER_URL=..., PROJECT_ID=..., and PROJECT_KEY=..."}]),c=Yt(a),l=e?.containerId||Le(),d=e?.tunnelPort||5173,u={masterUrl:c.masterUrl,projectId:c.projectId,projectKey:c.projectKey,containerId:l,enableTunnel:!0,tunnelPort:d};return console.log(`
|
|
1293
1370
|
\u{1F4CB} Parsed configuration:`),console.log(` Master URL: ${u.masterUrl}`),console.log(` Project ID: ${u.projectId}`),console.log(` Project Key: ${u.projectKey.substring(0,16)}...`),console.log(` Container ID: ${u.containerId}`),console.log(` Tunnel: enabled (port ${d})`),dt(n,u),{...u,embeddedProjectConfig:null}}if(o==="interactive"){console.log(`
|
|
1294
|
-
\u{1F50C} Connection Configuration:`);let a=await
|
|
1295
|
-
\u{1F4CB} Configuration built:`),console.log(` Master URL: ${u.masterUrl}`),console.log(` Project ID: ${u.projectId}`),console.log(` Project Key: ${u.projectKey.substring(0,16)}...`),console.log(` Container ID: ${u.containerId}`),console.log(` Tunnel: enabled (port ${d})`),console.log(c?" Project config: custom stack":" Project config: default .NET/React stack"),dt(n,u),{masterUrl:u.masterUrl,projectId:u.projectId,projectKey:u.projectKey,containerId:u.containerId,enableTunnel:u.enableTunnel,tunnelPort:u.tunnelPort,embeddedProjectConfig:c}}let s=await
|
|
1296
|
-
\u{1F50C} Connecting to backend...`),console.log(` Container ID: ${o.containerId}`),console.log(` Project Key: ${o.projectKey.substring(0,16)}...`),console.log(` Gateway URL: ${o.masterUrl}`);let c=new je(o),l=0,d=10;for(;l<d;)try{await c.connect();break}catch(p){if(l++,console.error(`Connection attempt ${l} failed:`,p instanceof Error?p.message:p),l<d){let C=Math.min(1e3*Math.pow(2,l),3e4);await new Promise(de=>setTimeout(de,C))}}l>=d&&(console.error("\u274C Failed to connect to gateway after max retries"),process.exit(1)),n&&console.log("\u2705 Connected to backend");let u=await c.getSecrets();n?(console.log("\u2705 Handshake complete"),u.gitHubPat&&console.log(" GitHub PAT received from backend")):console.log("\u2705 Secrets received");let g,w=!0,v="default";if(n){if(a)g=a,v="embedded in local-signalr-config.json",w=!1,console.log("[Config] Using embedded project config from local-signalr-config.json");else{let p=await _e({workspaceDir:o.workspaceDir});g=p.config,v=p.source==="file"?p.configPath||".sdd/project-config.json":"default (no config file)",p.source==="file"&&(w=!1)}w||g.tunnel?.port&&(i=g.tunnel.port)}let b=xe({workspaceDir:o.workspaceDir,projectConfig:g});he(b);let D=ce(o.workspaceDir);ie(D);let j=p=>{console.log(`[Planning] Setting up SignalR transport for project ${p}`),D=new Oe(c.getHubProxy(),p),ie(D)};n&&s&&j(s);let f;n?f={prewarmWorkspace:async()=>{console.log("[LocalMode] Skipping prewarm - using existing workspace")},handleInitSession:async p=>{console.log(`[LocalMode] Session init for ${p.sessionId}`),console.log("[LocalMode] Skipping branch switch - using current branch"),await c.reportStatus("Ready",void 0,p.sessionId,p.projectId),await c.getHubProxy().initSessionCompleted(o.containerId,p.sessionId,!0,"")}}:f=new
|
|
1371
|
+
\u{1F50C} Connection Configuration:`);let a=await q.prompt([{type:"input",name:"masterUrl",message:"Master URL (backend):",default:e?.masterUrl||process.env.MASTER_URL||"http://localhost:5338",validate:g=>{if(!g.trim())return"Master URL is required";try{return new URL(g),!0}catch{return"Please enter a valid URL"}}},{type:"input",name:"projectId",message:"Project ID:",default:e?.projectId||process.env.PROJECT_ID,validate:g=>g.trim()?!0:"Project ID is required"},{type:"password",name:"projectKey",message:"Project Key (pk_proj_...):",mask:"*",default:e?.projectKey||process.env.PROJECT_KEY,validate:g=>g.trim()?g.startsWith("pk_proj_")?!0:"Project Key must start with pk_proj_":"Project Key is required"}]),c=await Nn(n),l=e?.containerId||Le(),d=c?.tunnel?.port||e?.tunnelPort||5173,u={masterUrl:a.masterUrl,projectId:a.projectId,projectKey:a.projectKey,containerId:l,enableTunnel:!0,tunnelPort:d,...c&&{version:c.version,paths:c.paths,services:c.services,techStack:c.techStack,tunnel:c.tunnel}};return console.log(`
|
|
1372
|
+
\u{1F4CB} Configuration built:`),console.log(` Master URL: ${u.masterUrl}`),console.log(` Project ID: ${u.projectId}`),console.log(` Project Key: ${u.projectKey.substring(0,16)}...`),console.log(` Container ID: ${u.containerId}`),console.log(` Tunnel: enabled (port ${d})`),console.log(c?" Project config: custom stack":" Project config: default .NET/React stack"),dt(n,u),{masterUrl:u.masterUrl,projectId:u.projectId,projectKey:u.projectKey,containerId:u.containerId,enableTunnel:u.enableTunnel,tunnelPort:u.tunnelPort,embeddedProjectConfig:c}}let s=await q.prompt([{type:"input",name:"masterUrl",message:"Master URL (backend):",default:e?.masterUrl||process.env.MASTER_URL||"http://localhost:5338",validate:a=>{if(!a.trim())return"Master URL is required";try{return new URL(a),!0}catch{return"Please enter a valid URL"}}},{type:"input",name:"projectId",message:"Project ID:",default:e?.projectId||process.env.PROJECT_ID,validate:a=>a.trim()?!0:"Project ID is required"},{type:"password",name:"projectKey",message:"Project Key (pk_proj_...):",mask:"*",default:e?.projectKey||process.env.PROJECT_KEY,validate:a=>a.trim()?a.startsWith("pk_proj_")?!0:"Project Key must start with pk_proj_":"Project Key is required"},{type:"input",name:"containerId",message:"Container ID (unique identifier for this agent):",default:e?.containerId||Le()},{type:"confirm",name:"enableTunnel",message:"Enable preview URL reporting (report local dev server URL to backend)?",default:e?.enableTunnel!==!1}]),r=e?.tunnelPort||5173;s.enableTunnel&&(r=(await q.prompt([{type:"number",name:"tunnelPort",message:"Preview port (local dev server port to report):",default:r}])).tunnelPort||r);let i={...s,tunnelPort:r};return dt(n,i),{...i,embeddedProjectConfig:null}}async function Fn(){let n=process.env.LOCAL_MODE==="true",e=process.env.WORKSPACE_DIR||process.cwd(),t=An();console.log(n?`=== Glenn Code v${t} ===`:`=== SignalR Agent v${t} ===`),console.log(`Workspace: ${e}`),n&&!M.existsSync(Y.join(e,".git"))&&console.warn("\u26A0\uFE0F Warning: Not a git repository. Some features may not work.");let o,s,r=!1,i=5173,a=null;if(n){let p=await Mn(e);o={masterUrl:p.masterUrl,containerId:p.containerId,projectKey:p.projectKey,workspaceDir:e},s=p.projectId,r=p.enableTunnel,i=p.tunnelPort,a=p.embeddedProjectConfig,process.env.PROJECT_ID=p.projectId,process.env.CONTAINER_ID=p.containerId,process.env.PROJECT_KEY=p.projectKey}else{let p=process.env.PROJECT_KEY;p||(console.error("\u274C PROJECT_KEY environment variable is required"),process.exit(1)),o={masterUrl:process.env.MASTER_URL||"http://host.docker.internal:5338",containerId:process.env.CONTAINER_ID||"unknown",projectKey:p,workspaceDir:e}}console.log(`
|
|
1373
|
+
\u{1F50C} Connecting to backend...`),console.log(` Container ID: ${o.containerId}`),console.log(` Project Key: ${o.projectKey.substring(0,16)}...`),console.log(` Gateway URL: ${o.masterUrl}`);let c=new je(o),l=0,d=10;for(;l<d;)try{await c.connect();break}catch(p){if(l++,console.error(`Connection attempt ${l} failed:`,p instanceof Error?p.message:p),l<d){let C=Math.min(1e3*Math.pow(2,l),3e4);await new Promise(de=>setTimeout(de,C))}}l>=d&&(console.error("\u274C Failed to connect to gateway after max retries"),process.exit(1)),n&&console.log("\u2705 Connected to backend");let u=await c.getSecrets();n?(console.log("\u2705 Handshake complete"),u.gitHubPat&&console.log(" GitHub PAT received from backend")):console.log("\u2705 Secrets received");let g,w=!0,v="default";if(n){if(a)g=a,v="embedded in local-signalr-config.json",w=!1,console.log("[Config] Using embedded project config from local-signalr-config.json");else{let p=await _e({workspaceDir:o.workspaceDir});g=p.config,v=p.source==="file"?p.configPath||".sdd/project-config.json":"default (no config file)",p.source==="file"&&(w=!1)}w||g.tunnel?.port&&(i=g.tunnel.port)}let b=xe({workspaceDir:o.workspaceDir,projectConfig:g});he(b);let D=ce(o.workspaceDir);ie(D);let j=p=>{console.log(`[Planning] Setting up SignalR transport for project ${p}`),D=new Oe(c.getHubProxy(),p),ie(D)};n&&s&&j(s);let f;n?f={prewarmWorkspace:async()=>{console.log("[LocalMode] Skipping prewarm - using existing workspace")},handleInitSession:async p=>{console.log(`[LocalMode] Session init for ${p.sessionId}`),console.log("[LocalMode] Skipping branch switch - using current branch"),await c.reportStatus("Ready",void 0,p.sessionId,p.projectId),await c.getHubProxy().initSessionCompleted(o.containerId,p.sessionId,!0,"")}}:f=new Ne(c,b,o.workspaceDir,u.gitHubPat||null);let O=process.env.DISABLE_AUTO_TYPECHECK!=="true",G=process.env.DISABLE_AUTO_COMMIT!=="true"&&!n;n&&g?xn({useDefaultStack:w,projectConfig:g,configSource:v,tunnelPort:i,enableAutoTypecheck:O,enableAutoCommit:G}):(console.log(`[Config] Auto-typecheck: ${O?"enabled":"disabled"}`),console.log(`[Config] Auto-commit: ${G?"enabled":"disabled"}`));let F=new Fe(c,f,b,o.workspaceDir,O,G,j,g,w);if(c.registerReceiver(F),n){if(r){let p=`http://localhost:${i}`;c.setTunnelUrl(p),console.log(`
|
|
1297
1374
|
\u{1F310} Preview available at: ${p}`)}await c.reportStatus("Ready",void 0,void 0,s),console.log(`
|
|
1298
1375
|
\u2705 Agent ready and waiting for commands`),console.log(` Open the frontend and select this agent to start working
|
|
1299
1376
|
`),console.log(` Press Ctrl+C to disconnect
|