session-viewer-plugin 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ .dir-manager[data-v-1a66345f]{background:#fff;border-radius:12px;box-shadow:0 1px 3px #00000014;margin-bottom:24px}.dir-header[data-v-1a66345f]{display:flex;align-items:center;gap:12px;width:100%;padding:16px;border:none;background:transparent;cursor:pointer;font-size:14px;text-align:left;transition:background .15s}.dir-header[data-v-1a66345f]:hover{background:#fafafa}.dir-icon[data-v-1a66345f]{width:18px;height:18px;color:#fa8c16}.dir-title[data-v-1a66345f]{font-weight:500;color:#333}.dir-toggle[data-v-1a66345f]{margin-left:auto;color:#999;width:16px;height:16px}.dir-body[data-v-1a66345f]{padding:0 16px 16px}.dir-item[data-v-1a66345f]{display:flex;align-items:center;gap:8px;padding:6px 0;font-size:13px}.dir-item-icon[data-v-1a66345f]{width:14px;height:14px;flex-shrink:0;color:#8c8c8c}.dir-item-path[data-v-1a66345f]{font-family:monospace;font-size:12px;color:#595959;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dir-badge[data-v-1a66345f]{flex-shrink:0;background:#f0f0f0;padding:1px 6px;border-radius:3px;font-size:11px;color:#8c8c8c}.dir-remove[data-v-1a66345f]{flex-shrink:0;background:none;border:none;color:#bfbfbf;cursor:pointer;padding:2px;transition:color .15s;display:flex;align-items:center}.dir-remove[data-v-1a66345f]:hover{color:#ff4d4f}.dir-remove-icon[data-v-1a66345f]{width:14px;height:14px}.dir-add[data-v-1a66345f]{display:flex;align-items:center;gap:8px;padding-top:12px}.dir-input[data-v-1a66345f]{flex:1;padding:6px 12px;border:1px solid #d9d9d9;border-radius:6px;font-family:monospace;font-size:13px;outline:none;transition:border-color .15s}.dir-input[data-v-1a66345f]:focus{border-color:#1890ff}.dir-add-btn[data-v-1a66345f]{padding:6px 16px;border:none;border-radius:6px;background:#1890ff;color:#fff;font-size:13px;cursor:pointer;white-space:nowrap;transition:background .15s}.dir-add-btn[data-v-1a66345f]:hover:not(:disabled){background:#40a9ff}.dir-add-btn[data-v-1a66345f]:disabled{opacity:.4;cursor:not-allowed}.session-card[data-v-c4930a9a]{background:#fff;border-radius:12px;padding:20px;box-shadow:0 1px 3px #00000014;cursor:pointer;transition:all .2s;position:relative;min-width:0}.session-card[data-v-c4930a9a]:hover{box-shadow:0 4px 12px #0000001f;transform:translateY(-1px)}.delete-btn[data-v-c4930a9a]{position:absolute;top:5px;right:5px;background:none;border:none;cursor:pointer;padding:4px;border-radius:4px;color:#bfbfbf;opacity:0;transition:all .15s}.session-card:hover .delete-btn[data-v-c4930a9a]{opacity:1}.delete-btn[data-v-c4930a9a]:hover{color:#ff4d4f;background:#fff1f0}.delete-btn.deleting[data-v-c4930a9a]{opacity:1;color:#1890ff;cursor:not-allowed}@keyframes spin-c4930a9a{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.spin[data-v-c4930a9a]{animation:spin-c4930a9a 1s linear infinite}.delete-icon[data-v-c4930a9a]{width:16px;height:16px}.card-header[data-v-c4930a9a]{display:flex;align-items:flex-start;justify-content:space-between;margin-bottom:12px}.card-agent[data-v-c4930a9a]{display:flex;align-items:center;gap:8px}.agent-icon[data-v-c4930a9a]{width:16px;height:16px;color:#7c3aed}.agent-name[data-v-c4930a9a]{font-size:14px;font-weight:500;color:#333}.card-model[data-v-c4930a9a]{background:#f0f0f0;padding:2px 8px;border-radius:4px;font-size:12px;color:#595959;margin-right:12px}.card-id[data-v-c4930a9a]{font-family:monospace;font-size:12px;color:#8c8c8c;margin-bottom:8px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.card-message[data-v-c4930a9a]{font-size:13px;color:#595959;margin-bottom:6px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.card-dir[data-v-c4930a9a]{font-family:monospace;font-size:11px;color:#bfbfbf;margin-bottom:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.card-stats[data-v-c4930a9a]{display:grid;grid-template-columns:1fr 1fr;gap:10px}.stat-item[data-v-c4930a9a]{display:flex;align-items:center;gap:6px;font-size:13px;color:#595959}.stat-icon[data-v-c4930a9a]{width:14px;height:14px;color:#8c8c8c}.card-token-bar[data-v-c4930a9a]{margin-top:12px;padding-top:12px;border-top:1px solid #f0f0f0}.token-bar-track[data-v-c4930a9a]{display:flex;height:6px;border-radius:3px;overflow:hidden;background:#f0f0f0}.token-bar-segment[data-v-c4930a9a]{height:100%;transition:width .3s}.token-bar-legend[data-v-c4930a9a]{display:flex;align-items:center;gap:12px;margin-top:6px}.legend-item[data-v-c4930a9a]{display:flex;align-items:center;gap:4px;font-size:10px;color:#8c8c8c}.legend-dot[data-v-c4930a9a]{display:inline-block;width:6px;height:6px;border-radius:2px}.legend-date[data-v-c4930a9a]{margin-left:auto;font-size:10px;color:#bfbfbf}.card-footer[data-v-c4930a9a]{margin-top:12px;padding-top:12px;border-top:1px solid #f0f0f0;display:flex;justify-content:space-between}.card-date[data-v-c4930a9a]{font-size:12px;color:#bfbfbf}.home-sidebar[data-v-e341549f]{width:100%;flex-shrink:0;border-right:1px solid #f0f0f0;background:#fff;display:flex;flex-direction:column;overflow:hidden}.sidebar-overview[data-v-e341549f]{flex-shrink:0;padding:12px 16px;border:none;background:transparent;text-align:left;font-size:14px;cursor:pointer;color:#595959;transition:all .15s;display:flex;align-items:center;gap:8px}.sidebar-overview[data-v-e341549f]:hover{background:#fafafa}.sidebar-overview.active[data-v-e341549f]{background:#1890ff14;color:#1890ff;font-weight:500}.sidebar-overview-icon[data-v-e341549f]{width:16px;height:16px;color:#d9d9d9}.sidebar-overview-icon.star-filled[data-v-e341549f]{color:#faad14}.sidebar-title[data-v-e341549f]{flex-shrink:0;padding:12px 16px;border-bottom:1px solid #f0f0f0;border-top:1px solid #f0f0f0;font-size:14px;font-weight:600;color:#333}.sidebar-sessions[data-v-e341549f]{flex:1;overflow-y:auto;padding:8px 0}.month-section[data-v-e341549f]{margin-bottom:4px}.month-header[data-v-e341549f]{display:flex;align-items:center;gap:8px;width:100%;padding:8px 16px;border:none;background:transparent;font-size:13px;font-weight:500;color:#333;cursor:pointer;text-align:left;transition:background .15s}.month-header[data-v-e341549f]:hover{background:#fafafa}.month-label[data-v-e341549f]{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.month-count[data-v-e341549f]{font-size:12px;color:#d9d9d9}.month-sessions[data-v-e341549f]{padding:2px 0}.session-item[data-v-e341549f]{display:flex;align-items:center;gap:8px;width:100%;padding:5px 16px 5px 40px;cursor:pointer;transition:all .15s}.session-item[data-v-e341549f]:hover{background:#fafafa}.session-text[data-v-e341549f]{flex:1;font-size:12px;color:#595959;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:20px}.star-btn[data-v-e341549f]{flex-shrink:0;display:flex;align-items:center;justify-content:center;width:24px;height:24px;border:none;background:transparent;cursor:pointer;border-radius:4px;color:#d9d9d9;transition:all .15s}.star-btn[data-v-e341549f]:hover{background:#f0f0f0;color:#faad14}.star-btn.star-active[data-v-e341549f]{color:#faad14}.star-icon[data-v-e341549f]{width:14px;height:14px}.connection-banner[data-v-c38554e6]{display:flex;align-items:center;gap:12px;padding:10px 14px;border:1px solid #f3d19e;border-radius:6px;background:#fdf6ec;color:#b88230;flex-shrink:0}.banner-icon[data-v-c38554e6]{font-size:18px;line-height:1;flex-shrink:0}.banner-body[data-v-c38554e6]{flex:1;min-width:0}.banner-title[data-v-c38554e6]{font-size:14px;font-weight:600;margin:0}.banner-detail[data-v-c38554e6]{font-size:12px;margin:4px 0 0;color:#a4752a;word-break:break-all}.banner-retry[data-v-c38554e6]{flex-shrink:0;border:1px solid #d9b374;background:transparent;color:#b88230;border-radius:4px;padding:5px 14px;font-size:13px;cursor:pointer}.banner-retry[data-v-c38554e6]:hover{background:#f7e6c6}.session-viewer[data-v-fd683301]{display:flex;flex-direction:column;height:100vh}.viewer-header[data-v-fd683301]{flex-shrink:0;background:#fff;border-bottom:1px solid #f0f0f0;padding:12px 24px;display:flex;align-items:center;gap:16px;position:sticky;top:0;z-index:10}.sidebar-toggle[data-v-fd683301]{display:flex;align-items:center;padding:4px;border:none;border-radius:6px;background:transparent;cursor:pointer;color:#8c8c8c;transition:all .15s}.sidebar-toggle[data-v-fd683301]:hover{background:#f0f0f0;color:#333}.toggle-icon[data-v-fd683301]{width:18px;height:18px}.header-title[data-v-fd683301]{display:flex;align-items:center;gap:12px}.header-icon[data-v-fd683301]{font-size:20px}.header-title h1[data-v-fd683301]{font-size:18px;font-weight:600;color:#333;margin:0}.header-sub[data-v-fd683301]{font-size:13px;color:#8c8c8c;margin:0}.header-spacer[data-v-fd683301]{flex:1}.header-search[data-v-fd683301]{display:flex;align-items:center;flex-shrink:0}.search-input-wrap[data-v-fd683301]{position:relative;display:flex;align-items:center}.header-search-input[data-v-fd683301]{width:260px;padding:7px 30px 7px 32px;border:1px solid #e8e8e8;border-radius:8px;font-size:13px;outline:none;transition:border-color .15s}.header-search-input[data-v-fd683301]:focus{border-color:#1890ff}.search-icon-inner[data-v-fd683301]{position:absolute;left:10px;width:15px;height:15px;color:#bfbfbf;pointer-events:none}.search-clear[data-v-fd683301]{position:absolute;right:8px;background:none;border:none;cursor:pointer;padding:2px;display:flex;align-items:center}.clear-icon[data-v-fd683301]{width:13px;height:13px;color:#bfbfbf}.header-action-btn[data-v-fd683301]{display:flex;align-items:center;padding:4px;border:none;border-radius:6px;background:transparent;cursor:pointer;color:#8c8c8c;transition:all .15s}.header-action-btn[data-v-fd683301]:hover{background:#f0f0f0;color:#333}.action-icon[data-v-fd683301]{width:18px;height:18px}.viewer-body[data-v-fd683301]{flex:1;display:flex;overflow:hidden}.viewer-body.sidebar-collapsed .viewer-content[data-v-fd683301]{margin-left:0}.sidebar-resize-handle[data-v-fd683301]{width:4px;flex-shrink:0;cursor:col-resize;background:transparent;transition:background .15s;margin-left:-2px;margin-right:-2px;position:relative;z-index:5}.sidebar-resize-handle[data-v-fd683301]:hover,.sidebar-resize-handle[data-v-fd683301]:active{background:#1890ff}.viewer-content[data-v-fd683301]{flex:1;overflow-y:auto;padding:24px}.card-grid[data-v-fd683301]{display:grid;grid-template-columns:repeat(3,1fr);gap:16px}@media(max-width:1024px){.card-grid[data-v-fd683301]{grid-template-columns:repeat(2,1fr)}}@media(max-width:640px){.card-grid[data-v-fd683301]{grid-template-columns:1fr}}.skeleton-card[data-v-fd683301]{background:#fff;border-radius:12px;padding:20px;box-shadow:0 1px 3px #00000014}.skeleton[data-v-fd683301]{background:linear-gradient(90deg,#f0f0f0 25%,#e8e8e8,#f0f0f0 75%);background-size:200% 100%;animation:shimmer-fd683301 1.5s infinite;border-radius:4px}.skeleton-header[data-v-fd683301]{height:20px;width:60%;margin-bottom:12px}.skeleton-id[data-v-fd683301]{height:14px;width:80%;margin-bottom:12px}.skeleton-stats[data-v-fd683301]{height:16px;width:100%}@keyframes shimmer-fd683301{0%{background-position:200% 0}to{background-position:-200% 0}}.empty-state[data-v-fd683301]{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:80px 0;color:#8c8c8c}.empty-icon[data-v-fd683301]{font-size:48px;margin-bottom:16px;opacity:.3}.empty-title[data-v-fd683301]{font-size:16px;font-weight:500;color:#595959;margin:0}.empty-sub[data-v-fd683301]{font-size:13px;color:#bfbfbf;margin:4px 0 0}.status-badge[data-v-4097ac6f]{display:inline-flex;align-items:center;border-radius:9999px;padding:2px 8px;font-size:12px;font-weight:500}.status-green[data-v-4097ac6f]{background:#f6ffed;color:#52c41a}.status-red[data-v-4097ac6f]{background:#fff1f0;color:#ff4d4f}.status-yellow[data-v-4097ac6f]{background:#fff7e6;color:#fa8c16}.status-blue[data-v-4097ac6f]{background:#e6f7ff;color:#1890ff}.status-gray[data-v-4097ac6f]{background:#f5f5f5;color:#8c8c8c}.step-card[data-v-aef727b1]{background:#fff;border-radius:12px;box-shadow:0 1px 3px #00000014;overflow:hidden;border-left:3px solid transparent;transition:border-color .2s,box-shadow .2s}.step-card.focused[data-v-aef727b1]{border-left-color:#1890ff;box-shadow:0 2px 8px #1890ff26}.step-detail-header[data-v-aef727b1]{display:flex;align-items:center;gap:12px;background:#fafafa;padding:12px 16px;flex-wrap:wrap}.step-detail-num[data-v-aef727b1]{width:24px;height:24px;border-radius:50%;background:#e6f7ff;color:#1890ff;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700}.step-detail-title[data-v-aef727b1]{font-size:14px;font-weight:500;color:#333}.step-detail-dur[data-v-aef727b1],.step-detail-tokens[data-v-aef727b1],.step-detail-tools[data-v-aef727b1]{font-size:12px;color:#bfbfbf;font-family:monospace}.step-detail-time[data-v-aef727b1]{margin-left:auto;font-size:12px;color:#8c8c8c}.meta-icon[data-v-aef727b1]{width:12px;height:12px;vertical-align:-2px;margin-right:2px}.step-detail-body[data-v-aef727b1]{padding:16px}.section-label[data-v-aef727b1]{display:flex;align-items:center;gap:6px;font-size:12px;font-weight:600;text-transform:uppercase;color:#8c8c8c;margin:0 0 8px;cursor:pointer;-webkit-user-select:none;user-select:none}.section-label[data-v-aef727b1]:hover{color:#595959}.reasoning-label[data-v-aef727b1]{color:#722ed1}.reasoning-label[data-v-aef727b1]:hover{color:#531dab}.label-toggle[data-v-aef727b1]{font-size:10px;width:12px}.label-meta[data-v-aef727b1]{font-weight:400;font-size:11px;color:#bfbfbf;text-transform:none}.step-text[data-v-aef727b1],.step-reasoning[data-v-aef727b1]{background:#fafafa;border-radius:8px;padding:12px;font-family:SF Mono,Menlo,Monaco,Courier New,monospace;font-size:13px;line-height:1.6;color:#333;white-space:pre-wrap;word-break:break-word;max-height:384px;overflow-y:auto;margin:0 0 16px}.step-reasoning[data-v-aef727b1]{background:#f9f0ff;color:#531dab}.tool-call-card[data-v-aef727b1]{background:#fafafa;border-radius:8px;padding:12px;margin-bottom:8px}.tool-call-header[data-v-aef727b1]{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.tool-call-name[data-v-aef727b1]{font-size:14px;font-weight:500;color:#333}.tool-call-dur[data-v-aef727b1]{font-size:12px;color:#8c8c8c}.tool-toggle[data-v-aef727b1]{margin-left:auto;font-size:12px;color:#1890ff;background:none;border:none;cursor:pointer}.tool-toggle[data-v-aef727b1]:hover{text-decoration:underline}.tool-call-input[data-v-aef727b1],.tool-call-output[data-v-aef727b1]{margin-top:8px}.tool-call-input pre[data-v-aef727b1],.tool-call-output pre[data-v-aef727b1]{background:#fff;border:1px solid #e8e8e8;border-radius:6px;padding:12px;font-family:SF Mono,Menlo,Monaco,Courier New,monospace;font-size:12px;line-height:1.5;color:#333;white-space:pre-wrap;word-break:break-word;max-height:300px;overflow-y:auto;margin:0}.step-detail-footer[data-v-aef727b1]{display:flex;gap:24px;padding-top:12px;margin-top:4px;border-top:1px solid #f0f0f0;font-size:12px;color:#8c8c8c}.filter-bar[data-v-adac3c66]{background:#fff;border-radius:10px;padding:10px 14px;box-shadow:0 1px 3px #00000014}.filter-top-row[data-v-adac3c66]{display:flex;align-items:center;gap:10px}.search-input-wrap[data-v-adac3c66]{width:240px;flex-shrink:0;position:relative;display:flex;align-items:center}.search-icon[data-v-adac3c66]{position:absolute;left:10px;width:15px;height:15px;color:#bfbfbf;pointer-events:none}.search-input[data-v-adac3c66]{width:100%;padding:7px 30px 7px 32px;border:1px solid #e8e8e8;border-radius:8px;font-size:13px;outline:none;transition:border-color .15s}.search-input[data-v-adac3c66]:focus{border-color:#1890ff}.search-clear[data-v-adac3c66]{position:absolute;right:8px;background:none;border:none;cursor:pointer;padding:2px;display:flex;align-items:center}.clear-icon[data-v-adac3c66]{width:13px;height:13px;color:#bfbfbf}.tool-chips-inline[data-v-adac3c66]{display:flex;align-items:center;gap:6px;flex:1;min-width:0;flex-wrap:wrap}.tool-chip[data-v-adac3c66]{display:inline-flex;align-items:center;gap:4px;padding:4px 10px;background:#f0f5ff;border:1px solid #d6e4ff;border-radius:6px;font-size:12px;color:#1890ff;white-space:nowrap}.chip-remove[data-v-adac3c66]{background:none;border:none;cursor:pointer;padding:0;display:flex;align-items:center}.chip-x[data-v-adac3c66]{width:11px;height:11px;color:#1890ff}.tool-dropdown-wrap[data-v-adac3c66]{position:relative}.add-tool-btn[data-v-adac3c66]{padding:4px 10px;border:1px dashed #d9d9d9;border-radius:6px;background:#fff;font-size:12px;color:#8c8c8c;cursor:pointer;transition:all .15s;white-space:nowrap}.add-tool-btn[data-v-adac3c66]:hover{border-color:#1890ff;color:#1890ff}.tool-dropdown[data-v-adac3c66]{position:absolute;top:100%;left:0;margin-top:4px;background:#fff;border:1px solid #e8e8e8;border-radius:8px;box-shadow:0 4px 12px #0000001a;z-index:10;min-width:140px;max-height:200px;overflow-y:auto}.tool-dropdown-item[data-v-adac3c66]{display:block;width:100%;padding:6px 12px;border:none;background:transparent;font-size:12px;color:#333;cursor:pointer;text-align:left;transition:background .15s}.tool-dropdown-item[data-v-adac3c66]:hover{background:#f0f5ff}.tool-dropdown-empty[data-v-adac3c66]{padding:6px 12px;font-size:12px;color:#bfbfbf}.filter-actions[data-v-adac3c66]{display:flex;align-items:center;gap:8px;flex-shrink:0;margin-left:auto}.filter-toggle[data-v-adac3c66]{display:flex;align-items:center;gap:5px;padding:5px 10px;border:1px solid #e8e8e8;border-radius:8px;background:#fff;font-size:12px;color:#595959;cursor:pointer;transition:all .15s}.filter-toggle[data-v-adac3c66]:hover{border-color:#1890ff;color:#1890ff}.filter-toggle.active[data-v-adac3c66]{background:#f0f5ff;border-color:#1890ff;color:#1890ff}.toggle-icon[data-v-adac3c66]{width:13px;height:13px}.filter-badge[data-v-adac3c66]{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;border-radius:50%;background:#1890ff;color:#fff;font-size:9px;font-weight:600}.reset-btn[data-v-adac3c66]{padding:5px 10px;border:1px solid #ff4d4f;border-radius:8px;background:#fff;font-size:12px;color:#ff4d4f;cursor:pointer;transition:all .15s}.reset-btn[data-v-adac3c66]:hover{background:#fff1f0}.filter-count[data-v-adac3c66]{font-size:12px;color:#bfbfbf;white-space:nowrap}.filter-options[data-v-adac3c66]{margin-top:10px;padding-top:10px;border-top:1px solid #f0f0f0;display:flex;gap:20px;flex-wrap:wrap}.filter-row[data-v-adac3c66]{display:flex;align-items:center;gap:6px}.filter-label[data-v-adac3c66]{font-size:12px;font-weight:500;color:#595959;white-space:nowrap}.range-input[data-v-adac3c66]{width:80px;padding:4px 8px;border:1px solid #e8e8e8;border-radius:6px;font-size:12px;outline:none;transition:border-color .15s}.range-input[data-v-adac3c66]:focus{border-color:#1890ff}.range-sep[data-v-adac3c66]{color:#d9d9d9;font-size:12px}.range-unit[data-v-adac3c66]{font-size:12px;color:#8c8c8c}.has-tools-group[data-v-adac3c66]{display:flex;gap:0;border:1px solid #e8e8e8;border-radius:6px;overflow:hidden}.has-tools-btn[data-v-adac3c66]{padding:4px 12px;border:none;background:#fff;font-size:12px;color:#595959;cursor:pointer;transition:all .15s}.has-tools-btn[data-v-adac3c66]:not(:last-child){border-right:1px solid #e8e8e8}.has-tools-btn.active[data-v-adac3c66]{background:#1890ff;color:#fff}.has-tools-btn[data-v-adac3c66]:hover:not(.active){background:#fafafa}.chart-section[data-v-2979710d]{background:#fff;border-radius:12px;padding:20px;box-shadow:0 1px 3px #00000014}.section-title[data-v-2979710d]{font-size:18px;font-weight:600;color:#333;margin:0 0 16px}.chart-container[data-v-2979710d]{position:relative}.gantt-legend[data-v-2979710d]{display:flex;align-items:center;gap:16px;margin-top:12px;padding-top:12px;border-top:1px solid #f0f0f0}.legend-item[data-v-2979710d]{display:flex;align-items:center;gap:6px;font-size:12px;color:#8c8c8c}.legend-dot[data-v-2979710d]{display:inline-block;width:10px;height:10px;border-radius:2px}.chart-section[data-v-7a01cfe3]{background:#fff;border:1px solid #e8e8e8;border-radius:8px;padding:20px;margin-bottom:16px}.section-title[data-v-7a01cfe3]{font-size:16px;font-weight:600;color:#333;margin-bottom:16px}.chart-container[data-v-7a01cfe3]{position:relative}.chart-section[data-v-dc55b693]{background:#fff;border:1px solid #e8e8e8;border-radius:8px;padding:20px;margin-bottom:16px}.section-title[data-v-dc55b693]{font-size:16px;font-weight:600;color:#333;margin-bottom:16px}.chart-container[data-v-dc55b693]{height:300px;position:relative}.chart-section[data-v-3aa0ed4e]{background:#fff;border:1px solid #e8e8e8;border-radius:8px;padding:20px;margin-bottom:16px}.section-title[data-v-3aa0ed4e]{font-size:16px;font-weight:600;color:#333;margin-bottom:16px}.chart-container[data-v-3aa0ed4e]{position:relative}.session-detail[data-v-6235eb94]{display:flex;flex-direction:column;height:100vh}.detail-header[data-v-6235eb94]{flex-shrink:0;background:#fff;border-bottom:1px solid #f0f0f0;padding:12px 24px;display:flex;align-items:center;gap:16px;position:sticky;top:0;z-index:10}.sidebar-toggle[data-v-6235eb94]{display:flex;align-items:center;padding:4px;border:none;border-radius:6px;background:transparent;cursor:pointer;color:#8c8c8c;transition:all .15s}.sidebar-toggle[data-v-6235eb94]:hover{background:#f0f0f0;color:#333}.toggle-icon[data-v-6235eb94]{width:18px;height:18px}.back-btn[data-v-6235eb94]{display:flex;align-items:center;padding:4px;border:none;border-radius:6px;background:transparent;cursor:pointer;color:#8c8c8c;transition:all .15s}.back-btn[data-v-6235eb94]:hover{background:#f0f0f0;color:#333}.back-icon[data-v-6235eb94]{width:20px;height:20px}.header-info[data-v-6235eb94]{display:flex;align-items:center;gap:12px}.header-spacer[data-v-6235eb94]{flex:1}.header-action-btn[data-v-6235eb94]{display:flex;align-items:center;padding:4px;border:none;border-radius:6px;background:transparent;cursor:pointer;color:#8c8c8c;transition:all .15s}.header-action-btn[data-v-6235eb94]:hover{background:#f0f0f0;color:#333}.action-icon[data-v-6235eb94]{width:18px;height:18px}.header-icon[data-v-6235eb94]{width:20px;height:20px;color:#7c3aed}.header-info h1[data-v-6235eb94]{font-size:18px;font-weight:600;color:#333;margin:0}.header-id[data-v-6235eb94]{font-family:monospace;font-size:12px;color:#8c8c8c;margin:0}.detail-loading[data-v-6235eb94]{flex:1;padding:24px;display:flex;flex-direction:column;gap:16px}.skeleton[data-v-6235eb94]{background:linear-gradient(90deg,#f0f0f0 25%,#e8e8e8,#f0f0f0 75%);background-size:200% 100%;animation:shimmer-6235eb94 1.5s infinite;border-radius:8px}@keyframes shimmer-6235eb94{0%{background-position:200% 0}to{background-position:-200% 0}}.detail-error[data-v-6235eb94]{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;color:#8c8c8c}.error-icon[data-v-6235eb94]{width:48px;height:48px;color:#ff7875;margin-bottom:16px}.detail-body[data-v-6235eb94]{flex:1;display:flex;overflow:hidden}.detail-body.sidebar-collapsed .detail-content[data-v-6235eb94]{margin-left:0}.sidebar-resize-handle[data-v-6235eb94]{width:4px;flex-shrink:0;cursor:col-resize;background:transparent;transition:background .15s;margin-left:-2px;margin-right:-2px;position:relative;z-index:5}.sidebar-resize-handle[data-v-6235eb94]:hover,.sidebar-resize-handle[data-v-6235eb94]:active{background:#1890ff}.detail-sidebar[data-v-6235eb94]{width:100%;flex-shrink:0;border-right:1px solid #f0f0f0;background:#fff;display:flex;flex-direction:column;overflow:hidden}.sidebar-overview[data-v-6235eb94]{flex-shrink:0;padding:12px 16px;border:none;background:transparent;text-align:left;font-size:14px;cursor:pointer;color:#595959;transition:all .15s}.sidebar-overview[data-v-6235eb94]:hover{background:#fafafa}.sidebar-overview.active[data-v-6235eb94]{background:#1890ff14;color:#1890ff;font-weight:500}.sidebar-title[data-v-6235eb94]{flex-shrink:0;padding:12px 16px;border-bottom:1px solid #f0f0f0;border-top:1px solid #f0f0f0;font-size:14px;font-weight:600;color:#333}.sidebar-steps[data-v-6235eb94]{flex:1;overflow-y:auto;padding:8px 0}.agent-section[data-v-6235eb94]{margin-bottom:4px}.agent-header[data-v-6235eb94]{display:flex;align-items:center;gap:8px;width:100%;padding:8px 16px;border:none;background:transparent;font-size:13px;font-weight:500;color:#333;cursor:pointer;text-align:left;transition:background .15s}.agent-header[data-v-6235eb94]:hover{background:#fafafa}.agent-icon-sm[data-v-6235eb94]{width:14px;height:14px;color:#1890ff}.agent-label[data-v-6235eb94]{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.agent-tokens[data-v-6235eb94]{font-family:monospace;font-size:11px;color:#bfbfbf}.agent-count[data-v-6235eb94]{font-size:12px;color:#d9d9d9}.step-item[data-v-6235eb94]{display:flex;align-items:center;gap:8px;width:100%;padding:5px 16px 5px 40px;border:none;background:transparent;font-size:12px;line-height:20px;color:#595959;cursor:pointer;text-align:left;transition:all .15s}.step-item[data-v-6235eb94]:hover{background:#fafafa}.step-item.active[data-v-6235eb94]{background:#e6f7ff;color:#1890ff;font-weight:500}.step-num[data-v-6235eb94]{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:50%;background:#f0f0f0;font-size:10px;font-weight:700;color:#8c8c8c;flex-shrink:0;line-height:1}.step-item.active .step-num[data-v-6235eb94]{background:#bae7ff;color:#0050b3}.step-text[data-v-6235eb94]{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:20px}.step-dur[data-v-6235eb94]{font-family:monospace;font-size:11px;color:#bfbfbf;flex-shrink:0;line-height:20px}.detail-content[data-v-6235eb94]{flex:1;overflow-y:auto;padding:24px;display:flex;flex-direction:column;gap:24px}.compact-overview[data-v-6235eb94]{display:flex;align-items:center;gap:12px;background:#fff;border-radius:8px;padding:10px 16px;box-shadow:0 1px 3px #00000014;font-size:13px;flex-wrap:wrap}.compact-agent[data-v-6235eb94]{font-weight:500;color:#333}.compact-sep[data-v-6235eb94]{color:#d9d9d9}.compact-model[data-v-6235eb94],.compact-dur[data-v-6235eb94],.compact-tokens[data-v-6235eb94],.compact-tools[data-v-6235eb94]{color:#8c8c8c;font-size:12px}.compact-expand[data-v-6235eb94]{margin-left:auto;font-size:12px;color:#1890ff;background:none;border:none;cursor:pointer}.compact-expand[data-v-6235eb94]:hover{text-decoration:underline}.compact-icon[data-v-6235eb94]{width:14px;height:14px;vertical-align:-2px;margin-right:2px}.card-title-icon[data-v-6235eb94]{width:18px;height:18px;vertical-align:-3px;margin-right:4px}.build-icon[data-v-6235eb94]{width:16px;height:16px;vertical-align:-3px;margin-right:4px}.section-title-icon[data-v-6235eb94]{width:14px;height:14px;vertical-align:-2px;margin-right:4px}.cycle-status-icon[data-v-6235eb94]{width:14px;height:14px;vertical-align:-2px;flex-shrink:0}.cycle-status-icon.success[data-v-6235eb94]{color:#52c41a}.cycle-status-icon.fail[data-v-6235eb94]{color:#ff4d4f}.meta-icon[data-v-6235eb94]{width:12px;height:12px;vertical-align:-2px;margin-right:2px}.sidebar-overview-icon[data-v-6235eb94]{width:16px;height:16px;vertical-align:-3px;margin-right:4px;color:#1890ff}.scope-bar[data-v-6235eb94]{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:8px}.scope-pill[data-v-6235eb94]{display:flex;align-items:center;gap:6px;padding:6px 12px;border-radius:8px;border:1px solid #e8e8e8;background:#fff;font-size:12px;font-weight:500;color:#595959;cursor:pointer;transition:all .15s}.scope-pill.active[data-v-6235eb94]{border-color:transparent}.scope-dot[data-v-6235eb94]{width:8px;height:8px;border-radius:50%;flex-shrink:0}.card[data-v-6235eb94]{background:#fff;border-radius:12px;padding:20px;box-shadow:0 1px 3px #00000014}.card h2[data-v-6235eb94]{font-size:18px;font-weight:600;color:#333;margin:0 0 16px}.overview-grid[data-v-6235eb94]{display:grid;grid-template-columns:repeat(4,1fr);gap:16px}.overview-item[data-v-6235eb94]{display:flex;align-items:center;gap:8px}.oi-label[data-v-6235eb94]{font-size:12px;color:#8c8c8c}.oi-value[data-v-6235eb94]{font-size:14px;font-weight:500;color:#333}.overview-dir[data-v-6235eb94]{margin-top:12px;background:#fafafa;padding:8px 12px;border-radius:4px;font-family:monospace;font-size:12px;color:#8c8c8c}.overview-date[data-v-6235eb94]{margin-top:8px;font-size:12px;color:#bfbfbf}.usage-row[data-v-6235eb94]{display:flex;align-items:center;gap:32px;margin-bottom:16px}.donut-wrap[data-v-6235eb94]{position:relative;flex-shrink:0}.donut-svg[data-v-6235eb94]{transform:rotate(-90deg)}.donut-center[data-v-6235eb94]{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;flex-direction:column;align-items:center;justify-content:center}.donut-total[data-v-6235eb94]{font-size:12px;font-weight:700;color:#333}.donut-label[data-v-6235eb94]{font-size:9px;color:#8c8c8c}.usage-legend[data-v-6235eb94]{display:grid;grid-template-columns:1fr 1fr;gap:6px 24px;font-size:12px}.legend-row[data-v-6235eb94]{display:flex;align-items:center;gap:6px}.legend-color[data-v-6235eb94]{width:10px;height:10px;border-radius:2px;flex-shrink:0}.legend-name[data-v-6235eb94]{color:#595959}.legend-val[data-v-6235eb94]{margin-left:auto;font-weight:500;color:#333}.usage-stats[data-v-6235eb94]{display:grid;grid-template-columns:repeat(6,1fr);gap:16px;text-align:center}.stat-val[data-v-6235eb94]{font-size:18px;font-weight:700;color:#333;display:block}.text-blue[data-v-6235eb94]{color:#1890ff}.text-purple[data-v-6235eb94]{color:#722ed1}.text-rose[data-v-6235eb94]{color:#eb2f96}.stat-lbl[data-v-6235eb94]{font-size:12px;color:#8c8c8c}.cache-rate[data-v-6235eb94]{display:flex;align-items:center;gap:8px;margin-top:12px}.cache-label[data-v-6235eb94]{font-size:12px;color:#8c8c8c}.cache-bar[data-v-6235eb94]{width:128px;height:8px;border-radius:4px;background:#e8e8e8}.cache-fill[data-v-6235eb94]{height:100%;border-radius:4px;background:#fa8c16;transition:width .5s}.cache-pct[data-v-6235eb94]{font-size:12px;font-weight:500;color:#fa8c16}.by-agent[data-v-6235eb94]{margin-top:20px;padding-top:16px;border-top:1px solid #f0f0f0}.by-agent h3[data-v-6235eb94]{font-size:12px;font-weight:600;text-transform:uppercase;color:#8c8c8c;margin:0 0 8px}.agent-bar-track[data-v-6235eb94]{display:flex;height:12px;border-radius:6px;overflow:hidden;background:#f0f0f0;margin-bottom:8px}.agent-bar-seg[data-v-6235eb94]{height:100%;transition:width .5s}.agent-rows[data-v-6235eb94]{display:flex;flex-direction:column;gap:6px}.agent-row[data-v-6235eb94]{display:flex;align-items:center;gap:8px;font-size:12px}.agent-dot[data-v-6235eb94]{width:10px;height:10px;border-radius:2px;flex-shrink:0}.agent-name[data-v-6235eb94]{flex:1;color:#595959;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.agent-val[data-v-6235eb94]{font-weight:500;color:#333}.agent-pct[data-v-6235eb94]{width:48px;text-align:right;color:#bfbfbf}.rounds-list[data-v-6235eb94]{display:flex;flex-direction:column;gap:12px}.round-item[data-v-6235eb94]{display:flex;gap:12px;background:#fafafa;border-radius:8px;padding:16px}.round-num[data-v-6235eb94]{width:28px;height:28px;border-radius:50%;background:#f0f5ff;color:#2f54eb;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;flex-shrink:0}.round-body[data-v-6235eb94]{flex:1;min-width:0}.round-msg[data-v-6235eb94]{font-size:14px;color:#333;margin:0;white-space:pre-wrap}.round-msg-empty[data-v-6235eb94]{color:#bfbfbf;font-style:italic}.round-msg-list[data-v-6235eb94]{display:flex;flex-direction:column;gap:6px}.round-meta[data-v-6235eb94]{display:flex;flex-wrap:wrap;gap:16px;margin-top:8px;font-size:12px;color:#8c8c8c}.build-banner[data-v-6235eb94]{display:flex;align-items:center;gap:12px;border-radius:8px;padding:12px 16px;margin-bottom:16px;font-size:14px;font-weight:500}.build-success[data-v-6235eb94]{background:#f6ffed;color:#52c41a}.build-fail[data-v-6235eb94]{background:#fff1f0;color:#ff4d4f}.build-stat[data-v-6235eb94]{font-size:12px;color:#595959;margin-left:auto}.build-errors[data-v-6235eb94]{font-size:12px;color:#ff4d4f;font-weight:600}.error-codes h3[data-v-6235eb94],.fix-cycles h3[data-v-6235eb94],.warnings h3[data-v-6235eb94]{font-size:12px;font-weight:600;text-transform:uppercase;color:#8c8c8c;margin:0 0 12px}.error-row[data-v-6235eb94]{display:flex;align-items:center;gap:8px;padding:4px 6px;border-radius:4px;transition:background .15s}.error-row[data-v-6235eb94]:hover{background:#fff1f0}.err-code[data-v-6235eb94]{width:120px;flex-shrink:0;font-family:monospace;font-size:12px;color:#ff4d4f;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.err-msg[data-v-6235eb94]{flex:1;font-size:12px;color:#595959;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.err-bar[data-v-6235eb94]{width:96px;height:12px;border-radius:3px;background:#f0f0f0;overflow:hidden;flex-shrink:0}.err-fill[data-v-6235eb94]{height:100%;border-radius:3px;background:#ff7875;display:flex;align-items:center;justify-content:flex-end;padding-right:4px}.err-fill span[data-v-6235eb94]{font-size:9px;font-weight:500;color:#fff}.fix-cycles[data-v-6235eb94]{margin-top:16px}.cycle-stats[data-v-6235eb94]{display:flex;gap:24px;margin-bottom:12px}.cycle-val[data-v-6235eb94]{font-size:18px;font-weight:700;display:block}.text-green[data-v-6235eb94]{color:#52c41a}.text-amber[data-v-6235eb94]{color:#fa8c16}.cycle-lbl[data-v-6235eb94]{font-size:10px;color:#8c8c8c}.cycle-row[data-v-6235eb94]{display:flex;align-items:center;gap:8px;background:#fafafa;padding:6px 8px;border-radius:4px;font-size:12px;color:#595959;margin-bottom:4px}.cycle-dur[data-v-6235eb94]{margin-left:auto;font-family:monospace;color:#bfbfbf}.warnings[data-v-6235eb94]{margin-top:16px}.warn-type-row[data-v-6235eb94]{display:flex;align-items:center;gap:8px;margin-bottom:4px}.warn-type[data-v-6235eb94]{font-size:12px;color:#595959;text-transform:capitalize;width:120px;flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.warn-bar[data-v-6235eb94]{flex:1;height:12px;border-radius:3px;background:#f0f0f0;overflow:hidden}.warn-fill[data-v-6235eb94]{height:100%;border-radius:3px;background:#ffc53d}.warn-count[data-v-6235eb94]{font-size:10px;color:#8c8c8c;width:16px;text-align:right}.warn-entry[data-v-6235eb94]{background:#fffbe6;padding:6px 8px;border-radius:4px;margin-bottom:4px}.warn-file[data-v-6235eb94]{font-family:monospace;font-size:10px;color:#8c8c8c;display:block}.warn-msg[data-v-6235eb94]{font-size:12px;color:#595959}.waterfall[data-v-6235eb94]{display:flex;flex-direction:column;gap:16px}.mobile-steps[data-v-6235eb94]{display:none}@media(max-width:1024px){.detail-sidebar[data-v-6235eb94]{display:none}.mobile-steps[data-v-6235eb94]{display:block}.mobile-steps h2[data-v-6235eb94]{font-size:18px;font-weight:600;color:#333;margin:0 0 12px}.mobile-step[data-v-6235eb94]{display:flex;align-items:center;gap:12px;width:100%;background:#fff;border-radius:8px;padding:12px;box-shadow:0 1px 3px #00000014;margin-bottom:8px;border:none;cursor:pointer;text-align:left;transition:background .15s}.mobile-step[data-v-6235eb94]:hover{background:#fafafa}.mobile-step-num[data-v-6235eb94]{width:28px;height:28px;border-radius:50%;background:#e6f7ff;color:#1890ff;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;flex-shrink:0}.mobile-step-body[data-v-6235eb94]{flex:1;min-width:0}.mobile-step-text[data-v-6235eb94]{font-size:14px;color:#333;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mobile-step-meta[data-v-6235eb94]{display:flex;gap:12px;margin-top:4px;font-size:12px;color:#8c8c8c}}.overview-skills[data-v-6235eb94]{margin-top:16px;padding-top:16px;border-top:1px solid #f0f0f0}.skills-title[data-v-6235eb94]{font-size:12px;font-weight:600;text-transform:uppercase;color:#8c8c8c;margin:0 0 8px}.skills-list[data-v-6235eb94]{display:flex;flex-direction:column;gap:4px}.skill-row[data-v-6235eb94]{display:flex;align-items:center;gap:8px;padding:6px 8px;background:#fafafa;border-radius:4px;font-size:13px}.skill-icon[data-v-6235eb94]{width:14px;height:14px;flex-shrink:0}.skill-icon.completed[data-v-6235eb94]{color:#52c41a}.skill-icon.running[data-v-6235eb94]{color:#1890ff}.skill-icon.failed[data-v-6235eb94]{color:#ff4d4f}.skill-name[data-v-6235eb94]{flex:1;color:#333;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.skill-status[data-v-6235eb94]{font-size:12px;flex-shrink:0}.skill-status.completed[data-v-6235eb94]{color:#52c41a}.skill-status.running[data-v-6235eb94]{color:#1890ff}.skill-status.failed[data-v-6235eb94]{color:#ff4d4f}.overview-system-prompts[data-v-6235eb94]{margin-top:16px;padding-top:16px;border-top:1px solid #f0f0f0}.sp-title[data-v-6235eb94]{font-size:12px;font-weight:600;text-transform:uppercase;color:#8c8c8c;margin:0 0 8px}.sp-list[data-v-6235eb94]{display:flex;flex-direction:column;gap:4px}.sp-model[data-v-6235eb94]{border-radius:4px;overflow:hidden}.sp-model-header[data-v-6235eb94]{display:flex;align-items:center;gap:8px;width:100%;padding:6px 8px;border:none;background:#fafafa;font-size:13px;cursor:pointer;text-align:left;transition:background .15s}.sp-model-header[data-v-6235eb94]:hover{background:#f0f0f0}.sp-model-name[data-v-6235eb94]{flex:1;font-weight:500;color:#333}.sp-model-count[data-v-6235eb94]{font-size:11px;color:#bfbfbf}.sp-contents[data-v-6235eb94]{display:flex;flex-direction:column;gap:4px;padding:4px 0}.sp-content[data-v-6235eb94]{background:#fafafa;border-radius:4px;padding:8px 12px}.sp-text[data-v-6235eb94]{margin:0;font-family:monospace;font-size:12px;color:#595959;white-space:pre-wrap;word-break:break-all;line-height:1.6}.planning-content[data-v-6235eb94]{flex:1;overflow-y:auto;padding:24px;display:flex;flex-direction:column;gap:24px}.planning-summary[data-v-6235eb94]{display:grid;grid-template-columns:repeat(4,1fr);gap:16px}.summary-card[data-v-6235eb94]{background:#fff;border-radius:12px;padding:16px;box-shadow:0 1px 3px #00000014;display:flex;flex-direction:column;align-items:center;gap:4px}.summary-val[data-v-6235eb94]{font-size:18px;font-weight:700;color:#333}.summary-lbl[data-v-6235eb94]{font-size:12px;color:#8c8c8c}.summary-card-priority[data-v-6235eb94]{padding:12px 16px;gap:6px}.priority-counts[data-v-6235eb94]{display:flex;flex-direction:column;gap:4px}.priority-item[data-v-6235eb94]{display:flex;align-items:center;gap:6px;font-size:13px;color:#333;font-weight:500}.legend-dot-sm[data-v-6235eb94]{display:inline-block;width:8px;height:8px;border-radius:2px}.rounds-section[data-v-6235eb94]{background:#fff;border-radius:12px;padding:20px;box-shadow:0 1px 3px #00000014}.rounds-header[data-v-6235eb94]{display:flex;align-items:center;gap:8px;width:100%;border:none;background:transparent;cursor:pointer;padding:0;text-align:left}.rounds-title[data-v-6235eb94]{font-size:18px;font-weight:600;color:#333}.rounds-body[data-v-6235eb94]{margin-top:16px;display:flex;flex-direction:column;gap:12px}.round-block[data-v-6235eb94]{background:#fafafa;border-radius:8px;padding:12px 16px}.round-header[data-v-6235eb94]{display:flex;align-items:baseline;gap:16px;margin-bottom:8px}.round-num[data-v-6235eb94]{font-size:13px;font-weight:600;color:#333;flex-shrink:0;min-width:56px}.round-time[data-v-6235eb94]{font-family:monospace;font-size:12px;color:#8c8c8c;flex-shrink:0}.round-dur[data-v-6235eb94]{font-family:monospace;font-size:11px;color:#bfbfbf;margin-left:auto}.round-progress-mini[data-v-6235eb94]{display:flex;height:4px;border-radius:2px;overflow:hidden;background:#e8e8e8;margin-bottom:8px}.round-progress-seg[data-v-6235eb94]{height:100%;transition:width .3s}.round-progress-seg.completed[data-v-6235eb94]{background:#52c41a}.round-progress-seg.in-progress[data-v-6235eb94]{background:#3b82f6}.round-todos[data-v-6235eb94]{display:flex;flex-direction:column;gap:2px}.todo-row[data-v-6235eb94]{display:flex;align-items:center;gap:8px;padding:4px 6px;border-radius:4px;font-size:13px}.todo-row[data-v-6235eb94]:hover{background:#f0f0f0}.todo-icon[data-v-6235eb94]{width:14px;height:14px;flex-shrink:0}.todo-icon.completed[data-v-6235eb94]{color:#52c41a}.todo-icon.in-progress[data-v-6235eb94]{color:#1890ff;fill:#1890ff}.todo-icon.pending[data-v-6235eb94]{color:#d9d9d9}.todo-content[data-v-6235eb94]{flex:1;color:#333;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.todo-meta[data-v-6235eb94]{font-size:11px;color:#8c8c8c;flex-shrink:0}.filter-empty[data-v-6235eb94]{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:48px 24px;color:#8c8c8c}.filter-empty p[data-v-6235eb94]{font-size:14px;margin:0 0 12px}.filter-empty-reset[data-v-6235eb94]{font-size:13px;color:#1890ff;background:none;border:none;cursor:pointer}.filter-empty-reset[data-v-6235eb94]:hover{text-decoration:underline}.cloud-file-list[data-v-fcef4675]{padding:20px;height:100%;display:flex;flex-direction:column;gap:16px}.list-header[data-v-fcef4675]{display:flex;align-items:center;justify-content:space-between;gap:16px}.header-title h2[data-v-fcef4675]{font-size:18px;color:#303133}.header-sub[data-v-fcef4675]{font-size:13px;color:#909399;margin-top:2px}.header-actions[data-v-fcef4675]{display:flex;align-items:center;gap:8px}.prefix-tag[data-v-fcef4675]{flex-shrink:0}.prefix-input[data-v-fcef4675]{width:280px}.file-table[data-v-fcef4675]{flex:1;min-height:300px}.row-name[data-v-fcef4675]{display:inline-flex;align-items:center;gap:8px;min-width:0;font-size:14.5px}.folder-name[data-v-fcef4675]{font-weight:500}.row-icon[data-v-fcef4675]{font-size:18px;color:#909399;flex-shrink:0}.folder-name .row-icon[data-v-fcef4675]{color:#e6a23c}[data-v-fcef4675] .folder-row{cursor:pointer}.action-btn[data-v-fcef4675]{font-size:17px}.list-footer[data-v-fcef4675]{display:flex;justify-content:flex-end}.page-indicator[data-v-fcef4675]{font-size:13px;color:#606266;padding:0 12px}.meta-file-name[data-v-fcef4675]{font-size:13px;color:#606266;word-break:break-all}
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "session-viewer-plugin",
3
+ "version": "0.1.0",
4
+ "description": "会话指标查看插件:可嵌入 Vue3 + Element Plus 宿主的视图组件库,搭配独立伴生服务提供本地会话快照浏览与 AGC 云端文件管理。",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "bin": {
8
+ "session-viewer-plugin": "server/dist/bin.js"
9
+ },
10
+ "exports": {
11
+ "./ui": "./frontend/dist-ui/index.mjs",
12
+ "./ui.css": "./frontend/dist-ui/style.css"
13
+ },
14
+ "files": [
15
+ "server/dist",
16
+ "!server/dist/assets",
17
+ "frontend/dist-ui",
18
+ "README.md"
19
+ ],
20
+ "scripts": {
21
+ "setup": "cd server && npm install && cd ../frontend && npm install",
22
+ "dev": "concurrently \"cd server && npm run dev\" \"cd frontend && npm run dev\"",
23
+ "dev:server": "cd server && npm run dev",
24
+ "dev:frontend": "cd frontend && npm run dev",
25
+ "build": "npm run build:server && npm run build:ui",
26
+ "build:server": "cd server && npx tsc && node -e \"require('fs').cpSync('src/assets','dist/assets',{recursive:true})\"",
27
+ "build:ui": "cd frontend && npx vite build --config vite.lib.config.js",
28
+ "prepublishOnly": "npm run build"
29
+ },
30
+ "dependencies": {
31
+ "chokidar": "^4.0.3",
32
+ "cors": "^2.8.5",
33
+ "env-paths": "^4.0.0",
34
+ "express": "^5.1.0"
35
+ },
36
+ "peerDependencies": {
37
+ "@element-plus/icons-vue": "^2.3.0",
38
+ "element-plus": "^2.4.0",
39
+ "vue": "^3.4.0"
40
+ },
41
+ "peerDependenciesMeta": {
42
+ "@element-plus/icons-vue": {
43
+ "optional": true
44
+ },
45
+ "element-plus": {
46
+ "optional": true
47
+ },
48
+ "vue": {
49
+ "optional": true
50
+ }
51
+ },
52
+ "engines": {
53
+ "node": ">=18"
54
+ },
55
+ "devDependencies": {
56
+ "concurrently": "^9.2.0"
57
+ }
58
+ }
@@ -0,0 +1,267 @@
1
+ import express from 'express';
2
+ import { Readable } from 'node:stream';
3
+ import { CloudStorageError } from '../services/cloudError.js';
4
+ import { listCloudFiles, listAllCloudFiles, downloadCloudFile, getCloudFileDownloadToken, deleteCloudFile, getCloudFileMetadata, updateCloudFileMetadata, cloudFileExists, renameCloudFile, } from '../services/cloudStorage.js';
5
+ // 模糊查询返回上限与文件夹单次删除上限(桶内对象量级 1 万以内)
6
+ const SEARCH_RESULT_LIMIT = 500;
7
+ const FOLDER_DELETE_LIMIT = 5000;
8
+ function sendCloudError(res, err, fallback) {
9
+ if (err instanceof CloudStorageError) {
10
+ console.error(`Cloud storage error: ${err.message}`);
11
+ res.status(err.status).json({ error: err.message });
12
+ return;
13
+ }
14
+ console.error(fallback + ':', err);
15
+ res.status(500).json({ error: fallback });
16
+ }
17
+ function requireKey(req) {
18
+ const key = typeof req.query.key === 'string' ? req.query.key.trim() : '';
19
+ return key || null;
20
+ }
21
+ export const cloudApi = express.Router();
22
+ cloudApi.get('/cloud/files', async (req, res) => {
23
+ try {
24
+ const prefix = typeof req.query.prefix === 'string' ? req.query.prefix : '';
25
+ const marker = typeof req.query.marker === 'string' ? req.query.marker : undefined;
26
+ const delimiterRaw = String(req.query.delimiter ?? '');
27
+ const delimiter = delimiterRaw !== '' && delimiterRaw !== '0' && delimiterRaw !== 'false';
28
+ const limitRaw = Number(req.query.limit);
29
+ const limit = Number.isFinite(limitRaw)
30
+ ? Math.min(Math.max(Math.trunc(limitRaw), 1), 1000)
31
+ : 100;
32
+ const result = await listCloudFiles({ prefix, marker, limit, delimiter });
33
+ console.log(`GET /api/cloud/files, prefix: '${prefix}', delimiter: ${delimiter}, limit: ${limit}, returning ${result.folders.length} folders, ${result.files.length} files`);
34
+ res.json(result);
35
+ }
36
+ catch (err) {
37
+ sendCloudError(res, err, 'Failed to list cloud files');
38
+ }
39
+ });
40
+ cloudApi.get('/cloud/file/download', async (req, res) => {
41
+ const key = requireKey(req);
42
+ if (!key) {
43
+ res.status(400).json({ error: 'Missing required query param: key' });
44
+ return;
45
+ }
46
+ try {
47
+ const dl = await downloadCloudFile(key);
48
+ res.setHeader('Content-Type', dl.contentType);
49
+ if (dl.contentLength)
50
+ res.setHeader('Content-Length', dl.contentLength);
51
+ res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(dl.filename)}"; filename*=UTF-8''${encodeURIComponent(dl.filename)}`);
52
+ if (dl.body) {
53
+ const stream = Readable.fromWeb(dl.body);
54
+ stream.pipe(res);
55
+ }
56
+ else {
57
+ res.end();
58
+ }
59
+ }
60
+ catch (err) {
61
+ sendCloudError(res, err, 'Failed to download cloud file');
62
+ }
63
+ });
64
+ // ─── 下载令牌(AGC 官方 ?token=create,公网免鉴权下载 URL)────
65
+ cloudApi.get('/cloud/file/token', async (req, res) => {
66
+ const key = requireKey(req);
67
+ if (!key) {
68
+ res.status(400).json({ error: 'Missing required query param: key' });
69
+ return;
70
+ }
71
+ try {
72
+ const result = await getCloudFileDownloadToken(key);
73
+ console.log(`GET /api/cloud/file/token, key: ${key}, token: ${result.token}`);
74
+ res.json(result);
75
+ }
76
+ catch (err) {
77
+ sendCloudError(res, err, 'Failed to create cloud file token');
78
+ }
79
+ });
80
+ cloudApi.delete('/cloud/file', async (req, res) => {
81
+ const key = requireKey(req);
82
+ if (!key) {
83
+ res.status(400).json({ error: 'Missing required query param: key' });
84
+ return;
85
+ }
86
+ try {
87
+ await deleteCloudFile(key);
88
+ console.log(`DELETE /api/cloud/file, key: ${key}, deleted`);
89
+ res.json({ deleted: true, key });
90
+ }
91
+ catch (err) {
92
+ sendCloudError(res, err, 'Failed to delete cloud file');
93
+ }
94
+ });
95
+ // ─── 模糊查询(AGC 无模糊 API:服务端全量拉取 + 内存 contains 匹配)─────────
96
+ cloudApi.get('/cloud/search', async (req, res) => {
97
+ const query = typeof req.query.query === 'string' ? req.query.query.trim() : '';
98
+ const prefix = typeof req.query.prefix === 'string' ? req.query.prefix.trim() : '';
99
+ if (!query) {
100
+ res.status(400).json({ error: 'Missing required query param: query' });
101
+ return;
102
+ }
103
+ try {
104
+ const all = await listAllCloudFiles(prefix);
105
+ const q = query.toLowerCase();
106
+ const hits = all.filter((f) => f.name.toLowerCase().includes(q));
107
+ const truncated = hits.length > SEARCH_RESULT_LIMIT;
108
+ const files = truncated ? hits.slice(0, SEARCH_RESULT_LIMIT) : hits;
109
+ console.log(`GET /api/cloud/search, query: '${query}', prefix: '${prefix}', scanned ${all.length}, hits ${hits.length}`);
110
+ res.json({ total: hits.length, truncated, scanned: all.length, files });
111
+ }
112
+ catch (err) {
113
+ sendCloudError(res, err, 'Failed to search cloud files');
114
+ }
115
+ });
116
+ // ─── 文件夹(前缀)预估与递归删除 ──────────────────────────────
117
+ // 合法文件夹前缀:非空、以 / 结尾、不含 ..,避免误删整桶或越权
118
+ function requireFolderPrefix(req) {
119
+ const prefix = typeof req.query.prefix === 'string' ? req.query.prefix.trim() : '';
120
+ if (!prefix || !prefix.endsWith('/') || prefix.includes('..'))
121
+ return null;
122
+ return prefix;
123
+ }
124
+ cloudApi.get('/cloud/folder/count', async (req, res) => {
125
+ const prefix = requireFolderPrefix(req);
126
+ if (!prefix) {
127
+ res.status(400).json({ error: 'prefix 必须非空且以 / 结尾' });
128
+ return;
129
+ }
130
+ try {
131
+ const files = await listAllCloudFiles(prefix);
132
+ res.json({ count: files.length });
133
+ }
134
+ catch (err) {
135
+ sendCloudError(res, err, 'Failed to count cloud folder');
136
+ }
137
+ });
138
+ cloudApi.delete('/cloud/folder', async (req, res) => {
139
+ const prefix = requireFolderPrefix(req);
140
+ if (!prefix) {
141
+ res.status(400).json({ error: 'prefix 必须非空且以 / 结尾' });
142
+ return;
143
+ }
144
+ try {
145
+ const files = await listAllCloudFiles(prefix, 5);
146
+ if (files.length === 0) {
147
+ res.json({ deleted: 0, failed: 0, errors: [] });
148
+ return;
149
+ }
150
+ if (files.length > FOLDER_DELETE_LIMIT) {
151
+ res.status(400).json({
152
+ error: `该前缀下共 ${files.length} 个对象,超过单次删除上限 ${FOLDER_DELETE_LIMIT},请缩小范围`,
153
+ });
154
+ return;
155
+ }
156
+ // 小并发逐个删除(AGC 无批量删除接口;并发过大会触发限流)
157
+ const failed = [];
158
+ const queue = files.map((f) => f.name);
159
+ let deleted = 0;
160
+ async function worker() {
161
+ for (;;) {
162
+ const key = queue.shift();
163
+ if (key === undefined)
164
+ return;
165
+ try {
166
+ await deleteCloudFile(key);
167
+ deleted++;
168
+ }
169
+ catch {
170
+ failed.push(key);
171
+ }
172
+ }
173
+ }
174
+ await Promise.all(Array.from({ length: Math.min(5, files.length) }, worker));
175
+ console.log(`DELETE /api/cloud/folder, prefix: ${prefix}, deleted ${deleted}, failed ${failed.length}`);
176
+ res.json({ deleted, failed: failed.length, errors: failed.slice(0, 10) });
177
+ }
178
+ catch (err) {
179
+ sendCloudError(res, err, 'Failed to delete cloud folder');
180
+ }
181
+ });
182
+ cloudApi.put('/cloud/file/rename', async (req, res) => {
183
+ const key = requireKey(req);
184
+ if (!key) {
185
+ res.status(400).json({ error: 'Missing required query param: key' });
186
+ return;
187
+ }
188
+ const name = typeof req.body?.name === 'string' ? req.body.name.trim() : '';
189
+ if (!name) {
190
+ res.status(400).json({ error: 'Missing required body field: name' });
191
+ return;
192
+ }
193
+ if (name.includes('/')) {
194
+ res.status(400).json({ error: '文件名不能包含 "/"' });
195
+ return;
196
+ }
197
+ const dir = key.includes('/') ? key.slice(0, key.lastIndexOf('/') + 1) : '';
198
+ const newKey = `${dir}${name}`;
199
+ if (newKey === key) {
200
+ res.status(400).json({ error: '新文件名与原文件名相同' });
201
+ return;
202
+ }
203
+ if (await cloudFileExists(newKey)) {
204
+ res.status(409).json({ error: `目标文件已存在:${newKey}` });
205
+ return;
206
+ }
207
+ try {
208
+ await renameCloudFile(key, newKey);
209
+ console.log(`PUT /api/cloud/file/rename, ${key} -> ${newKey}`);
210
+ res.json({ renamed: true, key: newKey });
211
+ }
212
+ catch (err) {
213
+ sendCloudError(res, err, 'Failed to rename cloud file');
214
+ }
215
+ });
216
+ cloudApi.get('/cloud/file/metadata', async (req, res) => {
217
+ const key = requireKey(req);
218
+ if (!key) {
219
+ res.status(400).json({ error: 'Missing required query param: key' });
220
+ return;
221
+ }
222
+ try {
223
+ const metadata = await getCloudFileMetadata(key);
224
+ res.json(metadata);
225
+ }
226
+ catch (err) {
227
+ sendCloudError(res, err, 'Failed to get cloud file metadata');
228
+ }
229
+ });
230
+ cloudApi.put('/cloud/file/metadata', async (req, res) => {
231
+ const key = requireKey(req);
232
+ if (!key) {
233
+ res.status(400).json({ error: 'Missing required query param: key' });
234
+ return;
235
+ }
236
+ try {
237
+ const body = (req.body ?? {});
238
+ const update = {};
239
+ const stringFields = [
240
+ 'contentType',
241
+ 'contentDisposition',
242
+ 'contentEncoding',
243
+ 'cacheControl',
244
+ 'contentLanguage',
245
+ ];
246
+ for (const field of stringFields) {
247
+ const v = body[field];
248
+ if (typeof v === 'string' && v.trim())
249
+ update[field] = v.trim();
250
+ }
251
+ if (body.customMetadata && typeof body.customMetadata === 'object' && !Array.isArray(body.customMetadata)) {
252
+ const meta = {};
253
+ for (const [k, v] of Object.entries(body.customMetadata)) {
254
+ if (k.trim())
255
+ meta[k.trim()] = String(v ?? '');
256
+ }
257
+ if (Object.keys(meta).length)
258
+ update.customMetadata = meta;
259
+ }
260
+ const metadata = await updateCloudFileMetadata(key, update);
261
+ console.log(`PUT /api/cloud/file/metadata, key: ${key}, updated fields:`, Object.keys(update));
262
+ res.json(metadata);
263
+ }
264
+ catch (err) {
265
+ sendCloudError(res, err, 'Failed to update cloud file metadata');
266
+ }
267
+ });
@@ -0,0 +1,38 @@
1
+ import express from 'express';
2
+ import { getConfig, saveConfig, toggleFavorite } from '../services/config.js';
3
+ export const configApi = express.Router();
4
+ configApi.get('/config', async (_req, res) => {
5
+ try {
6
+ const config = await getConfig();
7
+ res.json(config);
8
+ }
9
+ catch (err) {
10
+ console.error('Error reading config:', err);
11
+ res.status(500).json({ error: 'Failed to read config' });
12
+ }
13
+ });
14
+ configApi.put('/config', async (req, res) => {
15
+ try {
16
+ const { favorites, customDirs } = req.body;
17
+ const config = {
18
+ favorites: Array.isArray(favorites) ? favorites.map(String) : [],
19
+ customDirs: Array.isArray(customDirs) ? customDirs.map(String) : [],
20
+ };
21
+ await saveConfig(config);
22
+ res.json(config);
23
+ }
24
+ catch (err) {
25
+ console.error('Error saving config:', err);
26
+ res.status(500).json({ error: 'Failed to save config' });
27
+ }
28
+ });
29
+ configApi.put('/config/favorites/:id', async (req, res) => {
30
+ try {
31
+ const favorites = await toggleFavorite(req.params.id);
32
+ res.json({ favorites });
33
+ }
34
+ catch (err) {
35
+ console.error('Error toggling favorite:', err);
36
+ res.status(500).json({ error: 'Failed to toggle favorite' });
37
+ }
38
+ });
@@ -0,0 +1,151 @@
1
+ import envPaths from "env-paths";
2
+ import express from 'express';
3
+ import path from 'path';
4
+ import fs from 'fs/promises';
5
+ import { getSessions, getSessionDetail, invalidateCache } from '../services/assembler.js';
6
+ const paths = envPaths("hm-plugin");
7
+ const ompPaths = envPaths("opencode-metrics-plugin", { suffix: "" });
8
+ export const DEFAULT_DIRS = process.env.LOG_DIR
9
+ ? [process.env.LOG_DIR]
10
+ : [
11
+ path.join(paths.log, 'metrics'),
12
+ // opencode-metrics-plugin 0.2.0 默认输出目录(env-paths 无 suffix,与插件端 defaultDirs 一致)
13
+ path.join(ompPaths.log, 'metrics'),
14
+ ];
15
+ function resolveDirs(req) {
16
+ const customDirs = req.query.dirs;
17
+ const customs = Array.isArray(customDirs)
18
+ ? customDirs.map(String)
19
+ : customDirs
20
+ ? [String(customDirs)]
21
+ : [];
22
+ return [...DEFAULT_DIRS, ...customs];
23
+ }
24
+ export const sessionApi = express.Router();
25
+ sessionApi.get('/directories', (_req, res) => {
26
+ res.json({ defaults: DEFAULT_DIRS });
27
+ });
28
+ sessionApi.get('/sessions', async (req, res) => {
29
+ try {
30
+ const dirs = resolveDirs(req);
31
+ console.log('GET /api/sessions called, dirs:', dirs);
32
+ const sessions = await getSessions(dirs);
33
+ console.log('Returning', sessions.length, 'sessions');
34
+ res.json(sessions);
35
+ }
36
+ catch (err) {
37
+ console.error('Error listing sessions:', err);
38
+ res.status(500).json({ error: 'Failed to list sessions' });
39
+ }
40
+ });
41
+ sessionApi.get('/sessions/:id', async (req, res) => {
42
+ try {
43
+ const dirs = resolveDirs(req);
44
+ const detail = await getSessionDetail(dirs, req.params.id);
45
+ if (!detail) {
46
+ res.status(404).json({ error: 'Session not found' });
47
+ return;
48
+ }
49
+ res.json(detail);
50
+ }
51
+ catch (err) {
52
+ console.error('Error getting session detail:', err);
53
+ res.status(500).json({ error: 'Failed to get session detail' });
54
+ }
55
+ });
56
+ sessionApi.get('/sessions/:id/download', async (req, res) => {
57
+ try {
58
+ const dirs = resolveDirs(req);
59
+ const sessionId = req.params.id;
60
+ for (const dir of dirs) {
61
+ const filePath = path.resolve(dir, `${sessionId}.json`);
62
+ if (!filePath.startsWith(path.resolve(dir) + path.sep))
63
+ continue;
64
+ try {
65
+ await fs.access(filePath);
66
+ res.setHeader('Content-Type', 'application/json');
67
+ res.setHeader('Content-Disposition', `attachment; filename="${sessionId}.json"`);
68
+ const content = await fs.readFile(filePath, 'utf-8');
69
+ res.send(content);
70
+ return;
71
+ }
72
+ catch {
73
+ // File not found in this dir, try next
74
+ }
75
+ }
76
+ res.status(404).json({ error: 'Session file not found' });
77
+ }
78
+ catch (err) {
79
+ console.error('Error downloading session:', err);
80
+ res.status(500).json({ error: 'Failed to download session' });
81
+ }
82
+ });
83
+ sessionApi.delete('/sessions/:id', async (req, res) => {
84
+ try {
85
+ const id = req.params.id;
86
+ const filePath = req.body?.filePath;
87
+ const dirs = resolveDirs(req);
88
+ let metricsDeleted = false;
89
+ let logDeleted = false;
90
+ let metricsFile = '';
91
+ let logFile = '';
92
+ // Validate filePath is under one of the allowed directories
93
+ function isPathAllowed(targetPath) {
94
+ const resolved = path.resolve(targetPath);
95
+ return dirs.some((dir) => {
96
+ const dirResolved = path.resolve(dir);
97
+ return resolved.startsWith(dirResolved + path.sep) || resolved === dirResolved;
98
+ });
99
+ }
100
+ if (filePath && isPathAllowed(filePath)) {
101
+ const logPath = path.join(path.dirname(filePath), '..', 'events', `${id}.log`);
102
+ try {
103
+ await fs.unlink(filePath);
104
+ metricsDeleted = true;
105
+ metricsFile = filePath;
106
+ console.log('Deleted metrics file:', filePath);
107
+ }
108
+ catch { }
109
+ try {
110
+ await fs.unlink(logPath);
111
+ logDeleted = true;
112
+ logFile = logPath;
113
+ console.log('Deleted log file:', logPath);
114
+ }
115
+ catch { }
116
+ }
117
+ else {
118
+ // Fallback: search through all directories
119
+ for (const dir of dirs) {
120
+ const metricsPath = path.join(dir, `${id}.json`);
121
+ const logPath = path.join(dir, '..', 'events', `${id}.log`);
122
+ try {
123
+ await fs.access(metricsPath);
124
+ await fs.unlink(metricsPath);
125
+ metricsDeleted = true;
126
+ metricsFile = metricsPath;
127
+ console.log('Deleted metrics file:', metricsPath);
128
+ }
129
+ catch { }
130
+ try {
131
+ await fs.access(logPath);
132
+ await fs.unlink(logPath);
133
+ logDeleted = true;
134
+ logFile = logPath;
135
+ console.log('Deleted log file:', logPath);
136
+ }
137
+ catch { }
138
+ }
139
+ }
140
+ if (!metricsDeleted && !logDeleted) {
141
+ res.status(404).json({ error: 'Session not found' });
142
+ return;
143
+ }
144
+ invalidateCache();
145
+ res.json({ deleted: true, metricsFile, logFile });
146
+ }
147
+ catch (err) {
148
+ console.error('Error deleting session:', err);
149
+ res.status(500).json({ error: 'Failed to delete session' });
150
+ }
151
+ });
@@ -0,0 +1,19 @@
1
+ // 应用工厂:组装 Express 实例(中间件与全部 API 路由),供开发入口与 bin 启动入口复用。
2
+ import express from 'express';
3
+ import cors from 'cors';
4
+ import { sessionApi } from './api/sessionApi.js';
5
+ import { configApi } from './api/configApi.js';
6
+ import { cloudApi } from './api/cloudApi.js';
7
+ /**
8
+ * 创建组装完成的 Express 应用(未监听端口)。
9
+ * @returns 可直接调用 listen 的 Express 实例
10
+ */
11
+ export function createApp() {
12
+ const app = express();
13
+ app.use(cors());
14
+ app.use(express.json());
15
+ app.use('/api', sessionApi);
16
+ app.use('/api', configApi);
17
+ app.use('/api', cloudApi);
18
+ return app;
19
+ }
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ // 服务启动入口:读取环境变量并启动 HTTP 服务,可由 node/tsx 直接执行(npm bin 脚本)。
3
+ // 环境变量:PORT 监听端口(默认 3001);HOST 绑定地址(默认 127.0.0.1,容器部署需设为 0.0.0.0);
4
+ // LOG_DIR 覆盖默认扫描目录;AGC_CREDENTIALS_FILE/AGC_CREDENTIALS_JSON 覆盖云端凭证。
5
+ import { createApp } from './app.js';
6
+ import { DEFAULT_DIRS } from './api/sessionApi.js';
7
+ import { ensureDataDir } from './services/config.js';
8
+ const PORT = Number(process.env.PORT) || 3001;
9
+ const HOST = process.env.HOST || '127.0.0.1';
10
+ await ensureDataDir();
11
+ const app = createApp();
12
+ app.listen(PORT, HOST, () => {
13
+ console.log(`Server running on http://${HOST}:${PORT}`);
14
+ console.log('Scan directories:', DEFAULT_DIRS);
15
+ });
@@ -0,0 +1,2 @@
1
+ // 开发模式启动入口:复用 bin 的启动逻辑(npm run dev / tsx src/index.ts)。
2
+ import './bin.js';