acdev 1.0.0

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.
@@ -0,0 +1,449 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>acdev</title>
7
+ <link rel="icon" href="/acdev_wordmark_logo.svg" type="image/svg+xml">
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;1,9..40,400&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
11
+ <link rel="stylesheet" href="/styles.css">
12
+ </head>
13
+ <body>
14
+ <div class="app" id="app" data-theme="light">
15
+ <!-- SIDEBAR -->
16
+ <aside class="sidebar" aria-label="Navigation">
17
+ <div class="sidebar-brand">
18
+ <img
19
+ class="brand-logo"
20
+ src="/acdev_wordmark_logo.svg"
21
+ alt="acdev"
22
+ width="140"
23
+ height="28"
24
+ decoding="async"
25
+ >
26
+ <div class="brand-tagline">issue → fix → your review → PR</div>
27
+ </div>
28
+
29
+ <nav class="sidebar-nav" id="sidebar-nav" aria-label="Views">
30
+ <button type="button" class="nav-item active" data-view="overview" aria-current="page">
31
+ <span class="nav-icon" aria-hidden="true">
32
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="9"/><rect x="14" y="3" width="7" height="5"/><rect x="14" y="12" width="7" height="9"/><rect x="3" y="16" width="7" height="5"/></svg>
33
+ </span>
34
+ <span class="nav-label">Overview</span>
35
+ <span class="nav-badge hidden" id="nav-badge-overview"></span>
36
+ </button>
37
+ <button type="button" class="nav-item" data-view="runs">
38
+ <span class="nav-icon" aria-hidden="true">
39
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m12 3-1.9 5.8a2 2 0 0 1-1.287 1.288L3 12l5.8 1.9a2 2 0 0 1 1.288 1.287L12 21l1.9-5.8a2 2 0 0 1 1.287-1.288L21 12l-5.8-1.9a2 2 0 0 1-1.288-1.287Z"/></svg>
40
+ </span>
41
+ <span class="nav-label">Runs</span>
42
+ <span class="nav-badge hidden" id="nav-badge-runs"></span>
43
+ </button>
44
+ <button type="button" class="nav-item" data-view="review">
45
+ <span class="nav-icon" aria-hidden="true">
46
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="6" rx="1"/><rect x="3" y="14" width="18" height="6" rx="1"/><path d="M7 7h.01M7 17h.01"/></svg>
47
+ </span>
48
+ <span class="nav-label">Review</span>
49
+ <span class="nav-badge hidden" id="nav-badge-review"></span>
50
+ </button>
51
+ <button type="button" class="nav-item" data-view="logs">
52
+ <span class="nav-icon" aria-hidden="true">
53
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19V6a2 2 0 0 1 2-2h9l5 5v10a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2Z"/><path d="M9 9h1M9 13h6M9 17h6"/></svg>
54
+ </span>
55
+ <span class="nav-label">Logs</span>
56
+ </button>
57
+ <button type="button" class="nav-item" data-view="alerts">
58
+ <span class="nav-icon" aria-hidden="true">
59
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z"/><path d="M12 9v4M12 17h.01"/></svg>
60
+ </span>
61
+ <span class="nav-label">Alerts</span>
62
+ <span class="nav-badge hidden" id="nav-badge-alerts"></span>
63
+ </button>
64
+ <button type="button" class="nav-item" data-view="settings">
65
+ <span class="nav-icon" aria-hidden="true">
66
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2Z"/><circle cx="12" cy="12" r="3"/></svg>
67
+ </span>
68
+ <span class="nav-label">Settings</span>
69
+ </button>
70
+ </nav>
71
+
72
+ <div class="sidebar-footer">
73
+ <div class="sidebar-divider"></div>
74
+ <div class="model-block">
75
+ <div class="model-label">Model</div>
76
+ <div class="model-toggle" id="model-toggle" role="group" aria-label="Claude model">
77
+ <button type="button" class="model-btn active" data-model="claude-sonnet-5">Sonnet 5</button>
78
+ <button type="button" class="model-btn" data-model="claude-opus-5">Opus 5</button>
79
+ </div>
80
+ </div>
81
+ <div class="repo-meta">
82
+ <div class="repo-name" id="repo-name">—</div>
83
+ <div class="repo-status">
84
+ <span class="repo-dot" aria-hidden="true"></span>
85
+ <span id="repo-branch">—</span>
86
+ </div>
87
+ <div class="ticket-source-meta" id="ticket-source-meta">Tickets: GitHub</div>
88
+ </div>
89
+ </div>
90
+ </aside>
91
+
92
+ <!-- MAIN -->
93
+ <main class="main">
94
+ <header class="header">
95
+ <h1 class="header-title" id="view-title">Overview</h1>
96
+ <button type="button" class="theme-toggle" id="theme-toggle" aria-label="Toggle theme">
97
+ <svg class="theme-icon-moon" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79Z"/></svg>
98
+ <svg class="theme-icon-sun hidden" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"/></svg>
99
+ </button>
100
+ </header>
101
+
102
+ <div class="main-scroll amcp-scroll" id="content">
103
+ <!-- OVERVIEW -->
104
+ <section class="view" id="view-overview" data-view-panel="overview">
105
+ <div class="stack stack-lg max-w-overview">
106
+ <section>
107
+ <div class="section-label">Add issues</div>
108
+ <div class="card enqueue-card">
109
+ <label class="sr-only" for="issue-urls" id="issue-urls-label">Issue references</label>
110
+ <textarea
111
+ id="issue-urls"
112
+ class="input textarea"
113
+ rows="3"
114
+ placeholder="Paste GitHub issue links — one per line, or comma-separated&#10;e.g. https://github.com/acme/storefront/issues/482"
115
+ ></textarea>
116
+ <div class="enqueue-footer">
117
+ <span class="hint" id="enqueue-hint">Issues run one at a time, each in its own workspace and branch.</span>
118
+ <p id="enqueue-feedback" class="feedback hidden"></p>
119
+ <button id="add-btn" type="button" class="btn btn-primary">Add to queue</button>
120
+ </div>
121
+ </div>
122
+ </section>
123
+
124
+ <section class="stats-grid" id="stats-grid"></section>
125
+
126
+ <section>
127
+ <div class="section-label">Running — fix in progress</div>
128
+ <div id="overview-running" class="item-stack"></div>
129
+ <p id="overview-running-empty" class="empty-inline hidden">Nothing running.</p>
130
+ </section>
131
+
132
+ <section>
133
+ <div class="section-label">Ready for review — waiting on you</div>
134
+ <div id="overview-review" class="item-stack"></div>
135
+ <p id="overview-review-empty" class="empty-inline hidden">Nothing waiting for review.</p>
136
+ </section>
137
+
138
+ <section>
139
+ <div class="section-label">Queued — runs one at a time</div>
140
+ <div id="overview-queue" class="item-stack"></div>
141
+ <div id="overview-queue-empty" class="empty-dashed hidden">Nothing queued. Paste issue links above to get started.</div>
142
+ </section>
143
+ </div>
144
+ </section>
145
+
146
+ <!-- RUNS -->
147
+ <section class="view hidden" id="view-runs" data-view-panel="runs">
148
+ <div class="stack max-w-runs" id="runs-list"></div>
149
+ <div id="runs-empty" class="empty-dashed hidden">No active or recent runs.</div>
150
+ </section>
151
+
152
+ <!-- REVIEW -->
153
+ <section class="view hidden" id="view-review" data-view-panel="review">
154
+ <div id="review-toolbar" class="review-toolbar hidden max-w-runs">
155
+ <div class="review-filters" id="review-filters" role="tablist" aria-label="Review status filter">
156
+ <button type="button" class="filter-chip" role="tab" data-review-filter="all" aria-selected="false">
157
+ All <span class="filter-chip-count" data-count-for="all"></span>
158
+ </button>
159
+ <button type="button" class="filter-chip" role="tab" data-review-filter="pending" aria-selected="false">
160
+ Pending <span class="filter-chip-count" data-count-for="pending"></span>
161
+ </button>
162
+ <button type="button" class="filter-chip" role="tab" data-review-filter="approved" aria-selected="false">
163
+ Approved <span class="filter-chip-count" data-count-for="approved"></span>
164
+ </button>
165
+ <button type="button" class="filter-chip" role="tab" data-review-filter="rejected" aria-selected="false">
166
+ Rejected <span class="filter-chip-count" data-count-for="rejected"></span>
167
+ </button>
168
+ <button type="button" class="filter-chip" role="tab" data-review-filter="failed" aria-selected="false">
169
+ Failed <span class="filter-chip-count" data-count-for="failed"></span>
170
+ </button>
171
+ </div>
172
+ <label class="sr-only" for="review-search">Search review jobs</label>
173
+ <input
174
+ id="review-search"
175
+ class="input review-search"
176
+ type="search"
177
+ placeholder="Search issue, title, URL, branch, PR…"
178
+ autocomplete="off"
179
+ >
180
+ </div>
181
+ <div id="review-empty" class="empty-dashed hidden max-w-runs">Nothing to review yet. Finished runs will land here.</div>
182
+ <div id="review-no-matches" class="empty-dashed hidden max-w-runs">No jobs match your filters.</div>
183
+ <div id="review-layout" class="review-layout hidden">
184
+ <div class="review-sidebar" id="review-list"></div>
185
+ <div class="review-detail" id="review-detail">
186
+ <div class="empty-dashed" id="review-empty-detail">Select a job to review</div>
187
+ <div class="stack hidden" id="review-detail-content">
188
+ <div class="card">
189
+ <div class="field-label">PR title</div>
190
+ <input id="pr-title" class="input input-title" type="text">
191
+ <div class="field-label field-label-spaced">Description</div>
192
+ <textarea id="pr-body" class="input textarea" rows="4"></textarea>
193
+ </div>
194
+
195
+ <div class="card" id="review-files-section">
196
+ <div class="review-files-header">
197
+ <div class="field-label">Files in this change</div>
198
+ <div class="review-files-count" id="review-files-count"></div>
199
+ </div>
200
+ <div class="review-files-toolbar">
201
+ <button type="button" class="btn btn-muted-text btn-sm" id="review-files-select-all">Select all</button>
202
+ <button type="button" class="btn btn-muted-text btn-sm" id="review-files-deselect-all">Deselect all</button>
203
+ </div>
204
+ <div id="review-files-list" class="review-files-list"></div>
205
+ </div>
206
+
207
+ <div>
208
+ <div class="section-label" id="diff-header">Diff</div>
209
+ <div id="diff-viewer" class="diff-viewer amcp-scroll"></div>
210
+ </div>
211
+
212
+ <div class="card" id="review-feedback-section">
213
+ <div class="field-label">General review comment</div>
214
+ <textarea
215
+ id="review-general-comment"
216
+ class="input textarea"
217
+ rows="3"
218
+ placeholder="Feedback on the whole change (like a PR review summary)…"
219
+ ></textarea>
220
+ <div class="field-label field-label-spaced">Pending line comments</div>
221
+ <div id="review-pending-list" class="review-pending-list">
222
+ <div class="review-pending-empty" id="review-pending-empty">No line comments yet. Click a diff line to add one.</div>
223
+ </div>
224
+ </div>
225
+
226
+ <div id="review-actions" class="actions">
227
+ <button id="submit-review-btn" type="button" class="btn btn-primary" disabled>Submit review</button>
228
+ <button id="approve-ready-btn" type="button" class="btn btn-secondary">Approve · ready for review</button>
229
+ <button id="approve-draft-btn" type="button" class="btn btn-secondary">Approve · draft</button>
230
+ <button id="reject-btn" type="button" class="btn btn-danger-text">Reject</button>
231
+ <button id="review-clear-btn" type="button" class="btn btn-muted-text">Clear</button>
232
+ </div>
233
+
234
+ <div id="review-pr-section" class="card card-success hidden">
235
+ <p class="pr-opened-line">PR opened: <a id="review-pr-link" href="#" target="_blank" rel="noopener"></a></p>
236
+ <div class="actions">
237
+ <button id="review-pr-clear-btn" type="button" class="btn btn-secondary btn-sm">Clear from history</button>
238
+ </div>
239
+ </div>
240
+
241
+ <div id="review-terminal-section" class="card hidden">
242
+ <p id="review-terminal-message" class="pr-opened-line"></p>
243
+ <div class="actions">
244
+ <button id="review-retry-btn" type="button" class="btn btn-warn btn-sm hidden">Retry</button>
245
+ <button id="review-terminal-clear-btn" type="button" class="btn btn-secondary btn-sm">Clear from history</button>
246
+ </div>
247
+ </div>
248
+ </div>
249
+ </div>
250
+ </div>
251
+ </section>
252
+
253
+ <!-- LOGS -->
254
+ <section class="view hidden" id="view-logs" data-view-panel="logs">
255
+ <div class="card card-flush log-table-card max-w-logs">
256
+ <div class="log-toolbar">
257
+ <label class="field-inline" for="log-job-filter">
258
+ <span class="field-inline-label">Filter</span>
259
+ <select id="log-job-filter" class="input select-sm">
260
+ <option value="">All jobs</option>
261
+ </select>
262
+ </label>
263
+ </div>
264
+ <div class="table-wrap amcp-scroll">
265
+ <table class="log-table">
266
+ <thead>
267
+ <tr>
268
+ <th>Time</th>
269
+ <th>Issue</th>
270
+ <th>Event</th>
271
+ <th>Detail</th>
272
+ </tr>
273
+ </thead>
274
+ <tbody id="activity-log"></tbody>
275
+ </table>
276
+ <p id="log-empty" class="empty-inline hidden">No log entries yet.</p>
277
+ </div>
278
+ </div>
279
+ </section>
280
+
281
+ <!-- ALERTS -->
282
+ <section class="view hidden" id="view-alerts" data-view-panel="alerts">
283
+ <div id="alerts-empty" class="empty-dashed hidden max-w-runs">No alerts. Everything is running clean.</div>
284
+ <div id="alerts-list" class="item-stack max-w-runs"></div>
285
+ </section>
286
+
287
+ <!-- SETTINGS -->
288
+ <section class="view hidden" id="view-settings" data-view-panel="settings">
289
+ <div class="stack stack-lg max-w-settings">
290
+ <section>
291
+ <div class="section-label">Ticket source</div>
292
+ <p class="settings-lead">
293
+ Choose where tickets come from. Pull requests are always created on the local repo’s GitHub remote via <code>gh</code>.
294
+ </p>
295
+ <div class="card settings-card">
296
+ <div class="settings-field settings-field-full">
297
+ <div class="field-label">Ticket source</div>
298
+ <div class="source-toggle" id="settings-ticket-source" role="group" aria-label="Ticket source">
299
+ <button type="button" class="source-btn active" data-source="github">GitHub Issues</button>
300
+ <button type="button" class="source-btn" data-source="jira">Jira</button>
301
+ </div>
302
+ <p class="field-hint">Switch anytime — queued jobs keep their original source.</p>
303
+ </div>
304
+
305
+ <div id="jira-connect-panel" class="jira-panel hidden">
306
+ <div class="settings-grid">
307
+ <div class="settings-field settings-field-full">
308
+ <label class="field-label" for="settings-jira-base-url">Jira Cloud base URL</label>
309
+ <input id="settings-jira-base-url" class="input" type="url" name="jiraBaseUrl" autocomplete="off" placeholder="https://your-domain.atlassian.net">
310
+ <p class="field-hint">Your Atlassian Cloud site (Jira Server/Data Center is not supported).</p>
311
+ </div>
312
+ <div class="settings-field">
313
+ <label class="field-label" for="settings-jira-email">Email</label>
314
+ <input id="settings-jira-email" class="input" type="email" name="jiraEmail" autocomplete="username" placeholder="you@company.com">
315
+ </div>
316
+ <div class="settings-field">
317
+ <label class="field-label" for="settings-jira-token">API token</label>
318
+ <input id="settings-jira-token" class="input" type="password" name="jiraApiToken" autocomplete="new-password" placeholder="Leave blank to keep existing">
319
+ <p class="field-hint" id="settings-jira-token-hint">Create a token at id.atlassian.com → Security → API tokens. Stored in <code>.acdev/.env</code> (not committed).</p>
320
+ </div>
321
+ <div class="settings-field settings-field-full">
322
+ <label class="field-label" for="settings-jira-pr-phrase">PR link phrase</label>
323
+ <input id="settings-jira-pr-phrase" class="input" type="text" name="jiraPrLinkPhrase" autocomplete="off" placeholder="Relates to">
324
+ <p class="field-hint">PR bodies use <code>{phrase} PROJ-123</code> instead of <code>Closes #N</code>.</p>
325
+ </div>
326
+ </div>
327
+ <div class="jira-actions">
328
+ <button id="jira-test-btn" type="button" class="btn btn-secondary">Test connection</button>
329
+ <span id="jira-status" class="jira-status" role="status"></span>
330
+ </div>
331
+ </div>
332
+ </div>
333
+ </section>
334
+
335
+ <section>
336
+ <div class="section-label">Configuration</div>
337
+ <p class="settings-lead">
338
+ Changes save to <code>.acdev/config.json</code> and apply to the next job — no restart needed. Includes post-PR rules below.
339
+ </p>
340
+ <form id="settings-form" class="stack stack-lg" novalidate>
341
+ <div class="card settings-card">
342
+ <div class="rules-heading">Rules</div>
343
+ <p class="field-hint">
344
+ Optional automation after a PR is opened. Failures are logged as warnings and never undo a successful PR.
345
+ </p>
346
+
347
+ <div id="jira-rules-panel" class="rules-block hidden">
348
+ <div class="field-label">Jira — after PR opened</div>
349
+ <p class="field-hint">
350
+ Moves the ticket via a workflow transition whose target status name matches (case-insensitive), e.g. <code>In Review</code> or <code>Code Review</code>.
351
+ </p>
352
+ <div class="settings-grid">
353
+ <div class="settings-field settings-field-full">
354
+ <label class="rule-toggle">
355
+ <input id="settings-jira-rule-enabled" type="checkbox" name="jiraRuleEnabled">
356
+ <span>Enable status move</span>
357
+ </label>
358
+ </div>
359
+ <div class="settings-field settings-field-full">
360
+ <label class="field-label" for="settings-jira-rule-status">Target status name</label>
361
+ <input id="settings-jira-rule-status" class="input" type="text" name="jiraRuleTargetStatus" autocomplete="off" placeholder="In Review">
362
+ <p class="field-hint">Must match a status reachable from the issue’s current workflow (exact name, any casing).</p>
363
+ </div>
364
+ </div>
365
+ </div>
366
+
367
+ <div id="github-rules-panel" class="rules-block">
368
+ <div class="field-label">GitHub Issues — after PR opened</div>
369
+ <p class="field-hint">
370
+ GitHub issues don’t have Jira-like board statuses by default. Use a label (e.g. <code>in-review</code>) or close the issue. Projects board API is not supported in v1.
371
+ </p>
372
+ <div class="settings-grid">
373
+ <div class="settings-field settings-field-full">
374
+ <label class="rule-toggle">
375
+ <input id="settings-github-rule-enabled" type="checkbox" name="githubRuleEnabled">
376
+ <span>Enable action</span>
377
+ </label>
378
+ </div>
379
+ <div class="settings-field">
380
+ <label class="field-label" for="settings-github-rule-action">Action</label>
381
+ <select id="settings-github-rule-action" class="input" name="githubRuleAction">
382
+ <option value="none">None</option>
383
+ <option value="add_label">Add label</option>
384
+ <option value="close_issue">Close issue</option>
385
+ </select>
386
+ </div>
387
+ <div class="settings-field" id="settings-github-rule-label-field">
388
+ <label class="field-label" for="settings-github-rule-label">Label</label>
389
+ <input id="settings-github-rule-label" class="input" type="text" name="githubRuleLabel" autocomplete="off" placeholder="in-review">
390
+ <p class="field-hint">Used when action is Add label.</p>
391
+ </div>
392
+ </div>
393
+ </div>
394
+ </div>
395
+
396
+ <div class="card settings-card">
397
+ <div class="settings-grid">
398
+ <div class="settings-field">
399
+ <label class="field-label" for="settings-base-branch">Base branch</label>
400
+ <input id="settings-base-branch" class="input" type="text" name="baseBranch" autocomplete="off" required>
401
+ <p class="field-hint">Branch synced before each run (e.g. develop, main).</p>
402
+ </div>
403
+
404
+ <div class="settings-field">
405
+ <label class="field-label" for="settings-model">Model</label>
406
+ <select id="settings-model" class="input" name="model"></select>
407
+ <p class="field-hint">Synced with the sidebar model toggle.</p>
408
+ </div>
409
+
410
+ <div class="settings-field">
411
+ <label class="field-label" for="settings-max-turns">Max agent turns</label>
412
+ <input id="settings-max-turns" class="input" type="number" name="maxAgentTurns" min="1" step="1" required>
413
+ <p class="field-hint">Positive integer — how many tool/response turns the agent may take.</p>
414
+ </div>
415
+
416
+ <div class="settings-field">
417
+ <label class="field-label" for="settings-timeout">Agent timeout (minutes)</label>
418
+ <input id="settings-timeout" class="input" type="number" name="agentTimeoutMinutes" min="1" step="1" required>
419
+ <p class="field-hint">Stored as milliseconds in config (<span id="settings-timeout-ms">—</span> ms).</p>
420
+ </div>
421
+
422
+ <div class="settings-field settings-field-full">
423
+ <label class="field-label" for="settings-test-command">Test command</label>
424
+ <input id="settings-test-command" class="input" type="text" name="testCommand" autocomplete="off" placeholder="Optional — leave empty to clear">
425
+ <p class="field-hint">Run after the agent finishes (e.g. <code>npm test</code>). Empty clears the value.</p>
426
+ </div>
427
+
428
+ <div class="settings-field settings-field-full">
429
+ <div class="field-label">Allowed tools</div>
430
+ <div class="tools-grid" id="settings-tools" role="group" aria-label="Allowed tools"></div>
431
+ <p class="field-hint">Tools the agent may use. Defaults: Read, Glob, Grep, Edit, Write, Bash.</p>
432
+ </div>
433
+ </div>
434
+
435
+ <div class="settings-footer">
436
+ <p id="settings-feedback" class="feedback hidden" role="status"></p>
437
+ <button id="settings-save-btn" type="submit" class="btn btn-primary">Save settings</button>
438
+ </div>
439
+ </div>
440
+ </form>
441
+ </section>
442
+ </div>
443
+ </section>
444
+ </div>
445
+ </main>
446
+ </div>
447
+ <script src="/app.js" defer></script>
448
+ </body>
449
+ </html>