@@ -32,6 +32,15 @@ function parsePortFromOrigin(origin: string): number | null {
3232 }
3333}
3434
35+ function escapeHtml ( input : string ) : string {
36+ return input
37+ . replaceAll ( '&' , '&' )
38+ . replaceAll ( '<' , '<' )
39+ . replaceAll ( '>' , '>' )
40+ . replaceAll ( '"' , '"' )
41+ . replaceAll ( "'" , ''' )
42+ }
43+
3544async function fetchWithTimeout ( url : string , timeoutMs : number ) : Promise < Response > {
3645 const controller = new AbortController ( )
3746 const timer = setTimeout ( ( ) => controller . abort ( ) , timeoutMs )
@@ -113,6 +122,63 @@ app.get('/api/home-info', (_req, res) => {
113122 } )
114123} )
115124
125+ if ( ! SHOULD_SERVE_BUILT_WEB ) {
126+ app . get ( '/' , ( _req , res ) => {
127+ const functions = [
128+ 'action_approval' ,
129+ 'code_review' ,
130+ 'email_review' ,
131+ 'plan_review' ,
132+ 'trajectory_review' ,
133+ 'form_review' ,
134+ 'selection_review' ,
135+ ]
136+ let readmeSummary = 'README.md not found.'
137+ if ( existsSync ( README_PATH ) ) {
138+ const readme = readFileSync ( README_PATH , 'utf-8' )
139+ readmeSummary = readme . split ( '\n' ) . slice ( 0 , 40 ) . join ( '\n' )
140+ }
141+
142+ const html = `<!doctype html>
143+ <html>
144+ <head>
145+ <meta charset="utf-8" />
146+ <meta name="viewport" content="width=device-width, initial-scale=1" />
147+ <title>AgentClick Default Page</title>
148+ <style>
149+ body { font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif; background: #f6f7f9; color: #111827; margin: 0; }
150+ .wrap { max-width: 980px; margin: 0 auto; padding: 28px 16px; }
151+ .card { background: #fff; border: 1px solid #e5e7eb; border-radius: 10px; padding: 16px; margin-bottom: 14px; }
152+ .chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0 14px; }
153+ .chip { font-size: 12px; padding: 3px 8px; border-radius: 999px; background: #f3f4f6; color: #374151; border: 1px solid #e5e7eb; }
154+ pre { background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 8px; padding: 12px; overflow: auto; white-space: pre-wrap; font-size: 12px; line-height: 1.5; }
155+ a { color: #2563eb; text-decoration: none; }
156+ a:hover { text-decoration: underline; }
157+ h1 { margin: 0 0 6px; font-size: 24px; }
158+ p { margin: 0; color: #4b5563; }
159+ </style>
160+ </head>
161+ <body>
162+ <div class="wrap">
163+ <div class="card">
164+ <h1>AgentClick Default Page</h1>
165+ <p>Server: <strong>http://localhost:${ PORT } </strong> | Frontend: <strong>${ escapeHtml ( WEB_ORIGIN ) } </strong></p>
166+ <p style="margin-top:8px;"><a href="${ escapeHtml ( WEB_ORIGIN ) } /" target="_blank" rel="noreferrer">Open Frontend Default Page</a> · <a href="https://github.com/agentlayer-io/AgentClick" target="_blank" rel="noreferrer">Open Repository</a></p>
167+ </div>
168+ <div class="card">
169+ <strong>Functions</strong>
170+ <div class="chips">
171+ ${ functions . map ( fn => `<span class="chip">${ fn } </span>` ) . join ( '' ) }
172+ </div>
173+ <pre>${ escapeHtml ( readmeSummary ) } </pre>
174+ </div>
175+ </div>
176+ </body>
177+ </html>`
178+ res . status ( 200 ) . send ( html )
179+ } )
180+ }
181+
116182// OpenClaw calls this when a review is needed
117183app . post ( '/api/review' , async ( req , res ) => {
118184 const { type, sessionKey, payload, noOpen, openHome } = req . body
0 commit comments