@autocode-cli/autocode 0.0.41 → 0.0.43

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.
@@ -191,7 +191,7 @@ export function generateDashboard() {
191
191
  </div>
192
192
 
193
193
  <footer>
194
- <span>AutoCode v${pkg.version} | <span id="time">${timestamp}</span></span>
194
+ <span>AutoCode v${pkg.version} | <span id="time">${timestamp}</span> | <code title="autodoc root">${config.root}</code></span>
195
195
  </footer>
196
196
 
197
197
  <script>
@@ -1009,6 +1009,46 @@ function getStyles() {
1009
1009
  color: var(--text);
1010
1010
  margin: 0;
1011
1011
  }
1012
+ /* Formatted log entries */
1013
+ .log-entry { margin-bottom: 8px; padding: 8px 12px; border-radius: 4px; border-left: 3px solid transparent; flex-shrink: 0; }
1014
+ .log-entry.timestamp { color: #8b949e; font-size: 11px; border-left-color: #484f58; background: transparent; padding: 4px 12px; }
1015
+ .log-entry.system { color: #8b949e; border-left-color: #484f58; background: rgba(139,148,158,0.1); }
1016
+ .log-entry.user { color: #58a6ff; border-left-color: #58a6ff; background: rgba(88,166,255,0.1); }
1017
+ .log-entry.assistant { color: #7ee787; border-left-color: #7ee787; background: rgba(126,231,135,0.1); }
1018
+ .log-entry.tool-call { color: #d2a8ff; border-left-color: #d2a8ff; background: rgba(210,168,255,0.1); padding: 12px; }
1019
+ .log-entry.tool-result { color: #ffa657; border-left-color: #ffa657; background: rgba(255,166,87,0.1); }
1020
+ .log-entry.error { color: #f85149; border-left-color: #f85149; background: rgba(248,81,73,0.1); }
1021
+ .log-entry.success { color: #7ee787; border-left-color: #7ee787; background: rgba(126,231,135,0.1); }
1022
+ .log-label { font-weight: 600; font-size: 11px; text-transform: uppercase; margin-bottom: 4px; display: block; opacity: 0.8; }
1023
+ .log-content { white-space: pre-wrap; word-break: break-word; }
1024
+
1025
+ /* Code blocks with line numbers */
1026
+ .log-code-block { background: #161b22; border-radius: 6px; overflow: hidden; margin: 8px 0; border: 1px solid #30363d; }
1027
+ .log-code-header { background: #21262d; padding: 8px 12px; font-size: 12px; color: #8b949e; border-bottom: 1px solid #30363d; display: flex; align-items: center; gap: 8px; }
1028
+ .log-code-header::before { content: ''; display: inline-block; width: 12px; height: 12px; background: #ffa657; border-radius: 50%; }
1029
+ .log-code-content { padding: 12px; overflow-x: auto; font-family: 'Fira Code', 'SF Mono', Monaco, monospace; font-size: 12px; line-height: 1.5; max-height: 400px; overflow-y: auto; }
1030
+ .log-code-line { display: flex; min-height: 20px; }
1031
+ .log-line-number { color: #484f58; text-align: right; padding-right: 16px; user-select: none; min-width: 40px; flex-shrink: 0; }
1032
+ .log-line-content { color: #c9d1d9; white-space: pre; }
1033
+
1034
+ /* Message cards */
1035
+ .log-message-card { background: #161b22; border-radius: 8px; margin: 12px 0; overflow: hidden; border: 1px solid #30363d; flex-shrink: 0; }
1036
+ .log-message-header { padding: 10px 14px; display: flex; align-items: center; gap: 8px; font-weight: 500; font-size: 13px; }
1037
+ .log-message-header.assistant-header { background: linear-gradient(135deg, #238636 0%, #2ea043 100%); color: white; }
1038
+ .log-message-header.user-header { background: linear-gradient(135deg, #1f6feb 0%, #388bfd 100%); color: white; }
1039
+ .log-message-header.tool-header { background: linear-gradient(135deg, #8b5cf6 0%, #a78bfa 100%); color: white; }
1040
+ .log-message-header.result-header { background: linear-gradient(135deg, #f97316 0%, #fb923c 100%); color: white; }
1041
+ .log-message-body { padding: 14px; border-top: 1px solid #30363d; color: #c9d1d9; white-space: pre-wrap; word-break: break-word; }
1042
+
1043
+ /* Tool badges */
1044
+ .log-tool-badge { display: inline-flex; align-items: center; gap: 6px; background: rgba(139,92,246,0.2); color: #a78bfa; padding: 4px 10px; border-radius: 12px; font-size: 12px; font-weight: 500; }
1045
+ .log-tool-input { background: #0d1117; border-radius: 4px; padding: 8px 12px; margin-top: 8px; font-family: 'Fira Code', monospace; font-size: 11px; color: #8b949e; max-height: 150px; overflow: auto; white-space: pre-wrap; }
1046
+
1047
+ /* Collapsible raw JSON */
1048
+ .log-raw-details { margin: 8px 0; }
1049
+ .log-raw-summary { cursor: pointer; color: #8b949e; font-size: 12px; padding: 8px; background: rgba(139,148,158,0.1); border-radius: 4px; }
1050
+ .log-raw-summary:hover { background: rgba(139,148,158,0.2); }
1051
+ .log-raw-json { background: #0d1117; border-radius: 4px; padding: 12px; margin-top: 8px; font-size: 11px; color: #8b949e; max-height: 200px; overflow: auto; white-space: pre-wrap; }
1012
1052
  @keyframes pulse {
1013
1053
  0%, 100% { opacity: 1; }
1014
1054
  50% { opacity: 0.5; }
@@ -2103,10 +2143,144 @@ function getScript() {
2103
2143
  }
2104
2144
  }
2105
2145
 
2146
+ function escapeHtml(str) {
2147
+ if (typeof str !== 'string') return '';
2148
+ return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
2149
+ }
2150
+
2151
+ function formatCodeBlock(content, filename) {
2152
+ const lines = content.split(/\\\\n|\\n/);
2153
+ // Détecter le langage depuis le nom de fichier
2154
+ let lang = 'plaintext';
2155
+ if (filename) {
2156
+ const ext = filename.split('.').pop().toLowerCase();
2157
+ const langMap = {
2158
+ 'js': 'javascript', 'ts': 'typescript', 'vue': 'html', 'jsx': 'javascript',
2159
+ 'tsx': 'typescript', 'py': 'python', 'rb': 'ruby', 'java': 'java',
2160
+ 'go': 'go', 'rs': 'rust', 'cpp': 'cpp', 'c': 'c', 'h': 'c',
2161
+ 'css': 'css', 'scss': 'scss', 'html': 'html', 'json': 'json',
2162
+ 'md': 'markdown', 'sh': 'bash', 'yml': 'yaml', 'yaml': 'yaml'
2163
+ };
2164
+ lang = langMap[ext] || 'plaintext';
2165
+ }
2166
+ // Extraire le code sans les numéros de ligne pour highlight.js
2167
+ let codeLines = [];
2168
+ for (const line of lines) {
2169
+ const match = line.match(/^\\s*\\d+[→|](.*)$/);
2170
+ if (match) {
2171
+ codeLines.push(match[1]);
2172
+ } else if (line.trim()) {
2173
+ codeLines.push(line);
2174
+ }
2175
+ }
2176
+ const codeContent = codeLines.join('\\n');
2177
+ const lineCount = codeLines.length;
2178
+ const preview = filename || (lineCount + ' lignes');
2179
+
2180
+ let html = '<details class="log-code-block">';
2181
+ html += '<summary class="log-code-header"><span class="code-lang">' + lang + '</span> ' + escapeHtml(preview) + '</summary>';
2182
+ html += '<pre><code class="language-' + lang + '">' + escapeHtml(codeContent) + '</code></pre>';
2183
+ html += '</details>';
2184
+ return html;
2185
+ }
2186
+
2187
+ function formatLogContent(rawContent) {
2188
+ if (!rawContent) return '';
2189
+ // Supprimer les balises <system-reminder> et leur contenu
2190
+ let cleanedContent = rawContent.replace(/<system-reminder>[\\s\\S]*?<\\/system-reminder>/gi, '');
2191
+ const lines = cleanedContent.split('\\n');
2192
+ const entries = [];
2193
+
2194
+ for (const line of lines) {
2195
+ if (!line.trim()) continue;
2196
+
2197
+ // Timestamp line
2198
+ const timestampMatch = line.match(/^\\[(\\d{4}-\\d{2}-\\d{2}T[^\\]]+)\\]\\s*(.*)$/);
2199
+ if (timestampMatch) {
2200
+ const date = new Date(timestampMatch[1]);
2201
+ const timeStr = date.toLocaleTimeString();
2202
+ entries.push('<div class="log-entry timestamp">' + timeStr + ' - ' + escapeHtml(timestampMatch[2]) + '</div>');
2203
+ continue;
2204
+ }
2205
+
2206
+ // [RAW] - Parse avec regex (pas JSON.parse car les lignes sont coupées)
2207
+ if (line.startsWith('[RAW] ')) {
2208
+ const raw = line.slice(6);
2209
+
2210
+ // Ignorer les lignes de continuation (ne commencent pas par {)
2211
+ if (!raw.startsWith('{')) {
2212
+ continue;
2213
+ }
2214
+
2215
+ // Extraire code source avec numéros de ligne (tool_result)
2216
+ const codeMatch = raw.match(/"content":"(\\s*\\d+[→|][^"]*)/);
2217
+ if (codeMatch) {
2218
+ // Extraire tout le contenu entre "content":" et la fin
2219
+ const contentMatch = raw.match(/"content":"([^"]+)/);
2220
+ if (contentMatch) {
2221
+ const decoded = contentMatch[1].replace(/\\\\n/g, '\\n').replace(/\\\\"/g, '"');
2222
+ entries.push('<div class="log-message-card"><div class="log-message-header result-header">Tool Result</div><div class="log-message-body">' + formatCodeBlock(decoded) + '</div></div>');
2223
+ continue;
2224
+ }
2225
+ }
2226
+
2227
+ // Extraire message assistant texte
2228
+ const textMatch = raw.match(/"type":"text","text":"([^"]+)"/);
2229
+ if (textMatch) {
2230
+ const decoded = textMatch[1].replace(/\\\\n/g, '\\n').replace(/\\\\"/g, '"');
2231
+ entries.push('<div class="log-message-card"><div class="log-message-header assistant-header">Assistant</div><div class="log-message-body">' + escapeHtml(decoded) + '</div></div>');
2232
+ continue;
2233
+ }
2234
+
2235
+ // Extraire tool_use
2236
+ const toolMatch = raw.match(/"type":"tool_use"[^}]*"name":"([^"]+)"/);
2237
+ if (toolMatch) {
2238
+ entries.push('<div class="log-entry tool-call"><span class="log-tool-badge">' + escapeHtml(toolMatch[1]) + '</span></div>');
2239
+ continue;
2240
+ }
2241
+
2242
+ // Ignorer les autres RAW (métadonnées, etc.)
2243
+ continue;
2244
+ }
2245
+
2246
+ // Other prefixed messages
2247
+ if (line.startsWith('[SYSTEM]')) {
2248
+ entries.push('<div class="log-entry system"><span class="log-label">System</span><div class="log-content">' + escapeHtml(line.slice(9)) + '</div></div>');
2249
+ continue;
2250
+ }
2251
+ if (line.startsWith('[ASSISTANT]')) {
2252
+ entries.push('<div class="log-message-card"><div class="log-message-header assistant-header">Assistant</div><div class="log-message-body">' + escapeHtml(line.slice(12)) + '</div></div>');
2253
+ continue;
2254
+ }
2255
+ if (line.startsWith('[TOOL]')) {
2256
+ entries.push('<div class="log-entry tool-call"><span class="log-tool-badge">' + escapeHtml(line.slice(7)) + '</span></div>');
2257
+ continue;
2258
+ }
2259
+ if (line.startsWith('[RESULT]')) {
2260
+ const content = line.slice(9);
2261
+ if (/^\\s*\\d+[→|]/.test(content)) {
2262
+ entries.push('<div class="log-message-card"><div class="log-message-header result-header">Result</div><div class="log-message-body">' + formatCodeBlock(content) + '</div></div>');
2263
+ } else {
2264
+ entries.push('<div class="log-message-card"><div class="log-message-header result-header">Result</div><div class="log-message-body">' + escapeHtml(content.slice(0, 1000)) + (content.length > 1000 ? '...' : '') + '</div></div>');
2265
+ }
2266
+ continue;
2267
+ }
2268
+ if (line.startsWith('[ERROR]')) {
2269
+ entries.push('<div class="log-entry error"><span class="log-label">Error</span><div class="log-content">' + escapeHtml(line.slice(8)) + '</div></div>');
2270
+ continue;
2271
+ }
2272
+
2273
+ // Default
2274
+ entries.push('<div class="log-entry system">' + escapeHtml(line) + '</div>');
2275
+ }
2276
+
2277
+ return entries.join('');
2278
+ }
2279
+
2106
2280
  function resetClaudeLog() {
2107
2281
  stopLogPolling();
2108
2282
  document.getElementById('claude-log-section').style.display = 'none';
2109
- document.getElementById('claude-log').textContent = '';
2283
+ document.getElementById('claude-log').innerHTML = '';
2110
2284
  document.getElementById('claude-log-status').className = 'claude-log-status';
2111
2285
  document.getElementById('claude-log-status').textContent = t('status.waiting');
2112
2286
  }
@@ -2118,11 +2292,10 @@ function getScript() {
2118
2292
  if (json.success && json.data) {
2119
2293
  const section = document.getElementById('claude-log-section');
2120
2294
  const log = document.getElementById('claude-log');
2121
- const status = document.getElementById('claude-log-status');
2122
2295
 
2123
2296
  if (json.data.exists || json.data.content) {
2124
2297
  section.style.display = 'block';
2125
- log.textContent = json.data.content || '';
2298
+ log.innerHTML = formatLogContent(json.data.content || '');
2126
2299
  // Auto-scroll
2127
2300
  log.scrollTop = log.scrollHeight;
2128
2301
  }
@@ -2913,6 +3086,13 @@ export function generateTicketViewPage(ticketKey, lang) {
2913
3086
  padding: 16px;
2914
3087
  border-radius: 8px;
2915
3088
  }
3089
+ .log-container {
3090
+ max-height: 70vh;
3091
+ overflow-y: auto;
3092
+ display: flex;
3093
+ flex-direction: column;
3094
+ gap: 8px;
3095
+ }
2916
3096
  .actions-bar {
2917
3097
  display: flex;
2918
3098
  gap: 12px;
@@ -3109,6 +3289,46 @@ export function generateTicketViewPage(ticketKey, lang) {
3109
3289
  margin: 0;
3110
3290
  margin-top: 12px;
3111
3291
  }
3292
+ /* Formatted log entries */
3293
+ .log-entry { margin-bottom: 8px; padding: 8px 12px; border-radius: 4px; border-left: 3px solid transparent; flex-shrink: 0; }
3294
+ .log-entry.timestamp { color: #8b949e; font-size: 11px; border-left-color: #484f58; background: transparent; padding: 4px 12px; }
3295
+ .log-entry.system { color: #8b949e; border-left-color: #484f58; background: rgba(139,148,158,0.1); }
3296
+ .log-entry.user { color: #58a6ff; border-left-color: #58a6ff; background: rgba(88,166,255,0.1); }
3297
+ .log-entry.assistant { color: #7ee787; border-left-color: #7ee787; background: rgba(126,231,135,0.1); }
3298
+ .log-entry.tool-call { color: #d2a8ff; border-left-color: #d2a8ff; background: rgba(210,168,255,0.1); padding: 12px; }
3299
+ .log-entry.tool-result { color: #ffa657; border-left-color: #ffa657; background: rgba(255,166,87,0.1); }
3300
+ .log-entry.error { color: #f85149; border-left-color: #f85149; background: rgba(248,81,73,0.1); }
3301
+ .log-entry.success { color: #7ee787; border-left-color: #7ee787; background: rgba(126,231,135,0.1); }
3302
+ .log-label { font-weight: 600; font-size: 11px; text-transform: uppercase; margin-bottom: 4px; display: block; opacity: 0.8; }
3303
+ .log-content { white-space: pre-wrap; word-break: break-word; }
3304
+
3305
+ /* Code blocks with line numbers */
3306
+ .log-code-block { background: #161b22; border-radius: 6px; overflow: hidden; margin: 8px 0; border: 1px solid #30363d; }
3307
+ .log-code-header { background: #21262d; padding: 8px 12px; font-size: 12px; color: #8b949e; border-bottom: 1px solid #30363d; display: flex; align-items: center; gap: 8px; }
3308
+ .log-code-header::before { content: ''; display: inline-block; width: 12px; height: 12px; background: #ffa657; border-radius: 50%; }
3309
+ .log-code-content { padding: 12px; overflow-x: auto; font-family: 'Fira Code', 'SF Mono', Monaco, monospace; font-size: 12px; line-height: 1.5; max-height: 400px; overflow-y: auto; }
3310
+ .log-code-line { display: flex; min-height: 20px; }
3311
+ .log-line-number { color: #484f58; text-align: right; padding-right: 16px; user-select: none; min-width: 40px; flex-shrink: 0; }
3312
+ .log-line-content { color: #c9d1d9; white-space: pre; }
3313
+
3314
+ /* Message cards */
3315
+ .log-message-card { background: #161b22; border-radius: 8px; margin: 12px 0; overflow: hidden; border: 1px solid #30363d; flex-shrink: 0; }
3316
+ .log-message-header { padding: 10px 14px; display: flex; align-items: center; gap: 8px; font-weight: 500; font-size: 13px; }
3317
+ .log-message-header.assistant-header { background: linear-gradient(135deg, #238636 0%, #2ea043 100%); color: white; }
3318
+ .log-message-header.user-header { background: linear-gradient(135deg, #1f6feb 0%, #388bfd 100%); color: white; }
3319
+ .log-message-header.tool-header { background: linear-gradient(135deg, #8b5cf6 0%, #a78bfa 100%); color: white; }
3320
+ .log-message-header.result-header { background: linear-gradient(135deg, #f97316 0%, #fb923c 100%); color: white; }
3321
+ .log-message-body { padding: 14px; border-top: 1px solid #30363d; color: #c9d1d9; white-space: pre-wrap; word-break: break-word; }
3322
+
3323
+ /* Tool badges */
3324
+ .log-tool-badge { display: inline-flex; align-items: center; gap: 6px; background: rgba(139,92,246,0.2); color: #a78bfa; padding: 4px 10px; border-radius: 12px; font-size: 12px; font-weight: 500; }
3325
+ .log-tool-input { background: #0d1117; border-radius: 4px; padding: 8px 12px; margin-top: 8px; font-family: 'Fira Code', monospace; font-size: 11px; color: #8b949e; max-height: 150px; overflow: auto; white-space: pre-wrap; }
3326
+
3327
+ /* Collapsible raw JSON */
3328
+ .log-raw-details { margin: 8px 0; }
3329
+ .log-raw-summary { cursor: pointer; color: #8b949e; font-size: 12px; padding: 8px; background: rgba(139,148,158,0.1); border-radius: 4px; }
3330
+ .log-raw-summary:hover { background: rgba(139,148,158,0.2); }
3331
+ .log-raw-json { background: #0d1117; border-radius: 4px; padding: 12px; margin-top: 8px; font-size: 11px; color: #8b949e; max-height: 200px; overflow: auto; white-space: pre-wrap; }
3112
3332
  @keyframes pulse {
3113
3333
  0%, 100% { opacity: 1; }
3114
3334
  50% { opacity: 0.5; }
@@ -3178,8 +3398,8 @@ export function generateTicketViewPage(ticketKey, lang) {
3178
3398
  ${ticket.history.map(h => {
3179
3399
  const date = new Date(h.at);
3180
3400
  const dateStr = date.toLocaleDateString('en-US', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' });
3181
- const showPromptBtn = h.to && h.action !== 'created' ? `<button class="btn-prompt" onclick="showPrompt('${escapeHtml(h.to)}')" title="View prompt">📜</button>` : '';
3182
- const showLogBtn = h.to && h.action !== 'created' ? `<button class="btn-prompt" onclick="showLog('${escapeHtml(h.to)}')" title="View terminal log">🖥️</button>` : '';
3401
+ const showPromptBtn = h.to && h.action !== 'created' ? `<a href="/ticket/${ticketKey}/${escapeHtml(h.to)}/prompt" class="btn-prompt" title="View prompt">📜</a>` : '';
3402
+ const showLogBtn = h.to && h.action !== 'created' ? `<a href="/ticket/${ticketKey}/${escapeHtml(h.to)}/terminal" class="btn-prompt" title="View terminal log">🖥️</a>` : '';
3183
3403
  return `<li class="history-item">
3184
3404
  <span class="history-action">${h.action}</span>
3185
3405
  ${h.from ? `<span class="history-from">${escapeHtml(h.from)}</span> →` : ''}
@@ -3234,7 +3454,7 @@ export function generateTicketViewPage(ticketKey, lang) {
3234
3454
  <button class="prompt-modal-close" onclick="closePromptModal()">&times;</button>
3235
3455
  </div>
3236
3456
  <div class="prompt-modal-body">
3237
- <pre id="prompt-modal-content"></pre>
3457
+ <div id="prompt-modal-content" class="log-container"></div>
3238
3458
  </div>
3239
3459
  </div>
3240
3460
  </div>
@@ -3592,6 +3812,149 @@ export function generateTicketViewPage(ticketKey, lang) {
3592
3812
  };
3593
3813
  }
3594
3814
 
3815
+ function escapeHtml(str) {
3816
+ if (typeof str !== 'string') return '';
3817
+ return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
3818
+ }
3819
+
3820
+ function formatCodeBlock(content, filename) {
3821
+ const lines = content.split(/\\\\n|\\n/);
3822
+ // Détecter le langage depuis le nom de fichier
3823
+ let lang = 'plaintext';
3824
+ if (filename) {
3825
+ const ext = filename.split('.').pop().toLowerCase();
3826
+ const langMap = {
3827
+ 'js': 'javascript', 'ts': 'typescript', 'vue': 'html', 'jsx': 'javascript',
3828
+ 'tsx': 'typescript', 'py': 'python', 'rb': 'ruby', 'java': 'java',
3829
+ 'go': 'go', 'rs': 'rust', 'cpp': 'cpp', 'c': 'c', 'h': 'c',
3830
+ 'css': 'css', 'scss': 'scss', 'html': 'html', 'json': 'json',
3831
+ 'md': 'markdown', 'sh': 'bash', 'yml': 'yaml', 'yaml': 'yaml'
3832
+ };
3833
+ lang = langMap[ext] || 'plaintext';
3834
+ }
3835
+ // Extraire le code sans les numéros de ligne pour highlight.js
3836
+ let codeLines = [];
3837
+ for (const line of lines) {
3838
+ const match = line.match(/^\\s*\\d+[→|](.*)$/);
3839
+ if (match) {
3840
+ codeLines.push(match[1]);
3841
+ } else if (line.trim()) {
3842
+ codeLines.push(line);
3843
+ }
3844
+ }
3845
+ const codeContent = codeLines.join('\\n');
3846
+ const lineCount = codeLines.length;
3847
+ const preview = filename || (lineCount + ' lignes');
3848
+
3849
+ let html = '<details class="log-code-block">';
3850
+ html += '<summary class="log-code-header"><span class="code-lang">' + lang + '</span> ' + escapeHtml(preview) + '</summary>';
3851
+ html += '<pre><code class="language-' + lang + '">' + escapeHtml(codeContent) + '</code></pre>';
3852
+ html += '</details>';
3853
+ return html;
3854
+ }
3855
+
3856
+ function formatLogContent(rawContent) {
3857
+ if (!rawContent) return '';
3858
+ // Supprimer les balises <system-reminder> et leur contenu
3859
+ let cleanedContent = rawContent.replace(/<system-reminder>[\\s\\S]*?<\\/system-reminder>/gi, '');
3860
+ const lines = cleanedContent.split('\\n');
3861
+ const entries = [];
3862
+
3863
+ for (const line of lines) {
3864
+ if (!line.trim()) continue;
3865
+
3866
+ const timestampMatch = line.match(/^\\[(\\d{4}-\\d{2}-\\d{2}T[^\\]]+)\\]\\s*(.*)$/);
3867
+ if (timestampMatch) {
3868
+ const date = new Date(timestampMatch[1]);
3869
+ const timeStr = date.toLocaleTimeString();
3870
+ entries.push('<div class="log-entry timestamp">' + timeStr + ' - ' + escapeHtml(timestampMatch[2]) + '</div>');
3871
+ continue;
3872
+ }
3873
+
3874
+ // [RAW] - Parse avec regex (pas JSON.parse car les lignes sont coupées)
3875
+ if (line.startsWith('[RAW] ')) {
3876
+ const raw = line.slice(6);
3877
+
3878
+ // Ignorer les lignes de continuation (ne commencent pas par {)
3879
+ if (!raw.startsWith('{')) {
3880
+ continue;
3881
+ }
3882
+
3883
+ // Extraire tool_result content
3884
+ if (raw.includes('"type":"tool_result"')) {
3885
+ const contentStart = raw.indexOf('"content":"');
3886
+ if (contentStart !== -1) {
3887
+ // Extraire le contenu après "content":"
3888
+ let content = raw.slice(contentStart + 11);
3889
+ // Enlever le reste du JSON (approximatif car tronqué)
3890
+ const endQuote = content.lastIndexOf('"');
3891
+ if (endQuote > 0) content = content.slice(0, endQuote);
3892
+ // Décoder les échappements
3893
+ const decoded = content.replace(/\\\\n/g, '\\n').replace(/\\\\"/g, '"').replace(/\\\\t/g, '\\t');
3894
+ // Vérifier si c'est du code avec numéros de ligne
3895
+ if (/^\\s*\\d+[→|]/.test(decoded)) {
3896
+ entries.push('<div class="log-message-card"><div class="log-message-header result-header">Tool Result</div><div class="log-message-body">' + formatCodeBlock(decoded) + '</div></div>');
3897
+ } else {
3898
+ // Résultat texte simple (liste de fichiers, etc.)
3899
+ const lines = decoded.split('\\n').slice(0, 20); // Limiter à 20 lignes
3900
+ const truncated = decoded.split('\\n').length > 20 ? '<div style="color:#8b949e;font-style:italic">... (truncated)</div>' : '';
3901
+ entries.push('<div class="log-message-card"><div class="log-message-header result-header">Tool Result</div><div class="log-message-body" style="font-family:monospace;font-size:12px;white-space:pre-wrap;max-height:200px;overflow-y:auto">' + escapeHtml(lines.join('\\n')) + truncated + '</div></div>');
3902
+ }
3903
+ continue;
3904
+ }
3905
+ }
3906
+
3907
+ // Extraire message assistant texte
3908
+ const textMatch = raw.match(/"type":"text","text":"([^"]+)"/);
3909
+ if (textMatch) {
3910
+ const decoded = textMatch[1].replace(/\\\\n/g, '\\n').replace(/\\\\"/g, '"');
3911
+ entries.push('<div class="log-message-card"><div class="log-message-header assistant-header">Assistant</div><div class="log-message-body">' + escapeHtml(decoded) + '</div></div>');
3912
+ continue;
3913
+ }
3914
+
3915
+ // Extraire tool_use
3916
+ const toolMatch = raw.match(/"type":"tool_use"[^}]*"name":"([^"]+)"/);
3917
+ if (toolMatch) {
3918
+ entries.push('<div class="log-entry tool-call"><span class="log-tool-badge">' + escapeHtml(toolMatch[1]) + '</span></div>');
3919
+ continue;
3920
+ }
3921
+
3922
+ // Ignorer les autres RAW
3923
+ continue;
3924
+ }
3925
+
3926
+ if (line.startsWith('[SYSTEM]')) {
3927
+ entries.push('<div class="log-entry system"><span class="log-label">System</span><div class="log-content">' + escapeHtml(line.slice(9)) + '</div></div>');
3928
+ continue;
3929
+ }
3930
+ if (line.startsWith('[ASSISTANT]')) {
3931
+ entries.push('<div class="log-message-card"><div class="log-message-header assistant-header">Assistant</div><div class="log-message-body">' + escapeHtml(line.slice(12)) + '</div></div>');
3932
+ continue;
3933
+ }
3934
+ if (line.startsWith('[TOOL]')) {
3935
+ entries.push('<div class="log-entry tool-call"><span class="log-tool-badge">' + escapeHtml(line.slice(7)) + '</span></div>');
3936
+ continue;
3937
+ }
3938
+ if (line.startsWith('[RESULT]')) {
3939
+ const content = line.slice(9);
3940
+ if (/^\\s*\\d+[→|]/.test(content)) {
3941
+ entries.push('<div class="log-message-card"><div class="log-message-header result-header">Result</div><div class="log-message-body">' + formatCodeBlock(content) + '</div></div>');
3942
+ } else {
3943
+ entries.push('<div class="log-message-card"><div class="log-message-header result-header">Result</div><div class="log-message-body">' + escapeHtml(content.slice(0, 1000)) + (content.length > 1000 ? '...' : '') + '</div></div>');
3944
+ }
3945
+ continue;
3946
+ }
3947
+ if (line.startsWith('[ERROR]')) {
3948
+ entries.push('<div class="log-entry error"><span class="log-label">Error</span><div class="log-content">' + escapeHtml(line.slice(8)) + '</div></div>');
3949
+ continue;
3950
+ }
3951
+
3952
+ entries.push('<div class="log-entry system">' + escapeHtml(line) + '</div>');
3953
+ }
3954
+
3955
+ return entries.join('');
3956
+ }
3957
+
3595
3958
  function startLogPolling() {
3596
3959
  stopLogPolling();
3597
3960
  logPollingInterval = setInterval(fetchLog, 1000);
@@ -3612,10 +3975,10 @@ export function generateTicketViewPage(ticketKey, lang) {
3612
3975
  const log = document.getElementById('claude-log');
3613
3976
 
3614
3977
  if (json.success && json.data && (json.data.exists || json.data.content)) {
3615
- log.textContent = json.data.content || '';
3978
+ log.innerHTML = formatLogContent(json.data.content || '');
3616
3979
  log.scrollTop = log.scrollHeight;
3617
3980
  } else {
3618
- log.textContent = t('ticketView.noLog');
3981
+ log.innerHTML = '<div class="log-entry system">' + t('ticketView.noLog') + '</div>';
3619
3982
  }
3620
3983
  } catch (e) {
3621
3984
  console.error('Log fetch error:', e);
@@ -3688,7 +4051,11 @@ export function generateTicketViewPage(ticketKey, lang) {
3688
4051
  const json = await res.json();
3689
4052
  if (json.success) {
3690
4053
  title.textContent = 'Terminal → ' + columnSlug;
3691
- content.textContent = json.data.content || t('ticketView.noLog') || 'No log available';
4054
+ if (json.data.content) {
4055
+ content.innerHTML = formatLogContent(json.data.content);
4056
+ } else {
4057
+ content.textContent = t('ticketView.noLog') || 'No log available';
4058
+ }
3692
4059
  } else {
3693
4060
  title.textContent = t('ticketView.logError') || 'Log Error';
3694
4061
  content.textContent = json.error || 'Error loading log';
@@ -3709,4 +4076,771 @@ export function generateTicketViewPage(ticketKey, lang) {
3709
4076
  </body>
3710
4077
  </html>`;
3711
4078
  }
4079
+ /**
4080
+ * Generate the column terminal page HTML
4081
+ */
4082
+ export function generateColumnTerminalPage(ticketKey, columnSlug, lang) {
4083
+ const config = getConfig();
4084
+ const ticket = getTicket(config.root, ticketKey);
4085
+ // 404 page if ticket not found
4086
+ if (!ticket) {
4087
+ return `<!DOCTYPE html>
4088
+ <html lang="${lang}">
4089
+ <head>
4090
+ <meta charset="UTF-8">
4091
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
4092
+ <title>Ticket Not Found - AutoCode</title>
4093
+ <style>
4094
+ :root {
4095
+ --bg: #0a0a0f;
4096
+ --text: #f1f5f9;
4097
+ --accent: #6366f1;
4098
+ }
4099
+ * { margin: 0; padding: 0; box-sizing: border-box; }
4100
+ body {
4101
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
4102
+ background: var(--bg);
4103
+ color: var(--text);
4104
+ min-height: 100vh;
4105
+ display: flex;
4106
+ align-items: center;
4107
+ justify-content: center;
4108
+ }
4109
+ .not-found {
4110
+ text-align: center;
4111
+ padding: 48px;
4112
+ }
4113
+ h1 { font-size: 2rem; margin-bottom: 16px; }
4114
+ p { color: #94a3b8; margin-bottom: 24px; }
4115
+ a {
4116
+ display: inline-block;
4117
+ background: var(--accent);
4118
+ color: white;
4119
+ padding: 12px 24px;
4120
+ border-radius: 8px;
4121
+ text-decoration: none;
4122
+ font-weight: 500;
4123
+ }
4124
+ a:hover { opacity: 0.9; }
4125
+ </style>
4126
+ </head>
4127
+ <body>
4128
+ <div class="not-found">
4129
+ <h1>Ticket Not Found</h1>
4130
+ <p>Ticket ${escapeHtml(ticketKey)} does not exist or has been archived.</p>
4131
+ <a href="/">← Back to Dashboard</a>
4132
+ </div>
4133
+ </body>
4134
+ </html>`;
4135
+ }
4136
+ return `<!DOCTYPE html>
4137
+ <html lang="${lang}">
4138
+ <head>
4139
+ <meta charset="UTF-8">
4140
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
4141
+ <title>Terminal → ${escapeHtml(columnSlug)} | ${ticketKey} - AutoCode</title>
4142
+ <style>
4143
+ :root {
4144
+ --bg: #0a0a0f;
4145
+ --bg-secondary: #12121a;
4146
+ --bg-tertiary: #1a1a24;
4147
+ --text: #f1f5f9;
4148
+ --muted: #94a3b8;
4149
+ --border: #2a2a3a;
4150
+ --accent: #6366f1;
4151
+ --blue: #4dabf7;
4152
+ --green: #4ade80;
4153
+ --yellow: #facc15;
4154
+ --orange: #fb923c;
4155
+ --red: #f87171;
4156
+ --purple: #a78bfa;
4157
+ }
4158
+ * { margin: 0; padding: 0; box-sizing: border-box; }
4159
+ body {
4160
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
4161
+ background: var(--bg);
4162
+ color: var(--text);
4163
+ min-height: 100vh;
4164
+ display: flex;
4165
+ flex-direction: column;
4166
+ }
4167
+ .header {
4168
+ display: flex;
4169
+ align-items: center;
4170
+ gap: 24px;
4171
+ padding: 16px 24px;
4172
+ background: var(--bg-secondary);
4173
+ border-bottom: 1px solid var(--border);
4174
+ position: sticky;
4175
+ top: 0;
4176
+ z-index: 100;
4177
+ }
4178
+ .back-btn {
4179
+ color: var(--muted);
4180
+ text-decoration: none;
4181
+ font-size: 14px;
4182
+ display: flex;
4183
+ align-items: center;
4184
+ gap: 8px;
4185
+ padding: 8px 16px;
4186
+ background: var(--bg-tertiary);
4187
+ border-radius: 6px;
4188
+ transition: all 0.2s;
4189
+ }
4190
+ .back-btn:hover { color: var(--text); background: var(--border); }
4191
+ .ticket-key {
4192
+ font-family: 'SF Mono', Monaco, monospace;
4193
+ font-size: 12px;
4194
+ color: var(--accent);
4195
+ background: rgba(99, 102, 241, 0.15);
4196
+ padding: 4px 10px;
4197
+ border-radius: 4px;
4198
+ font-weight: 600;
4199
+ }
4200
+ .column-slug {
4201
+ font-family: 'SF Mono', Monaco, monospace;
4202
+ font-size: 14px;
4203
+ color: var(--green);
4204
+ background: rgba(74, 222, 128, 0.15);
4205
+ padding: 6px 12px;
4206
+ border-radius: 4px;
4207
+ font-weight: 500;
4208
+ }
4209
+ .page-title {
4210
+ flex: 1;
4211
+ font-size: 18px;
4212
+ font-weight: 600;
4213
+ display: flex;
4214
+ align-items: center;
4215
+ gap: 12px;
4216
+ }
4217
+ .main {
4218
+ flex: 1;
4219
+ padding: 24px;
4220
+ display: flex;
4221
+ flex-direction: column;
4222
+ }
4223
+ .terminal-container {
4224
+ flex: 1;
4225
+ background: var(--bg-secondary);
4226
+ border: 1px solid var(--border);
4227
+ border-radius: 12px;
4228
+ display: flex;
4229
+ flex-direction: column;
4230
+ overflow: hidden;
4231
+ }
4232
+ .terminal-header {
4233
+ display: flex;
4234
+ align-items: center;
4235
+ gap: 12px;
4236
+ padding: 12px 16px;
4237
+ background: var(--bg-tertiary);
4238
+ border-bottom: 1px solid var(--border);
4239
+ }
4240
+ .terminal-status {
4241
+ font-size: 12px;
4242
+ padding: 4px 12px;
4243
+ border-radius: 12px;
4244
+ font-weight: 500;
4245
+ }
4246
+ .terminal-status.idle { background: var(--bg-secondary); color: var(--muted); }
4247
+ .terminal-status.processing { background: rgba(251, 146, 60, 0.15); color: var(--orange); }
4248
+ .terminal-status.success { background: rgba(74, 222, 128, 0.15); color: var(--green); }
4249
+ .terminal-status.error { background: rgba(248, 113, 113, 0.15); color: var(--red); }
4250
+ .terminal-log {
4251
+ flex: 1;
4252
+ padding: 16px;
4253
+ overflow-y: auto;
4254
+ font-family: 'SF Mono', Monaco, Consolas, monospace;
4255
+ font-size: 13px;
4256
+ line-height: 1.6;
4257
+ }
4258
+
4259
+ /* Log entry styles */
4260
+ .log-entry {
4261
+ padding: 4px 0;
4262
+ flex-shrink: 0;
4263
+ }
4264
+ .log-entry.timestamp { color: var(--muted); font-size: 11px; }
4265
+ .log-entry.system { color: var(--blue); }
4266
+ .log-entry.error { color: var(--red); }
4267
+ .log-entry.tool-call { margin: 8px 0; }
4268
+ .log-label {
4269
+ display: inline-block;
4270
+ padding: 2px 8px;
4271
+ border-radius: 4px;
4272
+ font-size: 11px;
4273
+ font-weight: 600;
4274
+ margin-right: 8px;
4275
+ text-transform: uppercase;
4276
+ }
4277
+ .log-entry.system .log-label { background: rgba(77, 171, 247, 0.15); color: var(--blue); }
4278
+ .log-entry.error .log-label { background: rgba(248, 113, 113, 0.15); color: var(--red); }
4279
+ .log-tool-badge {
4280
+ display: inline-block;
4281
+ background: rgba(163, 139, 250, 0.15);
4282
+ color: var(--purple);
4283
+ padding: 4px 10px;
4284
+ border-radius: 4px;
4285
+ font-size: 12px;
4286
+ font-weight: 500;
4287
+ }
4288
+ .log-content { display: inline; }
4289
+
4290
+ /* Message cards */
4291
+ .log-message-card {
4292
+ background: var(--bg-tertiary);
4293
+ border: 1px solid var(--border);
4294
+ border-radius: 8px;
4295
+ margin: 12px 0;
4296
+ overflow: hidden;
4297
+ flex-shrink: 0;
4298
+ }
4299
+ .log-message-header {
4300
+ padding: 8px 12px;
4301
+ font-size: 11px;
4302
+ font-weight: 600;
4303
+ text-transform: uppercase;
4304
+ letter-spacing: 0.5px;
4305
+ }
4306
+ .assistant-header { background: rgba(99, 102, 241, 0.15); color: var(--accent); }
4307
+ .result-header { background: rgba(74, 222, 128, 0.15); color: var(--green); }
4308
+ .log-message-body {
4309
+ padding: 12px;
4310
+ line-height: 1.6;
4311
+ white-space: pre-wrap;
4312
+ word-break: break-word;
4313
+ }
4314
+
4315
+ /* Code blocks - collapsible */
4316
+ .log-code-block {
4317
+ background: #0d1117;
4318
+ border: 1px solid var(--border);
4319
+ border-radius: 6px;
4320
+ overflow: hidden;
4321
+ margin: 8px 0;
4322
+ }
4323
+ .log-code-block summary.log-code-header {
4324
+ background: #161b22;
4325
+ padding: 10px 14px;
4326
+ font-size: 12px;
4327
+ color: var(--muted);
4328
+ cursor: pointer;
4329
+ display: flex;
4330
+ align-items: center;
4331
+ gap: 8px;
4332
+ user-select: none;
4333
+ list-style: none;
4334
+ }
4335
+ .log-code-block summary.log-code-header::-webkit-details-marker { display: none; }
4336
+ .log-code-block summary.log-code-header::before {
4337
+ content: '▶';
4338
+ font-size: 10px;
4339
+ transition: transform 0.2s;
4340
+ }
4341
+ .log-code-block[open] summary.log-code-header::before {
4342
+ transform: rotate(90deg);
4343
+ }
4344
+ .log-code-block .code-lang {
4345
+ background: var(--accent);
4346
+ color: white;
4347
+ padding: 2px 6px;
4348
+ border-radius: 4px;
4349
+ font-size: 10px;
4350
+ font-weight: 600;
4351
+ text-transform: uppercase;
4352
+ }
4353
+ .log-code-block pre {
4354
+ margin: 0;
4355
+ padding: 0;
4356
+ background: transparent;
4357
+ }
4358
+ .log-code-block code {
4359
+ display: block;
4360
+ padding: 14px;
4361
+ overflow-x: auto;
4362
+ font-family: 'Fira Code', 'SF Mono', Monaco, monospace;
4363
+ font-size: 13px;
4364
+ line-height: 1.6;
4365
+ max-height: 500px;
4366
+ overflow-y: auto;
4367
+ }
4368
+
4369
+ .no-log {
4370
+ color: var(--muted);
4371
+ text-align: center;
4372
+ padding: 48px;
4373
+ font-size: 14px;
4374
+ }
4375
+ </style>
4376
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
4377
+ </head>
4378
+ <body>
4379
+ <header class="header">
4380
+ <a href="/ticket/${ticketKey}" class="back-btn">← Retour</a>
4381
+ <span class="ticket-key">${ticketKey}</span>
4382
+ <div class="page-title">
4383
+ <span>Terminal →</span>
4384
+ <span class="column-slug">${escapeHtml(columnSlug)}</span>
4385
+ </div>
4386
+ </header>
4387
+
4388
+ <main class="main">
4389
+ <div class="terminal-container">
4390
+ <div class="terminal-header">
4391
+ <span class="terminal-status idle" id="terminal-status">En attente</span>
4392
+ </div>
4393
+ <div class="terminal-log" id="terminal-log">
4394
+ <div class="no-log">Chargement...</div>
4395
+ </div>
4396
+ </div>
4397
+ </main>
4398
+
4399
+ <script>
4400
+ const TICKET_KEY = '${ticketKey}';
4401
+ const COLUMN_SLUG = '${escapeHtml(columnSlug)}';
4402
+
4403
+ function escapeHtml(str) {
4404
+ if (typeof str !== 'string') return '';
4405
+ return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
4406
+ }
4407
+
4408
+ function formatCodeBlock(content, filename) {
4409
+ const lines = content.split(/\\\\n|\\n/);
4410
+ // Détecter le langage depuis le nom de fichier
4411
+ let lang = 'plaintext';
4412
+ if (filename) {
4413
+ const ext = filename.split('.').pop().toLowerCase();
4414
+ const langMap = {
4415
+ 'js': 'javascript', 'ts': 'typescript', 'vue': 'html', 'jsx': 'javascript',
4416
+ 'tsx': 'typescript', 'py': 'python', 'rb': 'ruby', 'java': 'java',
4417
+ 'go': 'go', 'rs': 'rust', 'cpp': 'cpp', 'c': 'c', 'h': 'c',
4418
+ 'css': 'css', 'scss': 'scss', 'html': 'html', 'json': 'json',
4419
+ 'md': 'markdown', 'sh': 'bash', 'yml': 'yaml', 'yaml': 'yaml'
4420
+ };
4421
+ lang = langMap[ext] || 'plaintext';
4422
+ }
4423
+ // Extraire le code sans les numéros de ligne pour highlight.js
4424
+ let codeLines = [];
4425
+ for (const line of lines) {
4426
+ const match = line.match(/^\\s*\\d+[→|](.*)$/);
4427
+ if (match) {
4428
+ codeLines.push(match[1]);
4429
+ } else if (line.trim()) {
4430
+ codeLines.push(line);
4431
+ }
4432
+ }
4433
+ const codeContent = codeLines.join('\\n');
4434
+ const lineCount = codeLines.length;
4435
+ const preview = filename || (lineCount + ' lignes');
4436
+
4437
+ let html = '<details class="log-code-block">';
4438
+ html += '<summary class="log-code-header"><span class="code-lang">' + lang + '</span> ' + escapeHtml(preview) + '</summary>';
4439
+ html += '<pre><code class="language-' + lang + '">' + escapeHtml(codeContent) + '</code></pre>';
4440
+ html += '</details>';
4441
+ return html;
4442
+ }
4443
+
4444
+ function formatLogContent(rawContent) {
4445
+ if (!rawContent) return '';
4446
+ // Supprimer les balises <system-reminder> et leur contenu
4447
+ let cleanedContent = rawContent.replace(/<system-reminder>[\\s\\S]*?<\\/system-reminder>/gi, '');
4448
+ const lines = cleanedContent.split('\\n');
4449
+ const entries = [];
4450
+
4451
+ for (const line of lines) {
4452
+ if (!line.trim()) continue;
4453
+
4454
+ const timestampMatch = line.match(/^\\[(\\d{4}-\\d{2}-\\d{2}T[^\\]]+)\\]\\s*(.*)$/);
4455
+ if (timestampMatch) {
4456
+ const date = new Date(timestampMatch[1]);
4457
+ const timeStr = date.toLocaleTimeString();
4458
+ entries.push('<div class="log-entry timestamp">' + timeStr + ' - ' + escapeHtml(timestampMatch[2]) + '</div>');
4459
+ continue;
4460
+ }
4461
+
4462
+ if (line.startsWith('[RAW] ')) {
4463
+ const raw = line.slice(6);
4464
+ if (!raw.startsWith('{')) continue;
4465
+
4466
+ if (raw.includes('"type":"tool_result"')) {
4467
+ const contentStart = raw.indexOf('"content":"');
4468
+ if (contentStart !== -1) {
4469
+ let content = raw.slice(contentStart + 11);
4470
+ const endQuote = content.lastIndexOf('"');
4471
+ if (endQuote > 0) content = content.slice(0, endQuote);
4472
+ const decoded = content.replace(/\\\\n/g, '\\n').replace(/\\\\"/g, '"').replace(/\\\\t/g, '\\t');
4473
+ if (/^\\s*\\d+[→|]/.test(decoded)) {
4474
+ entries.push('<div class="log-message-card"><div class="log-message-header result-header">Tool Result</div><div class="log-message-body">' + formatCodeBlock(decoded) + '</div></div>');
4475
+ } else {
4476
+ const lines = decoded.split('\\n').slice(0, 20);
4477
+ const truncated = decoded.split('\\n').length > 20 ? '<div style="color:#8b949e;font-style:italic">... (truncated)</div>' : '';
4478
+ entries.push('<div class="log-message-card"><div class="log-message-header result-header">Tool Result</div><div class="log-message-body" style="font-family:monospace;font-size:12px;white-space:pre-wrap;max-height:200px;overflow-y:auto">' + escapeHtml(lines.join('\\n')) + truncated + '</div></div>');
4479
+ }
4480
+ continue;
4481
+ }
4482
+ }
4483
+
4484
+ const textMatch = raw.match(/"type":"text","text":"([^"]+)"/);
4485
+ if (textMatch) {
4486
+ const decoded = textMatch[1].replace(/\\\\n/g, '\\n').replace(/\\\\"/g, '"');
4487
+ entries.push('<div class="log-message-card"><div class="log-message-header assistant-header">Assistant</div><div class="log-message-body">' + escapeHtml(decoded) + '</div></div>');
4488
+ continue;
4489
+ }
4490
+
4491
+ const toolMatch = raw.match(/"type":"tool_use"[^}]*"name":"([^"]+)"/);
4492
+ if (toolMatch) {
4493
+ entries.push('<div class="log-entry tool-call"><span class="log-tool-badge">' + escapeHtml(toolMatch[1]) + '</span></div>');
4494
+ continue;
4495
+ }
4496
+ continue;
4497
+ }
4498
+
4499
+ if (line.startsWith('[SYSTEM]')) {
4500
+ entries.push('<div class="log-entry system"><span class="log-label">System</span><div class="log-content">' + escapeHtml(line.slice(9)) + '</div></div>');
4501
+ continue;
4502
+ }
4503
+ if (line.startsWith('[ASSISTANT]')) {
4504
+ entries.push('<div class="log-message-card"><div class="log-message-header assistant-header">Assistant</div><div class="log-message-body">' + escapeHtml(line.slice(12)) + '</div></div>');
4505
+ continue;
4506
+ }
4507
+ if (line.startsWith('[TOOL]')) {
4508
+ entries.push('<div class="log-entry tool-call"><span class="log-tool-badge">' + escapeHtml(line.slice(7)) + '</span></div>');
4509
+ continue;
4510
+ }
4511
+ if (line.startsWith('[RESULT]')) {
4512
+ const content = line.slice(9);
4513
+ if (/^\\s*\\d+[→|]/.test(content)) {
4514
+ entries.push('<div class="log-message-card"><div class="log-message-header result-header">Result</div><div class="log-message-body">' + formatCodeBlock(content) + '</div></div>');
4515
+ } else {
4516
+ entries.push('<div class="log-message-card"><div class="log-message-header result-header">Result</div><div class="log-message-body">' + escapeHtml(content.slice(0, 1000)) + (content.length > 1000 ? '...' : '') + '</div></div>');
4517
+ }
4518
+ continue;
4519
+ }
4520
+ if (line.startsWith('[ERROR]')) {
4521
+ entries.push('<div class="log-entry error"><span class="log-label">Error</span><div class="log-content">' + escapeHtml(line.slice(8)) + '</div></div>');
4522
+ continue;
4523
+ }
4524
+
4525
+ entries.push('<div class="log-entry system">' + escapeHtml(line) + '</div>');
4526
+ }
4527
+
4528
+ return entries.join('');
4529
+ }
4530
+
4531
+ async function fetchLog() {
4532
+ try {
4533
+ const res = await fetch('/api/tickets/' + TICKET_KEY + '/log/' + COLUMN_SLUG);
4534
+ const json = await res.json();
4535
+ const log = document.getElementById('terminal-log');
4536
+ const status = document.getElementById('terminal-status');
4537
+
4538
+ if (json.success && json.data && json.data.content) {
4539
+ log.innerHTML = formatLogContent(json.data.content);
4540
+ // Appliquer le syntax highlighting
4541
+ if (window.hljs) {
4542
+ log.querySelectorAll('pre code').forEach((block) => {
4543
+ hljs.highlightElement(block);
4544
+ });
4545
+ }
4546
+ log.scrollTop = log.scrollHeight;
4547
+ status.className = 'terminal-status success';
4548
+ status.textContent = 'Terminé';
4549
+ } else {
4550
+ log.innerHTML = '<div class="no-log">Aucun log disponible pour cette colonne.</div>';
4551
+ status.className = 'terminal-status idle';
4552
+ status.textContent = 'Aucun log';
4553
+ }
4554
+ } catch (e) {
4555
+ console.error('Log fetch error:', e);
4556
+ document.getElementById('terminal-log').innerHTML = '<div class="no-log">Erreur de chargement.</div>';
4557
+ }
4558
+ }
4559
+
4560
+ // WebSocket for live updates
4561
+ let ws = null;
4562
+ function connectWebSocket() {
4563
+ const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
4564
+ ws = new WebSocket(protocol + '//' + location.host + '/ws');
4565
+
4566
+ ws.onmessage = (event) => {
4567
+ try {
4568
+ const msg = JSON.parse(event.data);
4569
+ if (msg.type === 'claude:start' && msg.ticketKey === TICKET_KEY) {
4570
+ document.getElementById('terminal-status').className = 'terminal-status processing';
4571
+ document.getElementById('terminal-status').textContent = 'En cours...';
4572
+ }
4573
+ if (msg.type === 'claude:end' && msg.ticketKey === TICKET_KEY) {
4574
+ fetchLog();
4575
+ }
4576
+ if (msg.type === 'ticket:updated' && msg.ticketKey === TICKET_KEY) {
4577
+ fetchLog();
4578
+ }
4579
+ } catch (e) {
4580
+ console.error('WebSocket error:', e);
4581
+ }
4582
+ };
4583
+
4584
+ ws.onclose = () => {
4585
+ setTimeout(connectWebSocket, 3000);
4586
+ };
4587
+ }
4588
+
4589
+ // Init
4590
+ fetchLog();
4591
+ connectWebSocket();
4592
+ </script>
4593
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
4594
+ </body>
4595
+ </html>`;
4596
+ }
4597
+ /**
4598
+ * Generate the column prompt page HTML
4599
+ */
4600
+ export function generateColumnPromptPage(ticketKey, columnSlug, lang) {
4601
+ const config = getConfig();
4602
+ const ticket = getTicket(config.root, ticketKey);
4603
+ // 404 page if ticket not found
4604
+ if (!ticket) {
4605
+ return `<!DOCTYPE html>
4606
+ <html lang="${lang}">
4607
+ <head>
4608
+ <meta charset="UTF-8">
4609
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
4610
+ <title>Ticket Not Found - AutoCode</title>
4611
+ <style>
4612
+ :root {
4613
+ --bg: #0a0a0f;
4614
+ --text: #f1f5f9;
4615
+ --accent: #6366f1;
4616
+ }
4617
+ * { margin: 0; padding: 0; box-sizing: border-box; }
4618
+ body {
4619
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
4620
+ background: var(--bg);
4621
+ color: var(--text);
4622
+ min-height: 100vh;
4623
+ display: flex;
4624
+ align-items: center;
4625
+ justify-content: center;
4626
+ }
4627
+ .not-found {
4628
+ text-align: center;
4629
+ padding: 48px;
4630
+ }
4631
+ h1 { font-size: 2rem; margin-bottom: 16px; }
4632
+ p { color: #94a3b8; margin-bottom: 24px; }
4633
+ a {
4634
+ display: inline-block;
4635
+ background: var(--accent);
4636
+ color: white;
4637
+ padding: 12px 24px;
4638
+ border-radius: 8px;
4639
+ text-decoration: none;
4640
+ font-weight: 500;
4641
+ }
4642
+ a:hover { opacity: 0.9; }
4643
+ </style>
4644
+ </head>
4645
+ <body>
4646
+ <div class="not-found">
4647
+ <h1>Ticket Not Found</h1>
4648
+ <p>Ticket ${escapeHtml(ticketKey)} does not exist or has been archived.</p>
4649
+ <a href="/">← Back to Dashboard</a>
4650
+ </div>
4651
+ </body>
4652
+ </html>`;
4653
+ }
4654
+ return `<!DOCTYPE html>
4655
+ <html lang="${lang}">
4656
+ <head>
4657
+ <meta charset="UTF-8">
4658
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
4659
+ <title>Prompt → ${escapeHtml(columnSlug)} | ${ticketKey} - AutoCode</title>
4660
+ <style>
4661
+ :root {
4662
+ --bg: #0a0a0f;
4663
+ --bg-secondary: #12121a;
4664
+ --bg-tertiary: #1a1a24;
4665
+ --text: #f1f5f9;
4666
+ --muted: #94a3b8;
4667
+ --border: #2a2a3a;
4668
+ --accent: #6366f1;
4669
+ --blue: #4dabf7;
4670
+ --green: #4ade80;
4671
+ --yellow: #facc15;
4672
+ --orange: #fb923c;
4673
+ --red: #f87171;
4674
+ --purple: #a78bfa;
4675
+ }
4676
+ * { margin: 0; padding: 0; box-sizing: border-box; }
4677
+ body {
4678
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
4679
+ background: var(--bg);
4680
+ color: var(--text);
4681
+ min-height: 100vh;
4682
+ display: flex;
4683
+ flex-direction: column;
4684
+ }
4685
+ .header {
4686
+ display: flex;
4687
+ align-items: center;
4688
+ gap: 24px;
4689
+ padding: 16px 24px;
4690
+ background: var(--bg-secondary);
4691
+ border-bottom: 1px solid var(--border);
4692
+ position: sticky;
4693
+ top: 0;
4694
+ z-index: 100;
4695
+ }
4696
+ .back-btn {
4697
+ color: var(--muted);
4698
+ text-decoration: none;
4699
+ font-size: 14px;
4700
+ display: flex;
4701
+ align-items: center;
4702
+ gap: 8px;
4703
+ padding: 8px 16px;
4704
+ background: var(--bg-tertiary);
4705
+ border-radius: 6px;
4706
+ transition: all 0.2s;
4707
+ }
4708
+ .back-btn:hover { color: var(--text); background: var(--border); }
4709
+ .ticket-key {
4710
+ font-family: 'SF Mono', Monaco, monospace;
4711
+ font-size: 12px;
4712
+ color: var(--accent);
4713
+ background: rgba(99, 102, 241, 0.15);
4714
+ padding: 4px 10px;
4715
+ border-radius: 4px;
4716
+ font-weight: 600;
4717
+ }
4718
+ .column-slug {
4719
+ font-family: 'SF Mono', Monaco, monospace;
4720
+ font-size: 14px;
4721
+ color: var(--yellow);
4722
+ background: rgba(250, 204, 21, 0.15);
4723
+ padding: 6px 12px;
4724
+ border-radius: 4px;
4725
+ font-weight: 500;
4726
+ }
4727
+ .page-title {
4728
+ flex: 1;
4729
+ font-size: 18px;
4730
+ font-weight: 600;
4731
+ display: flex;
4732
+ align-items: center;
4733
+ gap: 12px;
4734
+ }
4735
+ .main {
4736
+ flex: 1;
4737
+ padding: 24px;
4738
+ display: flex;
4739
+ flex-direction: column;
4740
+ }
4741
+ .prompt-container {
4742
+ flex: 1;
4743
+ background: var(--bg-secondary);
4744
+ border: 1px solid var(--border);
4745
+ border-radius: 12px;
4746
+ display: flex;
4747
+ flex-direction: column;
4748
+ overflow: hidden;
4749
+ }
4750
+ .prompt-header {
4751
+ display: flex;
4752
+ align-items: center;
4753
+ gap: 12px;
4754
+ padding: 12px 16px;
4755
+ background: var(--bg-tertiary);
4756
+ border-bottom: 1px solid var(--border);
4757
+ }
4758
+ .prompt-status {
4759
+ font-size: 12px;
4760
+ padding: 4px 12px;
4761
+ border-radius: 12px;
4762
+ font-weight: 500;
4763
+ }
4764
+ .prompt-status.loaded { background: rgba(74, 222, 128, 0.15); color: var(--green); }
4765
+ .prompt-status.loading { background: rgba(251, 146, 60, 0.15); color: var(--orange); }
4766
+ .prompt-status.error { background: rgba(248, 113, 113, 0.15); color: var(--red); }
4767
+ .prompt-content {
4768
+ flex: 1;
4769
+ padding: 24px;
4770
+ overflow-y: auto;
4771
+ font-family: 'SF Mono', Monaco, Consolas, monospace;
4772
+ font-size: 14px;
4773
+ line-height: 1.8;
4774
+ white-space: pre-wrap;
4775
+ word-break: break-word;
4776
+ }
4777
+ .no-prompt {
4778
+ color: var(--muted);
4779
+ text-align: center;
4780
+ padding: 48px;
4781
+ font-size: 14px;
4782
+ }
4783
+ </style>
4784
+ </head>
4785
+ <body>
4786
+ <header class="header">
4787
+ <a href="/ticket/${ticketKey}" class="back-btn">← Retour</a>
4788
+ <span class="ticket-key">${ticketKey}</span>
4789
+ <div class="page-title">
4790
+ <span>Prompt →</span>
4791
+ <span class="column-slug">${escapeHtml(columnSlug)}</span>
4792
+ </div>
4793
+ </header>
4794
+
4795
+ <main class="main">
4796
+ <div class="prompt-container">
4797
+ <div class="prompt-header">
4798
+ <span class="prompt-status loading" id="prompt-status">Chargement...</span>
4799
+ </div>
4800
+ <div class="prompt-content" id="prompt-content">
4801
+ <div class="no-prompt">Chargement du prompt...</div>
4802
+ </div>
4803
+ </div>
4804
+ </main>
4805
+
4806
+ <script>
4807
+ const TICKET_KEY = '${ticketKey}';
4808
+ const COLUMN_SLUG = '${escapeHtml(columnSlug)}';
4809
+
4810
+ function escapeHtml(str) {
4811
+ if (typeof str !== 'string') return '';
4812
+ return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
4813
+ }
4814
+
4815
+ async function fetchPrompt() {
4816
+ const content = document.getElementById('prompt-content');
4817
+ const status = document.getElementById('prompt-status');
4818
+
4819
+ try {
4820
+ const res = await fetch('/api/tickets/' + TICKET_KEY + '/prompt/' + COLUMN_SLUG);
4821
+ const json = await res.json();
4822
+
4823
+ if (json.success && json.data && json.data.prompt) {
4824
+ content.textContent = json.data.prompt;
4825
+ status.className = 'prompt-status loaded';
4826
+ status.textContent = 'Chargé';
4827
+ } else {
4828
+ content.innerHTML = '<div class="no-prompt">Aucun prompt disponible pour cette colonne.</div>';
4829
+ status.className = 'prompt-status error';
4830
+ status.textContent = 'Non disponible';
4831
+ }
4832
+ } catch (e) {
4833
+ console.error('Prompt fetch error:', e);
4834
+ content.innerHTML = '<div class="no-prompt">Erreur de chargement.</div>';
4835
+ status.className = 'prompt-status error';
4836
+ status.textContent = 'Erreur';
4837
+ }
4838
+ }
4839
+
4840
+ // Init
4841
+ fetchPrompt();
4842
+ </script>
4843
+ </body>
4844
+ </html>`;
4845
+ }
3712
4846
  //# sourceMappingURL=dashboard.js.map