@tarunspandit/codexflow 0.29.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.
Files changed (92) hide show
  1. package/AGENTS.example.md +18 -0
  2. package/CHANGELOG.md +311 -0
  3. package/CHATGPT_PROMPT.md +12 -0
  4. package/CODEX_PROMPT.md +12 -0
  5. package/CONTRIBUTING.md +51 -0
  6. package/DOMAIN_SETUP.md +241 -0
  7. package/FAQ.md +327 -0
  8. package/FAQ_ZH.md +279 -0
  9. package/LICENSE +21 -0
  10. package/NOTICE +9 -0
  11. package/PUBLIC_LAUNCH_CHECKLIST.md +111 -0
  12. package/README.md +287 -0
  13. package/README_ZH.md +461 -0
  14. package/SECURITY.md +145 -0
  15. package/config.example.env +31 -0
  16. package/dist/analysis/cache.js +27 -0
  17. package/dist/analysis/cache.js.map +1 -0
  18. package/dist/analysis/classify.js +102 -0
  19. package/dist/analysis/classify.js.map +1 -0
  20. package/dist/analysis/extract.js +139 -0
  21. package/dist/analysis/extract.js.map +1 -0
  22. package/dist/analysis/graph.js +22 -0
  23. package/dist/analysis/graph.js.map +1 -0
  24. package/dist/analysis/impact.js +167 -0
  25. package/dist/analysis/impact.js.map +1 -0
  26. package/dist/analysis/index.js +215 -0
  27. package/dist/analysis/index.js.map +1 -0
  28. package/dist/analysis/inventory.js +51 -0
  29. package/dist/analysis/inventory.js.map +1 -0
  30. package/dist/analysis/providers.js +24 -0
  31. package/dist/analysis/providers.js.map +1 -0
  32. package/dist/analysis/rank.js +27 -0
  33. package/dist/analysis/rank.js.map +1 -0
  34. package/dist/analysis/types.js +8 -0
  35. package/dist/analysis/types.js.map +1 -0
  36. package/dist/bashOps.js +233 -0
  37. package/dist/bashOps.js.map +1 -0
  38. package/dist/capabilitiesOps.js +365 -0
  39. package/dist/capabilitiesOps.js.map +1 -0
  40. package/dist/codexSessions.js +379 -0
  41. package/dist/codexSessions.js.map +1 -0
  42. package/dist/config.js +289 -0
  43. package/dist/config.js.map +1 -0
  44. package/dist/fsOps.js +286 -0
  45. package/dist/fsOps.js.map +1 -0
  46. package/dist/gitOps.js +79 -0
  47. package/dist/gitOps.js.map +1 -0
  48. package/dist/guard.js +198 -0
  49. package/dist/guard.js.map +1 -0
  50. package/dist/http.js +1671 -0
  51. package/dist/http.js.map +1 -0
  52. package/dist/proContext.js +274 -0
  53. package/dist/proContext.js.map +1 -0
  54. package/dist/profileStore.js +89 -0
  55. package/dist/profileStore.js.map +1 -0
  56. package/dist/projectCatalog.js +134 -0
  57. package/dist/projectCatalog.js.map +1 -0
  58. package/dist/redact.js +73 -0
  59. package/dist/redact.js.map +1 -0
  60. package/dist/searchOps.js +186 -0
  61. package/dist/searchOps.js.map +1 -0
  62. package/dist/server.js +2502 -0
  63. package/dist/server.js.map +1 -0
  64. package/dist/stdio.js +36 -0
  65. package/dist/stdio.js.map +1 -0
  66. package/dist/toolCardWidget.js +1155 -0
  67. package/dist/toolCardWidget.js.map +1 -0
  68. package/dist/workspaceOps.js +229 -0
  69. package/dist/workspaceOps.js.map +1 -0
  70. package/docs/.nojekyll +1 -0
  71. package/docs/favicon.svg +5 -0
  72. package/docs/index.html +638 -0
  73. package/docs/og.png +0 -0
  74. package/docs/script.js +80 -0
  75. package/docs/star.svg +11 -0
  76. package/docs/styles.css +1229 -0
  77. package/docs/zh.html +436 -0
  78. package/package.json +94 -0
  79. package/scripts/analysis-cli-smoke.mjs +81 -0
  80. package/scripts/analysis-smoke.mjs +179 -0
  81. package/scripts/clean.mjs +6 -0
  82. package/scripts/cli-smoke.mjs +168 -0
  83. package/scripts/codexflow.mjs +4375 -0
  84. package/scripts/doctor-smoke.mjs +90 -0
  85. package/scripts/execute-handoff-smoke.mjs +1110 -0
  86. package/scripts/http-smoke.mjs +812 -0
  87. package/scripts/pro-apply.mjs +141 -0
  88. package/scripts/pro-bundle.mjs +121 -0
  89. package/scripts/pro-smoke.mjs +95 -0
  90. package/scripts/settings-smoke.mjs +756 -0
  91. package/scripts/smoke.mjs +1194 -0
  92. package/scripts/stress.mjs +835 -0
@@ -0,0 +1,1155 @@
1
+ export const TOOL_CARD_URI = "ui://widget/codexflow-tool-card-v10.html";
2
+ export const TOOL_CARD_LEGACY_URIS = ["ui://widget/codexflow-tool-card-v9.html", "ui://widget/codexflow-tool-card-v8.html"];
3
+ export const TOOL_CARD_MIME_TYPE = "text/html;profile=mcp-app";
4
+ export const toolCardWidgetHtml = String.raw `
5
+ <meta charset="utf-8">
6
+ <div id="root" class="wrap">
7
+ <article class="card pending">
8
+ <div class="rail"></div>
9
+ <header class="head">
10
+ <span class="glyph">C</span>
11
+ <div class="headline">
12
+ <div class="title">CodexFlow</div>
13
+ <div class="subtitle">Waiting for tool result...</div>
14
+ </div>
15
+ <span class="pill info">waiting</span>
16
+ </header>
17
+ <div class="skeleton">
18
+ <span></span>
19
+ <span></span>
20
+ <span></span>
21
+ </div>
22
+ </article>
23
+ </div>
24
+
25
+ <style>
26
+ :root {
27
+ color-scheme: dark light;
28
+ --panel: #11151c;
29
+ --panel-2: #161b24;
30
+ --panel-3: #0c1016;
31
+ --panel-4: #1d222b;
32
+ --line: rgba(212, 219, 229, 0.13);
33
+ --line-strong: rgba(212, 219, 229, 0.24);
34
+ --text: #f2f4f7;
35
+ --soft: #c9d0da;
36
+ --muted: #97a1af;
37
+ --quiet: #6f7988;
38
+ --accent: #d7b56d;
39
+ --accent-soft: rgba(215, 181, 109, 0.12);
40
+ --blue: #9dc3ff;
41
+ --green: #8edc99;
42
+ --red: #f29a9a;
43
+ --amber: #e8c978;
44
+ --shadow: rgba(0, 0, 0, 0.26);
45
+ }
46
+
47
+ * { box-sizing: border-box; }
48
+
49
+ body {
50
+ margin: 0;
51
+ background: transparent;
52
+ color: var(--text);
53
+ font: 12px/1.48 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
54
+ letter-spacing: 0;
55
+ }
56
+
57
+ .wrap {
58
+ width: 100%;
59
+ }
60
+
61
+ .card {
62
+ position: relative;
63
+ overflow: hidden;
64
+ border: 1px solid var(--line);
65
+ border-radius: 8px;
66
+ background:
67
+ radial-gradient(circle at 18px 0, rgba(215, 181, 109, 0.12), transparent 42px),
68
+ linear-gradient(180deg, rgba(255, 255, 255, 0.042), rgba(255, 255, 255, 0)),
69
+ var(--panel);
70
+ box-shadow: 0 14px 34px var(--shadow);
71
+ }
72
+
73
+ .rail {
74
+ position: absolute;
75
+ inset: 0 auto 0 0;
76
+ width: 3px;
77
+ background: linear-gradient(180deg, var(--accent), rgba(142, 220, 153, 0.75) 64%, transparent);
78
+ opacity: 0.88;
79
+ }
80
+
81
+ .head {
82
+ display: grid;
83
+ grid-template-columns: 28px minmax(0, 1fr) auto;
84
+ align-items: center;
85
+ gap: 10px;
86
+ min-height: 56px;
87
+ padding: 11px 12px 10px 14px;
88
+ border-bottom: 1px solid var(--line);
89
+ }
90
+
91
+ .glyph {
92
+ display: inline-grid;
93
+ place-items: center;
94
+ width: 26px;
95
+ height: 26px;
96
+ border: 1px solid rgba(215, 181, 109, 0.28);
97
+ border-radius: 8px;
98
+ background: linear-gradient(180deg, rgba(215, 181, 109, 0.16), rgba(215, 181, 109, 0.04));
99
+ color: var(--accent);
100
+ font-size: 10px;
101
+ font-weight: 900;
102
+ }
103
+
104
+ .headline {
105
+ min-width: 0;
106
+ }
107
+
108
+ .title {
109
+ overflow: hidden;
110
+ color: var(--text);
111
+ font-size: 12px;
112
+ font-weight: 760;
113
+ text-overflow: ellipsis;
114
+ white-space: nowrap;
115
+ }
116
+
117
+ .subtitle {
118
+ overflow: hidden;
119
+ margin-top: 2px;
120
+ color: var(--muted);
121
+ font-size: 11px;
122
+ font-weight: 650;
123
+ text-overflow: ellipsis;
124
+ white-space: nowrap;
125
+ }
126
+
127
+ .meta {
128
+ display: flex;
129
+ flex-wrap: wrap;
130
+ justify-content: flex-end;
131
+ gap: 6px;
132
+ min-width: 0;
133
+ }
134
+
135
+ .pill {
136
+ display: inline-flex;
137
+ align-items: center;
138
+ min-height: 20px;
139
+ max-width: 22ch;
140
+ overflow: hidden;
141
+ padding: 2px 7px;
142
+ border: 1px solid var(--line);
143
+ border-radius: 6px;
144
+ background: rgba(255, 255, 255, 0.035);
145
+ color: var(--muted);
146
+ font-size: 10px;
147
+ font-weight: 720;
148
+ text-overflow: ellipsis;
149
+ white-space: nowrap;
150
+ }
151
+
152
+ .pill.good { color: var(--green); border-color: rgba(134, 239, 172, 0.28); background: rgba(134, 239, 172, 0.08); }
153
+ .pill.bad { color: var(--red); border-color: rgba(253, 164, 175, 0.28); background: rgba(253, 164, 175, 0.08); }
154
+ .pill.info { color: var(--blue); border-color: rgba(157, 195, 255, 0.28); background: rgba(157, 195, 255, 0.08); }
155
+ .pill.warn { color: var(--amber); border-color: rgba(253, 230, 138, 0.28); background: rgba(253, 230, 138, 0.08); }
156
+
157
+ .body {
158
+ max-height: 420px;
159
+ overflow: auto;
160
+ padding: 10px;
161
+ }
162
+
163
+ .metrics {
164
+ display: grid;
165
+ grid-template-columns: repeat(3, minmax(0, 1fr));
166
+ gap: 8px;
167
+ margin-bottom: 10px;
168
+ }
169
+
170
+ .metric {
171
+ min-width: 0;
172
+ padding: 8px 9px;
173
+ border: 1px solid var(--line);
174
+ border-radius: 7px;
175
+ background: rgba(255, 255, 255, 0.025);
176
+ }
177
+
178
+ .metric .label {
179
+ display: block;
180
+ margin-bottom: 4px;
181
+ color: var(--quiet);
182
+ font-size: 10px;
183
+ font-weight: 900;
184
+ text-transform: uppercase;
185
+ }
186
+
187
+ .metric .value {
188
+ overflow: hidden;
189
+ color: var(--soft);
190
+ text-overflow: ellipsis;
191
+ white-space: nowrap;
192
+ }
193
+
194
+ .code {
195
+ overflow: hidden;
196
+ border: 1px solid var(--line);
197
+ border-radius: 7px;
198
+ background: var(--panel-3);
199
+ }
200
+
201
+ .codebar {
202
+ display: flex;
203
+ align-items: center;
204
+ justify-content: space-between;
205
+ gap: 10px;
206
+ min-height: 30px;
207
+ padding: 6px 9px;
208
+ border-bottom: 1px solid var(--line);
209
+ background: var(--panel-2);
210
+ color: var(--muted);
211
+ font-size: 11px;
212
+ font-weight: 720;
213
+ }
214
+
215
+ pre {
216
+ margin: 0;
217
+ padding: 10px;
218
+ overflow: visible;
219
+ color: var(--soft);
220
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
221
+ font-size: 11px;
222
+ line-height: 1.52;
223
+ white-space: pre-wrap;
224
+ overflow-wrap: anywhere;
225
+ }
226
+
227
+ .diff-line { display: block; min-height: 18px; padding: 0 4px; border-radius: 3px; }
228
+ .diff-add { color: var(--green); background: rgba(142, 220, 153, 0.08); }
229
+ .diff-del { color: var(--red); background: rgba(242, 154, 154, 0.08); }
230
+ .diff-hunk { color: var(--blue); }
231
+ .terminal pre { color: #dbe7f5; }
232
+ .prompt { color: var(--accent); }
233
+
234
+ .summary {
235
+ display: grid;
236
+ grid-template-columns: repeat(3, minmax(0, 1fr));
237
+ gap: 8px;
238
+ margin-bottom: 10px;
239
+ }
240
+
241
+ .summary-item {
242
+ min-width: 0;
243
+ padding: 9px 10px;
244
+ border: 1px solid var(--line);
245
+ border-radius: 7px;
246
+ background: rgba(255, 255, 255, 0.025);
247
+ }
248
+
249
+ .summary-label {
250
+ display: block;
251
+ margin-bottom: 4px;
252
+ color: var(--quiet);
253
+ font-size: 10px;
254
+ font-weight: 760;
255
+ }
256
+
257
+ .summary-value {
258
+ color: var(--text);
259
+ font-size: 15px;
260
+ font-variant-numeric: tabular-nums;
261
+ font-weight: 760;
262
+ }
263
+
264
+ .project-list { display: grid; gap: 7px; }
265
+ .project-button {
266
+ display: grid;
267
+ grid-template-columns: minmax(0, 1fr) auto;
268
+ gap: 10px;
269
+ width: 100%;
270
+ padding: 10px 11px;
271
+ border: 1px solid var(--line);
272
+ border-radius: 7px;
273
+ background: rgba(255, 255, 255, 0.025);
274
+ color: var(--text);
275
+ text-align: left;
276
+ cursor: pointer;
277
+ }
278
+ .project-button:hover { border-color: var(--line-strong); background: var(--accent-soft); }
279
+ .project-button:disabled { cursor: wait; opacity: 0.62; }
280
+ .project-main { min-width: 0; }
281
+ .project-name { display: block; overflow: hidden; font-weight: 760; text-overflow: ellipsis; white-space: nowrap; }
282
+ .project-path { display: block; overflow: hidden; margin-top: 2px; color: var(--muted); font-size: 10px; text-overflow: ellipsis; white-space: nowrap; }
283
+ .project-action { align-self: center; color: var(--accent); font-size: 10px; font-weight: 800; }
284
+
285
+ .file-list {
286
+ display: grid;
287
+ gap: 4px;
288
+ margin-bottom: 10px;
289
+ }
290
+
291
+ .section-label {
292
+ margin: 10px 1px 6px;
293
+ color: var(--quiet);
294
+ font-size: 10px;
295
+ font-weight: 850;
296
+ text-transform: uppercase;
297
+ }
298
+
299
+ .fold {
300
+ margin-top: 8px;
301
+ border: 1px solid var(--line);
302
+ border-radius: 7px;
303
+ background: rgba(255, 255, 255, 0.018);
304
+ }
305
+
306
+ .fold > summary {
307
+ display: grid;
308
+ grid-template-columns: minmax(0, 1fr) auto;
309
+ gap: 10px;
310
+ align-items: center;
311
+ min-height: 34px;
312
+ padding: 8px 10px;
313
+ cursor: pointer;
314
+ color: var(--soft);
315
+ font-weight: 760;
316
+ list-style: none;
317
+ }
318
+
319
+ .fold > summary::-webkit-details-marker { display: none; }
320
+
321
+ .fold-title {
322
+ overflow: hidden;
323
+ text-overflow: ellipsis;
324
+ white-space: nowrap;
325
+ }
326
+
327
+ .fold-count {
328
+ color: var(--muted);
329
+ font-size: 10px;
330
+ font-weight: 800;
331
+ }
332
+
333
+ .fold-body {
334
+ padding: 0 8px 8px;
335
+ }
336
+
337
+ .file-row {
338
+ display: grid;
339
+ grid-template-columns: 42px minmax(0, 1fr);
340
+ gap: 8px;
341
+ align-items: center;
342
+ padding: 7px 8px;
343
+ border: 1px solid var(--line);
344
+ border-radius: 7px;
345
+ background: rgba(255, 255, 255, 0.022);
346
+ }
347
+
348
+ .file-code {
349
+ color: var(--accent);
350
+ font: 10px/1.2 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
351
+ font-weight: 800;
352
+ }
353
+
354
+ .file-name {
355
+ overflow: hidden;
356
+ color: var(--soft);
357
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
358
+ text-overflow: ellipsis;
359
+ white-space: nowrap;
360
+ }
361
+
362
+ .empty {
363
+ padding: 10px;
364
+ border: 1px dashed var(--line-strong);
365
+ border-radius: 7px;
366
+ background: rgba(255, 255, 255, 0.018);
367
+ color: var(--muted);
368
+ }
369
+
370
+ .search {
371
+ display: grid;
372
+ gap: 4px;
373
+ }
374
+
375
+ .hit {
376
+ display: grid;
377
+ grid-template-columns: minmax(120px, 0.34fr) minmax(0, 1fr);
378
+ gap: 8px;
379
+ padding: 6px 8px;
380
+ border-radius: 7px;
381
+ }
382
+
383
+ .hit:nth-child(odd) {
384
+ background: rgba(255, 255, 255, 0.025);
385
+ }
386
+
387
+ .hit-file {
388
+ overflow: hidden;
389
+ color: var(--blue);
390
+ font-weight: 850;
391
+ text-overflow: ellipsis;
392
+ white-space: nowrap;
393
+ }
394
+
395
+ .hit-text {
396
+ color: var(--soft);
397
+ overflow-wrap: anywhere;
398
+ }
399
+
400
+ .muted { color: var(--muted); }
401
+
402
+ .skeleton {
403
+ display: grid;
404
+ gap: 7px;
405
+ padding: 11px 13px 13px 17px;
406
+ border-top: 1px solid rgba(255, 255, 255, 0.02);
407
+ }
408
+
409
+ .skeleton span {
410
+ height: 8px;
411
+ max-width: 78%;
412
+ border-radius: 999px;
413
+ background: linear-gradient(90deg, rgba(148, 163, 184, 0.12), rgba(148, 163, 184, 0.22), rgba(148, 163, 184, 0.12));
414
+ animation: codexflow-sheen 1.55s ease-in-out infinite;
415
+ }
416
+
417
+ .skeleton span:nth-child(2) { max-width: 52%; animation-delay: 0.12s; }
418
+ .skeleton span:nth-child(3) { max-width: 66%; animation-delay: 0.24s; }
419
+
420
+ @keyframes codexflow-sheen {
421
+ 0%, 100% { opacity: 0.46; transform: translateX(0); }
422
+ 50% { opacity: 1; transform: translateX(2px); }
423
+ }
424
+
425
+ @media (max-width: 640px) {
426
+ .head { grid-template-columns: 28px minmax(0, 1fr); }
427
+ .meta { grid-column: 1 / -1; justify-content: flex-start; }
428
+ .summary,
429
+ .metrics,
430
+ .hit { grid-template-columns: 1fr; }
431
+ }
432
+ </style>
433
+
434
+ <script>
435
+ const root = document.getElementById("root");
436
+
437
+ function esc(value) {
438
+ return String(value ?? "")
439
+ .replaceAll("&", "&amp;")
440
+ .replaceAll("<", "&lt;")
441
+ .replaceAll(">", "&gt;")
442
+ .replaceAll('"', "&quot;");
443
+ }
444
+
445
+ function truncate(value, max = 9000) {
446
+ const text = String(value ?? "");
447
+ return text.length > max ? text.slice(0, max) + "\n...[truncated in widget]" : text;
448
+ }
449
+
450
+ function countLines(value) {
451
+ const text = String(value || "");
452
+ if (!text) return 0;
453
+ return text.replace(/\n$/, "").split("\n").length;
454
+ }
455
+
456
+ function previewLines(value, maxLines = 18) {
457
+ const text = String(value || "").replace(/\n$/, "");
458
+ if (!text) return "";
459
+ const lines = text.split("\n");
460
+ const shown = lines.slice(0, maxLines).join("\n");
461
+ const remaining = lines.length - maxLines;
462
+ return remaining > 0 ? shown + "\n...[" + remaining + " more lines]" : shown;
463
+ }
464
+
465
+ function basename(value) {
466
+ const text = String(value || "");
467
+ return text.split("/").filter(Boolean).pop() || text || ".";
468
+ }
469
+
470
+ function titleFor(tool) {
471
+ const titles = {
472
+ server_config: "Server config",
473
+ codexflow_self_test: "Self-test",
474
+ codexflow_inventory: "Inventory",
475
+ list_projects: "Choose a project",
476
+ select_project: "Project selected",
477
+ load_skill: "Skill",
478
+ list_workspaces: "Workspaces",
479
+ open_current_workspace: "Workspace",
480
+ open_workspace: "Workspace",
481
+ workspace_snapshot: "Workspace snapshot",
482
+ inspect_workspace: "Workspace analysis",
483
+ tree: "File tree",
484
+ write: "File write",
485
+ edit: "Exact edit",
486
+ apply_patch: "Patch",
487
+ git_status: "Git Status",
488
+ git_diff: "Git Diff",
489
+ show_changes: "Change review",
490
+ read_handoff: "Handoff context",
491
+ codex_context: "Codex context",
492
+ export_pro_context: "Pro context",
493
+ handoff_to_agent: "Agent handoff",
494
+ handoff_to_codex: "Codex handoff",
495
+ bash: "Terminal",
496
+ search: "Search",
497
+ read: "Read file"
498
+ };
499
+ return titles[tool] || "CodexFlow";
500
+ }
501
+
502
+ function iconFor(tool) {
503
+ if (tool === "server_config") return "S";
504
+ if (tool === "codexflow_self_test") return "T";
505
+ if (tool === "codexflow_inventory") return "I";
506
+ if (tool === "list_projects" || tool === "select_project") return "P";
507
+ if (tool === "load_skill") return "L";
508
+ if (tool === "list_workspaces") return "W";
509
+ if (tool === "open_current_workspace" || tool === "open_workspace") return "W";
510
+ if (tool === "workspace_snapshot") return "W";
511
+ if (tool === "inspect_workspace") return "I";
512
+ if (tool === "tree") return "T";
513
+ if (tool === "write") return "W";
514
+ if (tool === "edit") return "E";
515
+ if (tool === "apply_patch") return "P";
516
+ if (tool === "git_status" || tool === "git_diff") return "G";
517
+ if (tool === "show_changes") return "D";
518
+ if (tool === "read_handoff") return "H";
519
+ if (tool === "codex_context") return "C";
520
+ if (tool === "export_pro_context") return "P";
521
+ if (tool === "handoff_to_agent") return "A";
522
+ if (tool === "handoff_to_codex") return "H";
523
+ if (tool === "bash") return "$";
524
+ if (tool === "search") return "S";
525
+ if (tool === "read") return "R";
526
+ return "C";
527
+ }
528
+
529
+ function subtitleFor(data) {
530
+ if (data?.codexflow_tool === "open_current_workspace" || data?.codexflow_tool === "open_workspace") {
531
+ return data?.root || "Workspace opened";
532
+ }
533
+ if (data?.codexflow_tool === "show_changes") {
534
+ if (data?.status_error || data?.diff_error) return "Git state unavailable";
535
+ const count = Array.isArray(data?.changed_files) ? data.changed_files.length : 0;
536
+ if (!count && !data?.changed) return "Workspace is clean";
537
+ return count === 1 ? "1 changed file" : count + " changed files";
538
+ }
539
+ if (data?.codexflow_tool === "codexflow_self_test") return data?.status ? "Status " + data.status : "Local diagnostic";
540
+ if (data?.codexflow_tool === "codexflow_inventory") return (data?.skill_count ?? 0) + " skills, " + (data?.mcp_server_count ?? 0) + " MCP servers";
541
+ if (data?.codexflow_tool === "list_projects") return (data?.count ?? 0) + " synchronized projects";
542
+ if (data?.codexflow_tool === "select_project") return data?.root || "Chat routed to project";
543
+ if (data?.codexflow_tool === "list_workspaces") return (data?.count ?? 0) + " open workspaces";
544
+ if (data?.codexflow_tool === "server_config") {
545
+ const session = data?.bashSessionId || data?.bash_session_id;
546
+ return "tools " + (data?.toolMode || data?.tool_mode || "-") + ", bash " + (data?.bashMode || data?.bash_mode || "-") + (session ? ", session " + session : "");
547
+ }
548
+ if (data?.codexflow_tool === "workspace_snapshot") return data?.root || "Workspace snapshot";
549
+ if (data?.codexflow_tool === "inspect_workspace") {
550
+ const coverage = data?.coverage || {};
551
+ return (coverage.analyzedFiles ?? coverage.analyzed_files ?? 0) + " files analyzed, " + (coverage.symbolCount ?? coverage.symbol_count ?? 0) + " symbols";
552
+ }
553
+ if (data?.codexflow_tool === "git_status") {
554
+ const count = Array.isArray(data?.changed_files) ? data.changed_files.length : 0;
555
+ return count ? count + " changed entries" : "Working tree clean";
556
+ }
557
+ if (data?.codexflow_tool === "codex_context") return (data?.agents_files?.length ?? 0) + " AGENTS, " + (data?.ai_context_files?.length ?? 0) + " bridge files";
558
+ if (data?.codexflow_tool === "read_handoff") return (data?.file_count ?? 0) + " bridge files";
559
+ if (data?.codexflow_tool === "load_skill" && data?.skill?.name) return data.skill.name;
560
+ if (data?.codexflow_tool === "handoff_to_agent" && data?.agent_name) return data.agent_name;
561
+ if (data?.path) return data.path;
562
+ if (data?.plan_path) return data.plan_path;
563
+ if (data?.root) return data.root;
564
+ if (data?.cwd) return data.cwd;
565
+ return "Tool output";
566
+ }
567
+
568
+ function pill(text, cls) {
569
+ return '<span class="pill ' + esc(cls || "") + '">' + esc(text) + '</span>';
570
+ }
571
+
572
+ function header(data, pills) {
573
+ const tool = data?.codexflow_tool;
574
+ return [
575
+ '<div class="rail"></div>',
576
+ '<header class="head">',
577
+ '<span class="glyph">' + esc(iconFor(tool)) + '</span>',
578
+ '<div class="headline"><div class="title">' + esc(titleFor(tool)) + '</div><div class="subtitle">' + esc(subtitleFor(data)) + '</div></div>',
579
+ '<div class="meta">' + (pills || '') + '</div>',
580
+ '</header>'
581
+ ].join('');
582
+ }
583
+
584
+ function metric(label, value) {
585
+ return '<div class="metric"><span class="label">' + esc(label) + '</span><div class="value">' + esc(value ?? "-") + '</div></div>';
586
+ }
587
+
588
+ function summaryItem(label, value) {
589
+ return '<div class="summary-item"><span class="summary-label">' + esc(label) + '</span><div class="summary-value">' + esc(value ?? "-") + '</div></div>';
590
+ }
591
+
592
+ function codebox(label, text, extraClass) {
593
+ return '<div class="code ' + esc(extraClass || "") + '"><div class="codebar"><span>' + esc(label || "output") + '</span></div><pre>' + text + '</pre></div>';
594
+ }
595
+
596
+ function fold(title, count, body, open) {
597
+ if (!body) return "";
598
+ return '<details class="fold"' + (open ? " open" : "") + '><summary><span class="fold-title">' + esc(title) + '</span><span class="fold-count">' + esc(count || "") + '</span></summary><div class="fold-body">' + body + '</div></details>';
599
+ }
600
+
601
+ function shortSource(value) {
602
+ if (value === "workspace") return "repo";
603
+ if (value === "plugin") return "plug";
604
+ if (value === "user") return "user";
605
+ return "skill";
606
+ }
607
+
608
+ function renderDiff(diff) {
609
+ return truncate(diff, 14000).split("\n").map((line) => {
610
+ let cls = "diff-line";
611
+ if (line.startsWith("+") && !line.startsWith("+++")) cls += " diff-add";
612
+ else if (line.startsWith("-") && !line.startsWith("---")) cls += " diff-del";
613
+ else if (line.startsWith("@@")) cls += " diff-hunk";
614
+ return '<span class="' + cls + '">' + esc(line) + '</span>';
615
+ }).join("");
616
+ }
617
+
618
+ function renderFile(data) {
619
+ const pills = [
620
+ data.bytes !== undefined ? pill(data.bytes + " bytes") : "",
621
+ data.additions !== undefined ? pill("+" + data.additions, "good") : "",
622
+ data.deletions !== undefined ? pill("-" + data.deletions, "bad") : "",
623
+ data.replacements !== undefined ? pill(data.replacements + " replacements", "info") : ""
624
+ ].join("");
625
+ const body = data.diff ? renderDiff(data.diff) : esc(truncate(data.text || ""));
626
+ return '<article class="card">' + header(data, pills) + '<div class="body">' +
627
+ codebox(basename(data.path || data.plan_path || "file"), body, "") +
628
+ '</div></article>';
629
+ }
630
+
631
+ function renderChanges(data) {
632
+ const files = Array.isArray(data.changed_files) ? data.changed_files : [];
633
+ const hasGitError = Boolean(data.status_error || data.diff_error);
634
+ const changed = Boolean(data.changed);
635
+ const pills = [
636
+ hasGitError ? pill("git unavailable", "warn") : changed ? pill("changed", "info") : pill("clean", "good"),
637
+ data.additions !== undefined ? pill("+" + data.additions, "good") : "",
638
+ data.deletions !== undefined ? pill("-" + data.deletions, "bad") : ""
639
+ ].join("");
640
+ const fileRows = files.slice(0, 10).map((line) => {
641
+ const status = String(line).slice(0, 2).trim() || "?";
642
+ const name = String(line).slice(2).trim() || String(line);
643
+ return '<div class="file-row"><span class="file-code">' + esc(status) + '</span><span class="file-name">' + esc(name) + '</span></div>';
644
+ }).join("");
645
+ const moreFiles = files.length > 10 ? '<div class="empty">+' + esc(files.length - 10) + ' more changed files</div>' : "";
646
+ const state = hasGitError
647
+ ? '<div class="empty">' + esc(data.status_error || data.diff_error) + '</div>'
648
+ : fileRows
649
+ ? '<div class="file-list">' + fileRows + '</div>' + moreFiles
650
+ : '<div class="empty">No changed files.</div>';
651
+ const diff = data.diff ? codebox("diff", renderDiff(data.diff), "") : "";
652
+ return '<article class="card">' + header(data, pills) + '<div class="body">' +
653
+ '<div class="summary">' +
654
+ summaryItem("Files", files.length) +
655
+ summaryItem("Added", "+" + (data.additions ?? 0)) +
656
+ summaryItem("Deleted", "-" + (data.deletions ?? 0)) +
657
+ '</div>' +
658
+ state +
659
+ diff +
660
+ '</div></article>';
661
+ }
662
+
663
+ function compactRows(values, code, max = 8) {
664
+ const items = Array.isArray(values) ? values : [];
665
+ const rows = items.slice(0, max).map((value) => {
666
+ const label = typeof value === "string" ? value : (value?.path || value?.label || value?.name || "item");
667
+ const detail = typeof value === "object" && value ? (value?.reasons || []).join(", ") : "";
668
+ return '<div class="file-row"><span class="file-code">' + esc(code) + '</span><span class="file-name">' + esc(label + (detail ? ": " + detail : "")) + '</span></div>';
669
+ }).join("");
670
+ const more = items.length > max ? '<div class="empty">+' + esc(items.length - max) + ' more</div>' : "";
671
+ return '<div class="file-list">' + (rows || '<div class="empty">None.</div>') + more + '</div>';
672
+ }
673
+
674
+ function renderWorkspaceAnalysis(data) {
675
+ const coverage = data.coverage || {};
676
+ const languages = Array.isArray(data.languages) ? data.languages : [];
677
+ const projects = Array.isArray(data.project_types) ? data.project_types : [];
678
+ const entrypoints = Array.isArray(data.entrypoints) ? data.entrypoints : [];
679
+ const areas = Array.isArray(data.areas) ? data.areas : [];
680
+ const symbols = Array.isArray(data.symbols) ? data.symbols : [];
681
+ const relationships = Array.isArray(data.relationships) ? data.relationships : [];
682
+ const warnings = Array.isArray(data.warnings) ? data.warnings : [];
683
+ const partial = Boolean(coverage.truncated || data.output_limited);
684
+ const pills = [
685
+ pill(projects.join(", ") || "project", "info"),
686
+ pill(languages.length + " languages"),
687
+ partial ? pill("limited", "warn") : pill("complete", "good")
688
+ ].join("");
689
+ const relationshipRows = relationships.slice(0, 8).map((edge) =>
690
+ '<div class="file-row"><span class="file-code">' + esc(edge?.kind || "edge") + '</span><span class="file-name">' + esc((edge?.from || "?") + " → " + (edge?.to || "?")) + '</span></div>'
691
+ ).join("");
692
+ return '<article class="card">' + header(data, pills) + '<div class="body">' +
693
+ '<div class="summary">' +
694
+ summaryItem("Files", coverage.inventoryFiles ?? coverage.inventory_files ?? 0) +
695
+ summaryItem("Analyzed", coverage.analyzedFiles ?? coverage.analyzed_files ?? 0) +
696
+ summaryItem("Symbols", coverage.symbolCount ?? coverage.symbol_count ?? symbols.length) +
697
+ '</div>' +
698
+ '<div class="section-label">Entrypoints</div>' + compactRows(entrypoints, "entry") +
699
+ fold("Areas", areas.length + " areas", compactRows(areas, "area"), false) +
700
+ fold("Symbols", symbols.length + " symbols", compactRows(symbols, "sym"), false) +
701
+ fold("Relationships", relationships.length + " edges", '<div class="file-list">' + (relationshipRows || '<div class="empty">None.</div>') + '</div>', false) +
702
+ (warnings.length ? fold("Warnings", warnings.length + " warnings", compactRows(warnings, "warn"), false) : "") +
703
+ '</div></article>';
704
+ }
705
+
706
+ function renderStructuredSearch(data) {
707
+ const analysis = data.analysis || {};
708
+ const groups = analysis.groups || {};
709
+ const order = ["definitions", "references", "tests", "configuration", "documentation", "other"];
710
+ const count = order.reduce((sum, name) => sum + (Array.isArray(groups[name]) ? groups[name].length : 0), 0);
711
+ const sections = order.map((name) => {
712
+ const matches = Array.isArray(groups[name]) ? groups[name] : [];
713
+ if (!matches.length) return "";
714
+ const rows = matches.slice(0, 8).map((match) =>
715
+ '<div class="hit"><div class="hit-file">' + esc((match.path || "match") + ":" + (match.line || 0)) + '</div><div class="hit-text">' + esc((match.text || "") + (match.reasons?.length ? ": " + match.reasons.join(", ") : "")) + '</div></div>'
716
+ ).join("");
717
+ const more = matches.length > 8 ? '<div class="empty">+' + esc(matches.length - 8) + ' more</div>' : "";
718
+ return fold(name, matches.length + " matches", '<div class="search">' + rows + more + '</div>', name === "definitions");
719
+ }).join("");
720
+ const coverage = analysis.coverage || {};
721
+ const pills = [pill(count + " grouped matches", "info"), pill(analysis.intent || "structured"), coverage.truncated ? pill("partial", "warn") : ""].join("");
722
+ return '<article class="card">' + header(data, pills) + '<div class="body">' +
723
+ '<div class="summary">' + summaryItem("Definitions", groups.definitions?.length ?? 0) + summaryItem("References", groups.references?.length ?? 0) + summaryItem("Tests", groups.tests?.length ?? 0) + '</div>' +
724
+ (sections || '<div class="empty">No grouped matches.</div>') +
725
+ '</div></article>';
726
+ }
727
+
728
+ function renderChangeAnalysis(data) {
729
+ const analysis = data.analysis || {};
730
+ const files = Array.isArray(data.changed_files) ? data.changed_files : [];
731
+ const risks = Array.isArray(analysis.risk_signals) ? analysis.risk_signals : [];
732
+ const tests = Array.isArray(analysis.related_tests) ? analysis.related_tests : [];
733
+ const commands = Array.isArray(analysis.recommended_commands) ? analysis.recommended_commands : [];
734
+ const affected = Array.isArray(analysis.affected_areas) ? analysis.affected_areas : [];
735
+ const pills = [
736
+ pill(data.changed ? "changed" : "clean", data.changed ? "info" : "good"),
737
+ risks.length ? pill(risks.length + " risks", "warn") : pill("no risks", "good"),
738
+ pill("+" + (data.additions ?? 0), "good"),
739
+ pill("-" + (data.deletions ?? 0), "bad")
740
+ ].join("");
741
+ const commandRows = commands.slice(0, 8).map((item) =>
742
+ '<div class="file-row"><span class="file-code">run</span><span class="file-name">' + esc(item?.command || "") + '</span></div>'
743
+ ).join("");
744
+ return '<article class="card">' + header(data, pills) + '<div class="body">' +
745
+ '<div class="summary">' + summaryItem("Files", files.length) + summaryItem("Areas", affected.length) + summaryItem("Tests", tests.length) + '</div>' +
746
+ '<div class="section-label">Affected areas</div>' + compactRows(affected, "area") +
747
+ fold("Risk signals", risks.length + " signals", compactRows(risks, "risk"), risks.length > 0) +
748
+ fold("Related tests", tests.length + " tests", compactRows(tests, "test"), false) +
749
+ fold("Verification", commands.length + " commands", '<div class="file-list">' + (commandRows || '<div class="empty">None.</div>') + '</div>', false) +
750
+ (data.diff ? fold("Diff", "+" + (data.additions ?? 0) + " -" + (data.deletions ?? 0), codebox("diff", renderDiff(data.diff), ""), false) : "") +
751
+ '</div></article>';
752
+ }
753
+
754
+ function gitStatusRows(status, max = 8) {
755
+ return String(status || "")
756
+ .split("\n")
757
+ .map((line) => line.trim())
758
+ .filter((line) => line && !line.startsWith("##"))
759
+ .slice(0, max)
760
+ .map((line) => {
761
+ const code = line.slice(0, 2).trim() || "?";
762
+ const name = line.slice(2).trim() || line;
763
+ return '<div class="file-row"><span class="file-code">' + esc(code) + '</span><span class="file-name">' + esc(name) + '</span></div>';
764
+ })
765
+ .join("");
766
+ }
767
+
768
+ function renderWorkspace(data) {
769
+ const skills = Array.isArray(data.skill_inventory) ? data.skill_inventory : (Array.isArray(data.skills) ? data.skills : []);
770
+ const skillCount = Number(data.skill_counts?.total ?? skills.length);
771
+ const changedRows = gitStatusRows(data.git_status, 8);
772
+ const gitLines = String(data.git_status || "").split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("##"));
773
+ const agentsLabel = data.agents_loaded ? (data.agents_path || "AGENTS.md") : "no AGENTS";
774
+ const pills = [
775
+ pill(agentsLabel, data.agents_loaded ? "good" : "warn"),
776
+ pill(skillCount + " skills", skillCount ? "info" : ""),
777
+ data.tool_mode ? pill("tools " + data.tool_mode) : ""
778
+ ].join("");
779
+ const contextRows = [
780
+ '<div class="file-row"><span class="file-code">root</span><span class="file-name">' + esc(data.root || ".") + '</span></div>',
781
+ data.workspace_id ? '<div class="file-row"><span class="file-code">id</span><span class="file-name">' + esc(data.workspace_id) + '</span></div>' : "",
782
+ data.agents_loaded ? '<div class="file-row"><span class="file-code">rules</span><span class="file-name">' + esc(data.agents_path || "AGENTS.md") + '</span></div>' : ""
783
+ ].join("");
784
+ const skillRows = skills.slice(0, 16).map((skill) => {
785
+ const value = typeof skill === "string" ? skill : (skill?.name || "skill");
786
+ const source = typeof skill === "string" ? "skill" : shortSource(skill?.source);
787
+ return '<div class="file-row"><span class="file-code">' + esc(source) + '</span><span class="file-name">' + esc(value) + '</span></div>';
788
+ }).join("");
789
+ const skillText = skills.length
790
+ ? '<div class="file-list">' + skillRows + '</div>' + (skills.length > 16 ? '<div class="empty">+' + esc(skills.length - 16) + ' more skills</div>' : "")
791
+ : '<div class="empty">No skills discovered. Use include_global_skills=true if this is unexpected.</div>';
792
+ const gitText = changedRows
793
+ ? '<div class="file-list">' + changedRows + '</div>' + (gitLines.length > 8 ? '<div class="empty">+' + esc(gitLines.length - 8) + ' more changed files</div>' : "")
794
+ : '<div class="empty">Working tree clean.</div>';
795
+ const tree = data.tree ? codebox("tree", esc(previewLines(data.tree, 18)), "") : "";
796
+ return '<article class="card">' + header(data, pills) + '<div class="body">' +
797
+ '<div class="summary">' +
798
+ summaryItem("Write", data.write_mode || "-") +
799
+ summaryItem("Bash", data.bash_mode || "-") +
800
+ summaryItem("Tools", data.tool_mode || "-") +
801
+ '</div>' +
802
+ '<div class="section-label">Context</div><div class="file-list">' + contextRows + '</div>' +
803
+ fold("Git", gitLines.length ? gitLines.length + " changed" : "clean", gitText, false) +
804
+ fold("Skills", skillCount + " discovered", skillText, false) +
805
+ fold("Tree", data.tree ? "available" : "", tree, false) +
806
+ '</div></article>';
807
+ }
808
+
809
+ function renderHandoff(data) {
810
+ const pills = [
811
+ data.agent_name ? pill(data.agent_name, "info") : "",
812
+ data.model ? pill(data.model) : "",
813
+ data.additions !== undefined ? pill("+" + data.additions, "good") : "",
814
+ data.deletions !== undefined ? pill("-" + data.deletions, "bad") : ""
815
+ ].join("");
816
+ const rows = [
817
+ data.plan_path ? '<div class="file-row"><span class="file-code">plan</span><span class="file-name">' + esc(data.plan_path) + '</span></div>' : "",
818
+ data.status_path ? '<div class="file-row"><span class="file-code">status</span><span class="file-name">' + esc(data.status_path) + '</span></div>' : "",
819
+ data.diff_path ? '<div class="file-row"><span class="file-code">diff</span><span class="file-name">' + esc(data.diff_path) + '</span></div>' : ""
820
+ ].join("");
821
+ const diff = data.diff ? codebox("plan file diff", renderDiff(data.diff), "") : "";
822
+ return '<article class="card">' + header(data, pills) + '<div class="body">' +
823
+ '<div class="file-list">' + rows + '</div>' +
824
+ diff +
825
+ '</div></article>';
826
+ }
827
+
828
+ function renderBash(data) {
829
+ const ok = Number(data.exitCode) === 0;
830
+ const stdoutLines = countLines(data.stdout);
831
+ const stderrLines = countLines(data.stderr);
832
+ const totalLines = stdoutLines + stderrLines;
833
+ const pills = [
834
+ pill(ok ? "passed" : "failed", ok ? "good" : "bad"),
835
+ pill(totalLines + " lines", "info"),
836
+ pill((data.durationMs ?? "-") + " ms")
837
+ ].join("");
838
+ const command = '<span class="prompt">$</span> ' + esc(data.command || "");
839
+ const output = previewLines(data.stdout || data.stderr || "", 18);
840
+ const outputBox = output
841
+ ? fold("Output preview", totalLines + " lines", codebox("output preview", esc(truncate(output, 5000)), "terminal"), false)
842
+ : '<div class="empty">Command produced no output.</div>';
843
+ return '<article class="card">' + header(data, pills) + '<div class="body">' +
844
+ '<div class="summary">' +
845
+ summaryItem("Exit", data.exitCode ?? "-") +
846
+ summaryItem("Lines", totalLines) +
847
+ summaryItem("Duration", (data.durationMs ?? "-") + " ms") +
848
+ '</div>' +
849
+ codebox("command", command, "terminal") +
850
+ outputBox +
851
+ '</div></article>';
852
+ }
853
+
854
+ function renderSearch(data) {
855
+ const count = Array.isArray(data.matches) ? data.matches.length : 0;
856
+ const lines = String(data.text || "").split("\\n").filter(Boolean).slice(0, 90);
857
+ const hits = lines.map((line) => {
858
+ const parts = line.split(":");
859
+ const file = parts.length > 2 ? parts.slice(0, 2).join(":") : (parts[0] || "match");
860
+ const body = parts.length > 2 ? parts.slice(2).join(":").trim() : line;
861
+ return '<div class="hit"><div class="hit-file">' + esc(file) + '</div><div class="hit-text">' + esc(body) + '</div></div>';
862
+ }).join("") || '<div class="muted">No matches.</div>';
863
+ return '<article class="card">' + header(data, pill(count + " matches", "info") + pill(data.used || "search")) +
864
+ '<div class="body"><div class="search">' + hits + '</div></div></article>';
865
+ }
866
+
867
+ function renderSelfTest(data) {
868
+ const checks = Array.isArray(data.checks) ? data.checks : [];
869
+ const status = String(data.status || "unknown");
870
+ const pills = [
871
+ pill(status, status === "pass" ? "good" : status === "fail" ? "bad" : "warn"),
872
+ pill((data.expected_tool_count ?? "-") + " tools", "info"),
873
+ pill((data.duration_ms ?? "-") + " ms")
874
+ ].join("");
875
+ const rows = checks.slice(0, 16).map((check) => {
876
+ const state = String(check?.status || "?").toUpperCase();
877
+ const cls = check?.status === "pass" ? "good" : check?.status === "fail" ? "bad" : "warn";
878
+ return '<div class="file-row"><span class="file-code ' + esc(cls) + '">' + esc(state) + '</span><span class="file-name">' + esc((check?.name || "check") + ": " + (check?.detail || "")) + '</span></div>';
879
+ }).join("");
880
+ const terms = data.terms_boundary
881
+ ? '<div class="file-list">' +
882
+ '<div class="file-row"><span class="file-code">tos</span><span class="file-name">local repo bridge only; no model access, quota, resale, or bypass behavior</span></div>' +
883
+ '</div>'
884
+ : "";
885
+ return '<article class="card">' + header(data, pills) + '<div class="body">' +
886
+ '<div class="summary">' +
887
+ summaryItem("Passed", data.passed ?? 0) +
888
+ summaryItem("Warned", data.warned ?? 0) +
889
+ summaryItem("Failed", data.failed ?? 0) +
890
+ '</div>' +
891
+ '<div class="file-list">' + (rows || '<div class="empty">No checks returned.</div>') + '</div>' +
892
+ fold("Terms boundary", "", terms, false) +
893
+ fold("Expected tools", Array.isArray(data.expected_tools) ? data.expected_tools.length + " tools" : "", codebox("tools", esc((data.expected_tools || []).join("\\n")), ""), false) +
894
+ '</div></article>';
895
+ }
896
+
897
+ function renderInventory(data) {
898
+ const skills = Array.isArray(data.skills) ? data.skills : [];
899
+ const plugins = Array.isArray(data.plugins) ? data.plugins : [];
900
+ const servers = Array.isArray(data.mcp_servers) ? data.mcp_servers : [];
901
+ const skillRows = skills.slice(0, 18).map((skill) =>
902
+ '<div class="file-row"><span class="file-code">' + esc(shortSource(skill?.source)) + '</span><span class="file-name">' + esc((skill?.name || "skill") + (skill?.description ? " — " + skill.description : "")) + '</span></div>'
903
+ ).join("");
904
+ const serverRows = servers.slice(0, 18).map((server) =>
905
+ '<div class="file-row"><span class="file-code">mcp</span><span class="file-name">' + esc((server?.name || "server") + (server?.source ? " — " + server.source : "")) + '</span></div>'
906
+ ).join("");
907
+ const pluginRows = plugins.slice(0, 18).map((plugin) =>
908
+ '<div class="file-row"><span class="file-code">plugin</span><span class="file-name">' + esc((plugin?.name || "plugin") + (plugin?.version ? " " + plugin.version : "") + (plugin?.description ? " — " + plugin.description : "")) + '</span></div>'
909
+ ).join("");
910
+ return '<article class="card">' + header(data, pill((data.skill_count ?? skills.length) + " skills", "info") + pill((data.plugin_count ?? plugins.length) + " plugins") + pill((data.mcp_server_count ?? servers.length) + " MCP")) +
911
+ '<div class="body">' +
912
+ '<div class="summary">' +
913
+ summaryItem("Write", data.write_mode || "-") +
914
+ summaryItem("Bash", data.bash_mode || "-") +
915
+ summaryItem("Tools", data.tool_mode || "-") +
916
+ '</div>' +
917
+ fold("Skills", (data.skill_count ?? skills.length) + " found", '<div class="file-list">' + (skillRows || '<div class="empty">No skills discovered.</div>') + '</div>', false) +
918
+ fold("Plugins", (data.plugin_count ?? plugins.length) + " found", '<div class="file-list">' + (pluginRows || '<div class="empty">No plugins discovered.</div>') + '</div>', false) +
919
+ fold("MCP servers", (data.mcp_server_count ?? servers.length) + " found", '<div class="file-list">' + (serverRows || '<div class="empty">No MCP server names discovered.</div>') + '</div>', false) +
920
+ '</div></article>';
921
+ }
922
+
923
+ function renderWorkspaces(data) {
924
+ const spaces = Array.isArray(data.workspaces) ? data.workspaces : [];
925
+ const rows = spaces.map((workspace) =>
926
+ '<div class="file-row"><span class="file-code">ws</span><span class="file-name">' + esc((workspace?.id || "workspace") + " — " + (workspace?.root || "")) + '</span></div>'
927
+ ).join("");
928
+ return '<article class="card">' + header(data, pill((data.count ?? spaces.length) + " open", "info")) +
929
+ '<div class="body"><div class="file-list">' + (rows || '<div class="empty">No workspaces opened yet.</div>') + '</div></div></article>';
930
+ }
931
+
932
+ function renderProjects(data) {
933
+ const projects = Array.isArray(data.projects) ? data.projects : [];
934
+ const rows = projects.map((project) => {
935
+ const sources = Array.isArray(project?.sources) ? project.sources.join(", ") : "local";
936
+ return '<button type="button" class="project-button" data-project-id="' + esc(project?.project_id || "") + '" data-project-name="' + esc(project?.name || "project") + '">' +
937
+ '<span class="project-main"><span class="project-name">' + esc(project?.name || "Project") + '</span>' +
938
+ '<span class="project-path">' + esc(project?.root || "") + ' · ' + esc(sources) + '</span></span>' +
939
+ '<span class="project-action">' + (project?.selected ? "Selected" : "Use project") + '</span></button>';
940
+ }).join("");
941
+ const roots = Array.isArray(data.allowed_roots) ? data.allowed_roots : [];
942
+ return '<article class="card">' + header(data, pill(projects.length + " projects", "info") + pill("one broker")) +
943
+ '<div class="body"><div class="section-label">Choose where this chat works</div>' +
944
+ '<div class="project-list">' + (rows || '<div class="empty">No projects found. Add a projects directory with --allow-root and refresh.</div>') + '</div>' +
945
+ fold("Synchronized roots", roots.length + " roots", compactRows(roots, "root", 12), false) +
946
+ '<div class="empty" id="project-status">The selected project stays bound to this ChatGPT conversation.</div>' +
947
+ '</div></article>';
948
+ }
949
+
950
+ function renderServerConfig(data) {
951
+ const blocked = Array.isArray(data.blockedGlobs) ? data.blockedGlobs : [];
952
+ const allowed = Array.isArray(data.allowedRoots) ? data.allowedRoots : [];
953
+ const bashSession = data.bashSessionId || data.bash_session_id || "";
954
+ const bashSessionRequired = Boolean(data.requireBashSession || data.require_bash_session);
955
+ const rootRows = [
956
+ '<div class="file-row"><span class="file-code">root</span><span class="file-name">' + esc(data.defaultRoot || "-") + '</span></div>',
957
+ '<div class="file-row"><span class="file-code">url</span><span class="file-name">' + esc((data.host || "127.0.0.1") + ":" + (data.port || "-")) + '</span></div>',
958
+ '<div class="file-row"><span class="file-code">ui</span><span class="file-name">' + esc(data.widgetDomain || "-") + '</span></div>',
959
+ bashSession ? '<div class="file-row"><span class="file-code">bash</span><span class="file-name">' + esc("session " + bashSession + (bashSessionRequired ? " required" : "")) + '</span></div>' : ""
960
+ ].join("");
961
+ const allowedRows = allowed.map((root) =>
962
+ '<div class="file-row"><span class="file-code">allow</span><span class="file-name">' + esc(root) + '</span></div>'
963
+ ).join("");
964
+ const blockedRows = blocked.slice(0, 24).map((pattern) =>
965
+ '<div class="file-row"><span class="file-code">block</span><span class="file-name">' + esc(pattern) + '</span></div>'
966
+ ).join("");
967
+ const limits = [
968
+ summaryItem("Read", data.maxReadBytes ?? "-"),
969
+ summaryItem("Write", data.maxWriteBytes ?? "-"),
970
+ summaryItem("Output", data.maxOutputBytes ?? "-")
971
+ ].join("");
972
+ return '<article class="card">' + header(data, [
973
+ pill("tools " + (data.toolMode || "-"), "info"),
974
+ pill("bash " + (data.bashMode || "-")),
975
+ bashSession ? pill("session " + bashSession, bashSessionRequired ? "warn" : "info") : "",
976
+ pill(data.authEnabled ? "auth on" : "auth off", data.authEnabled ? "good" : "warn")
977
+ ].join("")) + '<div class="body">' +
978
+ '<div class="summary">' +
979
+ summaryItem("Write", data.writeMode || "-") +
980
+ summaryItem("Bash", data.bashMode || "-") +
981
+ summaryItem("Session", bashSession ? bashSession + (bashSessionRequired ? " required" : "") : "-") +
982
+ summaryItem("Tools", data.toolMode || "-") +
983
+ '</div>' +
984
+ '<div class="section-label">Runtime</div><div class="file-list">' + rootRows + '</div>' +
985
+ fold("Allowed roots", allowed.length + " roots", '<div class="file-list">' + (allowedRows || '<div class="empty">No roots configured.</div>') + '</div>', false) +
986
+ fold("Limits", "", '<div class="summary">' + limits + '</div>', false) +
987
+ fold("Blocked paths", blocked.length + " patterns", '<div class="file-list">' + (blockedRows || '<div class="empty">No blocked globs configured.</div>') + '</div>', false) +
988
+ fold("Raw config", "", codebox("config", esc(truncate(JSON.stringify(data || {}, null, 2), 8000)), ""), false) +
989
+ '</div></article>';
990
+ }
991
+
992
+ function renderStatus(data) {
993
+ const files = Array.isArray(data.changed_files) ? data.changed_files : [];
994
+ const rows = files.slice(0, 14).map((line) => {
995
+ const status = String(line).slice(0, 2).trim() || "?";
996
+ const name = String(line).slice(2).trim() || String(line);
997
+ return '<div class="file-row"><span class="file-code">' + esc(status) + '</span><span class="file-name">' + esc(name) + '</span></div>';
998
+ }).join("");
999
+ const state = data.status_error ? '<div class="empty">' + esc(data.status_error) + '</div>' : rows || '<div class="empty">Working tree clean.</div>';
1000
+ return '<article class="card">' + header(data, pill(files.length ? files.length + " changed" : "clean", files.length ? "info" : "good")) +
1001
+ '<div class="body"><div class="file-list">' + state + '</div>' +
1002
+ fold("Raw status", countLines(data.status) + " lines", codebox("git status", esc(previewLines(data.status, 40)), ""), false) +
1003
+ '</div></article>';
1004
+ }
1005
+
1006
+ function renderTextSummary(data, label) {
1007
+ const files = Array.isArray(data.files) ? data.files : Array.isArray(data.ai_context_files) ? data.ai_context_files : [];
1008
+ const preview = data.preview || data.text || data.status || "";
1009
+ const rows = files.slice(0, 14).map((file) =>
1010
+ '<div class="file-row"><span class="file-code">file</span><span class="file-name">' + esc(file) + '</span></div>'
1011
+ ).join("");
1012
+ return '<article class="card">' + header(data, pill(files.length + " files", "info")) +
1013
+ '<div class="body">' +
1014
+ (rows ? '<div class="file-list">' + rows + '</div>' : '<div class="empty">No files listed.</div>') +
1015
+ fold(label || "Preview", countLines(preview) + " lines", codebox(label || "preview", esc(previewLines(preview, 40)), ""), false) +
1016
+ '</div></article>';
1017
+ }
1018
+
1019
+ function renderGeneric(data) {
1020
+ const keys = Object.keys(data || {}).filter((key) => !key.startsWith("codexflow_"));
1021
+ const metrics = keys.slice(0, 3).map((key) => metric(key, typeof data[key] === "object" ? JSON.stringify(data[key]) : data[key])).join("");
1022
+ return '<article class="card">' + header(data, pill("structured", "info")) +
1023
+ '<div class="body">' + (metrics ? '<div class="metrics">' + metrics + '</div>' : '') +
1024
+ codebox("structured output", esc(truncate(JSON.stringify(data || {}, null, 2))), "") +
1025
+ '</div></article>';
1026
+ }
1027
+
1028
+ function isPlaceholderPayload(data) {
1029
+ if (!data || typeof data !== "object") return true;
1030
+ const keys = Object.keys(data);
1031
+ return !keys.length || (keys.length === 1 && data.codexflow_tool === "codexflow");
1032
+ }
1033
+
1034
+ function renderPending() {
1035
+ root.innerHTML = [
1036
+ '<article class="card pending">',
1037
+ '<div class="rail"></div>',
1038
+ '<header class="head">',
1039
+ '<span class="glyph">C</span>',
1040
+ '<div class="headline"><div class="title">CodexFlow</div><div class="subtitle">Waiting for tool result...</div></div>',
1041
+ '<span class="pill info">waiting</span>',
1042
+ '</header>',
1043
+ '<div class="skeleton"><span></span><span></span><span></span></div>',
1044
+ '</article>'
1045
+ ].join("");
1046
+ }
1047
+
1048
+ function render(data) {
1049
+ if (isPlaceholderPayload(data)) {
1050
+ renderPending();
1051
+ return;
1052
+ }
1053
+ const tool = data.codexflow_tool;
1054
+ if (tool === "server_config") {
1055
+ root.innerHTML = renderServerConfig(data);
1056
+ } else if (tool === "codexflow_self_test") {
1057
+ root.innerHTML = renderSelfTest(data);
1058
+ } else if (tool === "codexflow_inventory") {
1059
+ root.innerHTML = renderInventory(data);
1060
+ } else if (tool === "list_projects") {
1061
+ root.innerHTML = renderProjects(data);
1062
+ } else if (tool === "list_workspaces") {
1063
+ root.innerHTML = renderWorkspaces(data);
1064
+ } else if (tool === "select_project" || tool === "open_current_workspace" || tool === "open_workspace" || tool === "workspace_snapshot") {
1065
+ root.innerHTML = renderWorkspace(data);
1066
+ } else if (tool === "inspect_workspace") {
1067
+ root.innerHTML = renderWorkspaceAnalysis(data);
1068
+ } else if (tool === "git_status") {
1069
+ root.innerHTML = renderStatus(data);
1070
+ } else if (tool === "show_changes") {
1071
+ root.innerHTML = data.analysis ? renderChangeAnalysis(data) : renderChanges(data);
1072
+ } else if (tool === "handoff_to_agent" || tool === "handoff_to_codex") {
1073
+ root.innerHTML = renderHandoff(data);
1074
+ } else if (tool === "write" || tool === "edit" || tool === "apply_patch" || tool === "git_diff" || tool === "export_pro_context" || tool === "read") {
1075
+ root.innerHTML = renderFile(data);
1076
+ } else if (tool === "bash") {
1077
+ root.innerHTML = renderBash(data);
1078
+ } else if (tool === "search") {
1079
+ root.innerHTML = data.analysis ? renderStructuredSearch(data) : renderSearch(data);
1080
+ } else if (tool === "read_handoff") {
1081
+ root.innerHTML = renderTextSummary(data, "handoff");
1082
+ } else if (tool === "codex_context") {
1083
+ root.innerHTML = renderTextSummary(data, "context");
1084
+ } else {
1085
+ root.innerHTML = renderGeneric(data);
1086
+ }
1087
+ }
1088
+
1089
+ function extractStructuredContent(value) {
1090
+ if (!value || typeof value !== "object") return {};
1091
+ if (value.codexflow_tool || value.codexflow_title) return value;
1092
+ const candidates = [
1093
+ value.structuredContent,
1094
+ value.toolOutput?.structuredContent,
1095
+ value.toolOutput,
1096
+ value.toolResponseMetadata?.structuredContent,
1097
+ value.mcp_tool_result?.structuredContent,
1098
+ value.call_tool_result?.structuredContent,
1099
+ value.result?.structuredContent
1100
+ ];
1101
+ for (const candidate of candidates) {
1102
+ if (candidate && typeof candidate === "object") return candidate;
1103
+ }
1104
+ return {};
1105
+ }
1106
+
1107
+ render(extractStructuredContent(window.openai?.toolOutput || window.openai?.toolResponseMetadata || {}));
1108
+
1109
+ root.addEventListener("click", async (event) => {
1110
+ const button = event.target?.closest?.("[data-project-id]");
1111
+ if (!button) return;
1112
+ const projectId = button.getAttribute("data-project-id") || "";
1113
+ const projectName = button.getAttribute("data-project-name") || "project";
1114
+ const status = document.getElementById("project-status");
1115
+ const buttons = root.querySelectorAll("[data-project-id]");
1116
+ buttons.forEach((item) => { item.disabled = true; });
1117
+ if (status) status.textContent = "Selecting " + projectName + "...";
1118
+ try {
1119
+ if (!window.openai?.callTool) throw new Error("Project selection is unavailable in this client.");
1120
+ await window.openai.callTool("select_project", { project_id: projectId });
1121
+ await window.openai.setWidgetState?.({ selectedProjectId: projectId, selectedProjectName: projectName });
1122
+ button.querySelector(".project-action").textContent = "Selected";
1123
+ if (status) status.textContent = projectName + " is selected. This chat now routes file, git, and terminal tools there.";
1124
+ await window.openai.sendFollowUpMessage?.({
1125
+ prompt: "Use the selected CodexFlow project " + projectName + " (project_id " + projectId + ") for this coding conversation. Inspect its repository instructions and relevant advertised skills before making changes.",
1126
+ scrollToBottom: true
1127
+ });
1128
+ } catch (error) {
1129
+ buttons.forEach((item) => { item.disabled = false; });
1130
+ if (status) status.textContent = error instanceof Error ? error.message : "Could not select the project.";
1131
+ }
1132
+ });
1133
+
1134
+ window.addEventListener("openai:set_globals", (event) => {
1135
+ render(extractStructuredContent(
1136
+ event.detail?.globals?.toolOutput ||
1137
+ event.detail?.globals?.toolResponseMetadata ||
1138
+ event.detail ||
1139
+ window.openai?.toolOutput ||
1140
+ window.openai?.toolResponseMetadata ||
1141
+ {}
1142
+ ));
1143
+ }, { passive: true });
1144
+
1145
+ window.addEventListener("message", (event) => {
1146
+ if (event.source !== window.parent) return;
1147
+ const message = event.data;
1148
+ if (!message || message.jsonrpc !== "2.0") return;
1149
+ if (message.method === "ui/notifications/tool-result") {
1150
+ render(extractStructuredContent(message.params || {}));
1151
+ }
1152
+ }, { passive: true });
1153
+ </script>
1154
+ `.trim();
1155
+ //# sourceMappingURL=toolCardWidget.js.map