pac-proxy-cli 1.0.4 → 1.1.2

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
+ .toast-container[data-v-5cf00320]{position:fixed;top:1.25rem;right:1.25rem;z-index:9999;display:flex;flex-direction:column;gap:.5rem;pointer-events:none}.toast[data-v-5cf00320]{display:flex;align-items:center;gap:.5rem;padding:.65rem 1rem;border-radius:8px;font-size:.9rem;font-weight:500;box-shadow:0 4px 12px #00000026;pointer-events:auto;max-width:320px}.toast.success[data-v-5cf00320]{background:#f0fdf4;color:#166534;border:1px solid #bbf7d0}.toast.error[data-v-5cf00320]{background:#fef2f2;color:#991b1b;border:1px solid #fecaca}.toast.info[data-v-5cf00320]{background:#eff6ff;color:#1e40af;border:1px solid #bfdbfe}.toast-icon[data-v-5cf00320]{flex-shrink:0;width:1.1rem;height:1.1rem;display:flex}.toast-icon svg[data-v-5cf00320]{width:100%;height:100%}.toast-msg[data-v-5cf00320]{line-height:1.4}.toast-confirm-mask[data-v-5cf00320]{position:fixed;top:0;right:0;bottom:0;left:0;background:#00000059;display:flex;align-items:center;justify-content:center;z-index:10000;pointer-events:auto}.toast-confirm[data-v-5cf00320]{background:#fff;border-radius:10px;padding:1.5rem;min-width:280px;max-width:360px;box-shadow:0 8px 24px #0000002e}.toast-confirm-msg[data-v-5cf00320]{font-size:.95rem;color:#0f172a;margin-bottom:1.25rem;line-height:1.5}.toast-confirm-actions[data-v-5cf00320]{display:flex;gap:.75rem;justify-content:flex-end}.tc-btn[data-v-5cf00320]{padding:.45rem 1rem;border-radius:6px;font-size:.9rem;cursor:pointer;border:none;font-weight:500}.tc-btn.cancel[data-v-5cf00320]{background:#f1f5f9;color:#475569}.tc-btn.cancel[data-v-5cf00320]:hover{background:#e2e8f0}.tc-btn.ok[data-v-5cf00320]{background:#2563eb;color:#fff}.tc-btn.ok[data-v-5cf00320]:hover{background:#1d4ed8}.toast-enter-active[data-v-5cf00320]{animation:toast-in-5cf00320 .22s ease}.toast-leave-active[data-v-5cf00320]{animation:toast-out-5cf00320 .18s ease forwards}@keyframes toast-in-5cf00320{0%{opacity:0;transform:translate(1rem)}to{opacity:1;transform:translate(0)}}@keyframes toast-out-5cf00320{0%{opacity:1;transform:translate(0)}to{opacity:0;transform:translate(1rem)}}.app-layout[data-v-ee1bb6ce]{min-height:100vh;display:flex}.sidebar[data-v-ee1bb6ce]{position:fixed;left:0;top:0;bottom:0;width:200px;background:var(--bg-sidebar);display:flex;flex-direction:column;z-index:20}.sidebar-logo[data-v-ee1bb6ce]{display:flex;align-items:center;gap:.6rem;padding:1.25rem 1.25rem 1rem;color:#fff;font-weight:700;font-size:1rem;border-bottom:1px solid rgba(255,255,255,.08)}.sidebar-logo svg[data-v-ee1bb6ce]{width:1.25rem;height:1.25rem;flex-shrink:0;color:#60a5fa}.side-nav[data-v-ee1bb6ce]{flex:1;display:flex;flex-direction:column;padding:.75rem 0}.side-nav a[data-v-ee1bb6ce]{display:flex;align-items:center;gap:.6rem;color:var(--text-sidebar-muted);padding:.6rem 1.25rem;font-size:.9rem;border-left:3px solid transparent;transition:background .15s,color .15s;text-decoration:none}.side-nav a svg[data-v-ee1bb6ce]{width:1rem;height:1rem;flex-shrink:0}.side-nav a[data-v-ee1bb6ce]:hover{background:var(--bg-sidebar-hover);color:var(--text-sidebar);text-decoration:none}.side-nav a.active[data-v-ee1bb6ce]{background:#2563eb40;color:#93c5fd;border-left-color:#3b82f6}.sidebar-footer[data-v-ee1bb6ce]{padding:1rem 1.25rem;border-top:1px solid rgba(255,255,255,.08);display:flex;align-items:center;justify-content:space-between}.mode-badge[data-v-ee1bb6ce]{font-size:.75rem;padding:.2rem .5rem;border-radius:4px;font-weight:500}.mode-badge.local[data-v-ee1bb6ce]{background:#dbeafe26;color:#93c5fd}.mode-badge.remote[data-v-ee1bb6ce]{background:#e9d5ff26;color:#c4b5fd}.switch-mode-link[data-v-ee1bb6ce]{font-size:.78rem;color:var(--text-sidebar-muted);text-decoration:none}.switch-mode-link[data-v-ee1bb6ce]:hover{color:var(--text-sidebar);text-decoration:none}.content-wrap[data-v-ee1bb6ce]{margin-left:200px;flex:1;display:flex;flex-direction:column;min-height:100vh}.content-standalone[data-v-ee1bb6ce]{margin-left:0}.topbar[data-v-ee1bb6ce]{position:sticky;top:0;height:52px;display:flex;align-items:center;justify-content:space-between;padding:0 1.5rem;background:var(--bg-card);border-bottom:1px solid var(--border);z-index:10}.topbar-left .page-title[data-v-ee1bb6ce]{font-weight:600;font-size:.95rem;color:var(--text-primary)}.topbar-right[data-v-ee1bb6ce]{display:flex;align-items:center;gap:.75rem}.username[data-v-ee1bb6ce]{font-size:.875rem;color:var(--text-secondary)}.logout-btn[data-v-ee1bb6ce]{font-size:.85rem;color:var(--text-secondary);background:none;border:none;cursor:pointer;padding:.25rem 0}.logout-btn[data-v-ee1bb6ce]:hover{color:var(--btn-primary)}.main[data-v-ee1bb6ce]{flex:1;padding:1.5rem}.mode-select[data-v-b465f762]{min-height:100vh;display:flex;align-items:center;justify-content:center;background:var(--bg-main);padding:2rem}.card[data-v-b465f762]{background:var(--bg-card);border-radius:12px;padding:2.5rem;max-width:480px;width:100%;box-shadow:0 1px 3px #00000014}.card h1[data-v-b465f762]{font-size:1.5rem;margin-bottom:.5rem}.desc[data-v-b465f762]{color:var(--text-secondary);font-size:.95rem;margin-bottom:1.5rem}.options[data-v-b465f762]{display:flex;flex-direction:column;gap:1rem}.option[data-v-b465f762]{padding:1.25rem 1.5rem;border:2px solid var(--border);border-radius:var(--radius);background:#fff;text-align:left;transition:border-color .2s,background .2s}.option[data-v-b465f762]:hover{border-color:var(--btn-primary);background:#f8fafc}.option .title[data-v-b465f762]{display:block;font-weight:600;margin-bottom:.25rem}.option .sub[data-v-b465f762]{font-size:.9rem;color:var(--text-secondary)}.auth-page[data-v-d4d57b91]{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:2rem}.card[data-v-d4d57b91]{background:var(--bg-card);border-radius:12px;padding:2rem;max-width:400px;width:100%;box-shadow:0 1px 3px #00000014}.card h1[data-v-d4d57b91]{font-size:1.5rem;margin-bottom:.5rem}.desc[data-v-d4d57b91]{color:var(--text-secondary);font-size:.9rem;margin-bottom:1.5rem}.form[data-v-d4d57b91]{display:flex;flex-direction:column;gap:1rem}.field label[data-v-d4d57b91]{display:block;font-size:.9rem;margin-bottom:.35rem;color:var(--text-secondary)}.field input[data-v-d4d57b91]{width:100%;padding:.6rem .75rem;border:1px solid var(--border);border-radius:var(--radius)}.error[data-v-d4d57b91]{color:#dc2626;font-size:.9rem}.btn.primary[data-v-d4d57b91]{background:var(--btn-primary);color:#fff;padding:.65rem 1rem;font-weight:500}.btn.primary[data-v-d4d57b91]:hover:not(:disabled){background:var(--btn-primary-hover)}.btn.primary[data-v-d4d57b91]:disabled{opacity:.6;cursor:not-allowed}.footer[data-v-d4d57b91]{margin-top:1rem;font-size:.9rem;color:var(--text-secondary)}.auth-page[data-v-a1c8458c]{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:2rem}.card[data-v-a1c8458c]{background:var(--bg-card);border-radius:12px;padding:2rem;max-width:400px;width:100%;box-shadow:0 1px 3px #00000014}.card h1[data-v-a1c8458c]{font-size:1.5rem;margin-bottom:.5rem}.desc[data-v-a1c8458c]{color:var(--text-secondary);font-size:.9rem;margin-bottom:1.5rem}.form[data-v-a1c8458c]{display:flex;flex-direction:column;gap:1rem}.field label[data-v-a1c8458c]{display:block;font-size:.9rem;margin-bottom:.35rem;color:var(--text-secondary)}.field input[data-v-a1c8458c]{width:100%;padding:.6rem .75rem;border:1px solid var(--border);border-radius:var(--radius)}.error[data-v-a1c8458c]{color:#dc2626;font-size:.9rem}.success[data-v-a1c8458c]{color:#16a34a;font-size:.9rem}.btn.primary[data-v-a1c8458c]{background:var(--btn-primary);color:#fff;padding:.65rem 1rem;font-weight:500}.btn.primary[data-v-a1c8458c]:hover:not(:disabled){background:var(--btn-primary-hover)}.btn.primary[data-v-a1c8458c]:disabled{opacity:.6;cursor:not-allowed}.footer[data-v-a1c8458c]{margin-top:1rem;font-size:.9rem;color:var(--text-secondary)}.proxy-settings[data-v-44d33970]{display:flex;flex-direction:column;gap:1.25rem}.card[data-v-44d33970]{background:var(--bg-card);border-radius:var(--radius);padding:1.5rem;box-shadow:0 1px 3px #0000000f}.card h3[data-v-44d33970]{font-size:.95rem;font-weight:600;margin-bottom:.35rem}.desc[data-v-44d33970]{color:var(--text-secondary);font-size:.875rem;margin-bottom:1.25rem;line-height:1.5}.desc code[data-v-44d33970]{background:#f1f5f9;padding:.1rem .3rem;border-radius:4px;font-size:.82rem}.mode-cards[data-v-44d33970]{display:grid;grid-template-columns:repeat(4,1fr);gap:.75rem}.mode-card[data-v-44d33970]{display:flex;flex-direction:column;align-items:center;gap:.35rem;padding:1rem .5rem;border:1.5px solid var(--border);border-radius:var(--radius);background:var(--bg-card);cursor:pointer;transition:border-color .15s,background .15s,box-shadow .15s;text-align:center}.mode-card[data-v-44d33970]:hover:not(:disabled){border-color:var(--btn-primary);background:#f8fafc}.mode-card.active[data-v-44d33970]{border-color:var(--btn-primary);background:#eff6ff;box-shadow:0 0 0 3px #2563eb1a}.mode-card.active-clear[data-v-44d33970]{border-color:#94a3b8;background:#f8fafc;box-shadow:0 0 0 3px #94a3b826}.mode-card[data-v-44d33970]:disabled{opacity:.55;cursor:not-allowed}.mode-icon[data-v-44d33970]{width:1.5rem;height:1.5rem;color:var(--text-secondary)}.mode-icon[data-v-44d33970] svg{width:100%;height:100%}.mode-card.active .mode-icon[data-v-44d33970]{color:var(--btn-primary)}.mode-card.active-clear .mode-icon[data-v-44d33970]{color:#64748b}.mode-label[data-v-44d33970]{font-size:.875rem;font-weight:600;color:var(--text-primary)}.mode-desc[data-v-44d33970]{font-size:.78rem;color:var(--text-secondary);line-height:1.3}.ca-download[data-v-44d33970]{margin-top:1rem;padding-top:1rem;border-top:1px solid var(--border)}.ca-label[data-v-44d33970]{display:block;font-size:.875rem;color:var(--text-secondary);margin-bottom:.5rem}.btn-ca[data-v-44d33970]{display:inline-block;padding:.4rem .8rem;font-size:.875rem;background:var(--btn-primary);color:#fff;border-radius:var(--radius);text-decoration:none;margin-bottom:.5rem}.btn-ca[data-v-44d33970]:hover{background:var(--btn-primary-hover);text-decoration:none}.ca-hint[data-v-44d33970]{font-size:.82rem;color:var(--text-secondary);margin:0}.field[data-v-44d33970]{margin-bottom:1rem}.field label[data-v-44d33970]{display:block;font-size:.875rem;margin-bottom:.35rem;color:var(--text-secondary)}.field input[data-v-44d33970]{width:100%;padding:.5rem .75rem;border:1px solid var(--border);border-radius:var(--radius);font-size:.9rem}.upstream-managed[data-v-44d33970]{display:flex;align-items:center;gap:.5rem;padding:.5rem .75rem;background:#eff6ff;border:1px solid #bfdbfe;border-radius:var(--radius);font-size:.875rem;color:#1e40af}.upstream-managed svg[data-v-44d33970]{width:1rem;height:1rem;flex-shrink:0}.upstream-managed a[data-v-44d33970]{color:#2563eb;font-size:.82rem;margin-left:auto}.row[data-v-44d33970]{display:grid;grid-template-columns:1fr 1fr;gap:1rem}.actions[data-v-44d33970]{margin-top:.5rem}.btn.primary[data-v-44d33970]{display:inline-flex;align-items:center;gap:.4rem;background:var(--btn-primary);color:#fff;padding:.5rem 1.25rem;font-size:.9rem;border:none;border-radius:var(--radius);cursor:pointer;transition:background .15s}.btn.primary[data-v-44d33970]:hover:not(:disabled){background:var(--btn-primary-hover)}.btn.primary[data-v-44d33970]:disabled{opacity:.6;cursor:not-allowed}.btn-spinner[data-v-44d33970]{width:.85rem;height:.85rem;border:2px solid rgba(255,255,255,.4);border-top-color:#fff;border-radius:50%;animation:spin-44d33970 .6s linear infinite;flex-shrink:0}@keyframes spin-44d33970{to{transform:rotate(360deg)}}.status-card .status-line[data-v-44d33970]{display:flex;align-items:center;gap:.5rem;margin-bottom:.5rem}.status-dot[data-v-44d33970]{width:8px;height:8px;border-radius:50%;flex-shrink:0}.status-dot.on[data-v-44d33970]{background:var(--switch-on);box-shadow:0 0 0 3px #22c55e33;animation:pulse-44d33970 2s ease-in-out infinite}.status-dot.off[data-v-44d33970]{background:#cbd5e1}@keyframes pulse-44d33970{0%,to{box-shadow:0 0 0 3px #22c55e33}50%{box-shadow:0 0 0 5px #22c55e14}}.status-on[data-v-44d33970]{color:var(--switch-on);font-weight:600;font-size:.9rem}.status-off[data-v-44d33970]{color:var(--text-secondary);font-size:.9rem}.status-port[data-v-44d33970]{color:var(--text-secondary);font-size:.875rem}.hint[data-v-44d33970]{font-size:.875rem;color:var(--text-secondary);margin-top:.25rem;line-height:1.5}.hint strong[data-v-44d33970]{color:var(--text-primary)}.hint.error[data-v-44d33970]{color:#dc2626}.pac-rules[data-v-abe2d935]{display:flex;flex-direction:column;gap:1.25rem}.card[data-v-abe2d935]{background:var(--bg-card);border-radius:var(--radius);padding:1.5rem;box-shadow:0 1px 3px #0000000f}.card-header[data-v-abe2d935]{margin-bottom:1rem}.card-header h3[data-v-abe2d935]{font-size:.95rem;font-weight:600;display:flex;align-items:center;gap:.5rem;margin-bottom:.25rem}.rule-count[data-v-abe2d935]{display:inline-flex;align-items:center;justify-content:center;background:#e0e7ff;color:#3730a3;font-size:.75rem;font-weight:600;min-width:1.4rem;height:1.4rem;padding:0 .35rem;border-radius:999px}.desc[data-v-abe2d935]{color:var(--text-secondary);font-size:.875rem;line-height:1.5}.toolbar[data-v-abe2d935]{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;margin-bottom:1rem}.btn[data-v-abe2d935]{display:inline-flex;align-items:center;gap:.35rem;padding:.45rem .85rem;font-size:.875rem;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg-card);cursor:pointer;color:var(--text-primary);transition:background .15s,border-color .15s;white-space:nowrap}.btn svg[data-v-abe2d935]{width:.9rem;height:.9rem;flex-shrink:0}.btn[data-v-abe2d935]:hover:not(:disabled){background:#f1f5f9;border-color:#cbd5e1}.btn[data-v-abe2d935]:disabled{opacity:.5;cursor:not-allowed}.btn.primary[data-v-abe2d935]{background:var(--btn-primary);color:#fff;border-color:var(--btn-primary)}.btn.primary[data-v-abe2d935]:hover:not(:disabled){background:var(--btn-primary-hover);border-color:var(--btn-primary-hover)}.search-wrap[data-v-abe2d935]{position:relative;margin-left:auto}.search-icon[data-v-abe2d935]{position:absolute;left:.6rem;top:50%;transform:translateY(-50%);width:.9rem;height:.9rem;color:var(--text-secondary);pointer-events:none}.search-input[data-v-abe2d935]{padding:.45rem .75rem .45rem 2rem;border:1px solid var(--border);border-radius:var(--radius);font-size:.875rem;min-width:180px}.table-wrap[data-v-abe2d935]{overflow-x:auto}.table[data-v-abe2d935]{width:100%;border-collapse:collapse}.table th[data-v-abe2d935],.table td[data-v-abe2d935]{padding:.6rem .75rem;text-align:left;border-bottom:1px solid var(--border)}.table th[data-v-abe2d935]{font-weight:600;color:var(--text-secondary);font-size:.82rem;text-transform:uppercase;letter-spacing:.03em}.table tbody tr[data-v-abe2d935]:hover{background:#f8fafc}.table .empty[data-v-abe2d935]{color:var(--text-secondary);text-align:center;padding:2rem;font-size:.9rem}.pattern-cell[data-v-abe2d935]{font-family:Menlo,Consolas,monospace;font-size:.875rem}.priority-cell[data-v-abe2d935]{color:var(--text-secondary);font-size:.875rem}.action-badge[data-v-abe2d935]{display:inline-block;padding:.15rem .5rem;border-radius:999px;font-size:.78rem;font-weight:500}.action-badge.proxy[data-v-abe2d935]{background:#dbeafe;color:#1d4ed8}.action-badge.direct[data-v-abe2d935]{background:#f1f5f9;color:#475569}.ops-cell[data-v-abe2d935]{white-space:nowrap}.icon-btn[data-v-abe2d935]{display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;border:none;border-radius:6px;background:transparent;cursor:pointer;color:var(--text-secondary);transition:background .15s,color .15s;margin-right:.25rem}.icon-btn svg[data-v-abe2d935]{width:.9rem;height:.9rem}.icon-btn[data-v-abe2d935]:hover{background:#f1f5f9;color:var(--text-primary)}.icon-btn.danger[data-v-abe2d935]:hover{background:#fee2e2;color:#dc2626}.modal[data-v-abe2d935]{position:fixed;top:0;right:0;bottom:0;left:0;background:#0006;display:flex;align-items:center;justify-content:center;z-index:100}.modal-content[data-v-abe2d935]{background:var(--bg-card);border-radius:var(--radius);padding:1.5rem;min-width:360px;max-width:440px;width:100%;box-shadow:0 8px 24px #00000026}.modal-content h3[data-v-abe2d935]{font-size:1rem;font-weight:600;margin-bottom:1.25rem}.field[data-v-abe2d935]{margin-bottom:1rem}.field label[data-v-abe2d935]{display:block;font-size:.875rem;margin-bottom:.35rem;color:var(--text-secondary)}.field input[data-v-abe2d935]{width:100%;padding:.5rem .75rem;border:1px solid var(--border);border-radius:var(--radius);font-size:.9rem}.action-toggle[data-v-abe2d935]{display:flex;gap:0;border:1px solid var(--border);border-radius:var(--radius);overflow:hidden}.toggle-opt[data-v-abe2d935]{flex:1;padding:.45rem 0;font-size:.875rem;border:none;background:transparent;cursor:pointer;color:var(--text-secondary);transition:background .15s,color .15s}.toggle-opt[data-v-abe2d935]:first-child{border-right:1px solid var(--border)}.toggle-opt.active[data-v-abe2d935]{background:var(--btn-primary);color:#fff}.modal-actions[data-v-abe2d935]{display:flex;gap:.75rem;justify-content:flex-end;margin-top:1.25rem}.traffic-page[data-v-4331eb78]{display:flex;flex-direction:column;gap:1.25rem}.card[data-v-4331eb78]{background:var(--bg-card);border-radius:var(--radius);padding:1.5rem;box-shadow:0 1px 3px #0000000f}.capture-card[data-v-4331eb78]{padding:1rem 1.5rem}.capture-banner[data-v-4331eb78]{display:flex;align-items:center;gap:.5rem;background:#eff6ff;color:#1e40af;padding:.5rem .75rem;border-radius:var(--radius);font-size:.875rem;margin-bottom:1rem}.capture-banner svg[data-v-4331eb78]{width:1rem;height:1rem;flex-shrink:0}.toolbar[data-v-4331eb78]{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem;margin-bottom:1rem}.auto-refresh[data-v-4331eb78]{display:flex;align-items:center;gap:.5rem}.toggle-btn[data-v-4331eb78]{width:36px;height:20px;border-radius:10px;background:var(--switch-off);padding:2px;border:none;cursor:pointer;transition:background .2s;position:relative}.toggle-btn.on[data-v-4331eb78]{background:var(--switch-on)}.toggle-knob[data-v-4331eb78]{display:block;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;box-shadow:0 1px 3px #0003}.toggle-btn.on .toggle-knob[data-v-4331eb78]{transform:translate(16px)}.toggle-label[data-v-4331eb78]{font-size:.875rem;color:var(--text-secondary)}.record-count[data-v-4331eb78]{font-size:.82rem;color:var(--text-secondary);margin-left:auto}.btn[data-v-4331eb78]{display:inline-flex;align-items:center;gap:.35rem;padding:.45rem .85rem;font-size:.875rem;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg-card);cursor:pointer;color:var(--text-primary);transition:background .15s}.btn svg[data-v-4331eb78]{width:.9rem;height:.9rem}.btn[data-v-4331eb78]:hover:not(:disabled){background:#f1f5f9}.btn[data-v-4331eb78]:disabled{opacity:.5;cursor:not-allowed}.btn.small[data-v-4331eb78]{padding:.35rem .75rem;font-size:.82rem}.toolbar .search-input[data-v-4331eb78],.toolbar .filter-select[data-v-4331eb78],.toolbar .status-input[data-v-4331eb78],.toolbar .sort-order-btn[data-v-4331eb78]{height:2.1rem;padding:0 .6rem;border:1px solid var(--border);border-radius:var(--radius);font-size:.875rem;background:#fff}.toolbar .search-input[data-v-4331eb78]{min-width:160px}.toolbar .filter-select[data-v-4331eb78]{min-width:6rem;cursor:pointer}.toolbar .status-input[data-v-4331eb78]{width:72px}.toolbar .sort-order-btn[data-v-4331eb78]{width:2.1rem;padding:0;cursor:pointer;background:#f8fafc}.table-wrap[data-v-4331eb78]{overflow-x:auto}.table[data-v-4331eb78]{width:100%;border-collapse:collapse}.table th[data-v-4331eb78],.table td[data-v-4331eb78]{padding:.55rem .75rem;text-align:left;border-bottom:1px solid var(--border)}.table th[data-v-4331eb78]{font-weight:600;color:var(--text-secondary);font-size:.82rem;text-transform:uppercase;letter-spacing:.03em}.table tbody tr[data-v-4331eb78]:hover{background:#f8fafc}.table .empty[data-v-4331eb78]{color:var(--text-secondary);text-align:center;padding:2rem;font-size:.9rem}.time-cell[data-v-4331eb78]{font-size:.82rem;color:var(--text-secondary);white-space:nowrap}.url-cell[data-v-4331eb78]{max-width:320px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.875rem}.action-badge[data-v-4331eb78]{display:inline-block;padding:.15rem .5rem;border-radius:999px;font-size:.78rem;font-weight:500}.action-badge.proxy[data-v-4331eb78]{background:#dbeafe;color:#1d4ed8}.action-badge.direct[data-v-4331eb78]{background:#f1f5f9;color:#475569}.row-clickable[data-v-4331eb78]{cursor:pointer}.method-tag[data-v-4331eb78]{font-size:.78rem;padding:.15rem .4rem;border-radius:4px;font-weight:500}.method-tag.GET[data-v-4331eb78]{background:#dbeafe;color:#1d4ed8}.method-tag.POST[data-v-4331eb78]{background:#d1fae5;color:#047857}.method-tag.PUT[data-v-4331eb78]{background:#fef3c7;color:#b45309}.method-tag.DELETE[data-v-4331eb78]{background:#fee2e2;color:#b91c1c}.method-tag.HEAD[data-v-4331eb78],.method-tag.OPTIONS[data-v-4331eb78]{background:#f1f5f9;color:#64748b}.method-tag.PATCH[data-v-4331eb78]{background:#fce7f3;color:#9d174d}.status-tag[data-v-4331eb78]{font-size:.82rem;padding:.15rem .4rem;border-radius:4px;font-weight:500}.status-2xx[data-v-4331eb78]{background:#d1fae5;color:#047857}.status-3xx[data-v-4331eb78]{background:#e0e7ff;color:#3730a3}.status-4xx[data-v-4331eb78]{background:#fee2e2;color:#b91c1c}.status-5xx[data-v-4331eb78]{background:#fecaca;color:#991b1b}.btn-link[data-v-4331eb78]{background:none;border:none;color:var(--btn-primary);cursor:pointer;font-size:.875rem;padding:0}.btn-link[data-v-4331eb78]:hover{text-decoration:underline}.pagination[data-v-4331eb78]{margin-top:1rem;display:flex;align-items:center;gap:.75rem}.pagination-info[data-v-4331eb78]{font-size:.875rem;color:var(--text-secondary)}.drawer-mask[data-v-4331eb78]{position:fixed;top:0;right:0;bottom:0;left:0;background:#0000004d;z-index:100}.drawer[data-v-4331eb78]{position:fixed;top:0;right:0;width:min(560px,100vw);height:100vh;background:var(--bg-card);box-shadow:-2px 0 16px #0000001f;z-index:101;display:flex;flex-direction:column;overflow:hidden}.drawer-header[data-v-4331eb78]{display:flex;align-items:center;justify-content:space-between;padding:1rem 1.25rem;border-bottom:1px solid var(--border);flex-shrink:0}.drawer-title[data-v-4331eb78]{font-size:.95rem;font-weight:600}.drawer-close[data-v-4331eb78]{width:2rem;height:2rem;border:none;background:none;font-size:1.5rem;line-height:1;cursor:pointer;color:var(--text-secondary);border-radius:6px}.drawer-close[data-v-4331eb78]:hover{background:#f1f5f9;color:var(--text-primary)}.drawer-url-section[data-v-4331eb78]{margin-bottom:1.25rem;padding-bottom:1rem;border-bottom:1px solid var(--border)}.drawer-url-head[data-v-4331eb78]{display:flex;align-items:center;justify-content:space-between;gap:.5rem;margin-bottom:.35rem}.drawer-method-tag[data-v-4331eb78]{font-size:.78rem;font-weight:600;padding:.15rem .4rem;border-radius:4px}.btn-copy-url[data-v-4331eb78]{padding:.3rem .6rem;font-size:.82rem;border:1px solid var(--border);border-radius:var(--radius);background:#f1f5f9;cursor:pointer}.btn-copy-url[data-v-4331eb78]:hover{background:#e2e8f0}.drawer-url[data-v-4331eb78]{margin:0;padding:.5rem .6rem;background:#f8fafc;border-radius:var(--radius);font-size:.82rem;line-height:1.4;white-space:pre-wrap;word-break:break-all;max-height:5rem;overflow:auto}.drawer-tabs[data-v-4331eb78]{display:flex;gap:.25rem;padding:.5rem 1rem;border-bottom:1px solid var(--border);flex-shrink:0}.drawer-tabs button[data-v-4331eb78]{padding:.4rem .75rem;font-size:.875rem;border:none;background:none;border-radius:var(--radius);cursor:pointer;color:var(--text-secondary)}.drawer-tabs button.active[data-v-4331eb78]{background:var(--btn-primary);color:#fff}.drawer-body[data-v-4331eb78]{flex:1;overflow:auto;padding:1rem 1.25rem}.detail-section[data-v-4331eb78]{margin-bottom:1.25rem}.detail-section h4[data-v-4331eb78]{font-size:.82rem;margin-bottom:.35rem;color:var(--text-secondary);font-weight:600;text-transform:uppercase;letter-spacing:.03em}.headers-pre[data-v-4331eb78],.body-pre[data-v-4331eb78]{margin:0;padding:.75rem;background:#f8fafc;border-radius:var(--radius);font-size:.8rem;line-height:1.4;overflow-x:auto;white-space:pre-wrap;word-break:break-all;max-height:280px;overflow-y:auto}.shadowsocks-page[data-v-2ef4dba1]{display:flex;flex-direction:column;gap:1.25rem}.card[data-v-2ef4dba1]{background:var(--bg-card);border-radius:var(--radius);padding:1.5rem;box-shadow:0 1px 3px #0000000f}.card h3[data-v-2ef4dba1]{font-size:.95rem;font-weight:600;margin-bottom:.35rem}.desc[data-v-2ef4dba1]{color:var(--text-secondary);font-size:.875rem;line-height:1.5}.enable-row[data-v-2ef4dba1]{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem}.toggle-btn[data-v-2ef4dba1]{flex-shrink:0;width:44px;height:24px;border-radius:12px;background:var(--switch-off);padding:2px;border:none;cursor:pointer;transition:background .2s;margin-top:.2rem}.toggle-btn.on[data-v-2ef4dba1]{background:var(--switch-on)}.toggle-btn[data-v-2ef4dba1]:disabled{opacity:.5;cursor:not-allowed}.toggle-knob[data-v-2ef4dba1]{display:block;width:20px;height:20px;border-radius:50%;background:#fff;transition:transform .2s;box-shadow:0 1px 3px #0003}.toggle-btn.on .toggle-knob[data-v-2ef4dba1]{transform:translate(20px)}.fields[data-v-2ef4dba1]{display:grid;grid-template-columns:1fr 1fr;gap:1rem;margin-bottom:1rem}.field-wide[data-v-2ef4dba1]{grid-column:span 2}.field label[data-v-2ef4dba1]{display:block;font-size:.875rem;margin-bottom:.35rem;color:var(--text-secondary)}.field input[data-v-2ef4dba1],.field select[data-v-2ef4dba1]{width:100%;padding:.5rem .75rem;border:1px solid var(--border);border-radius:var(--radius);font-size:.9rem}.password-wrap[data-v-2ef4dba1]{position:relative}.password-wrap input[data-v-2ef4dba1]{padding-right:2.5rem}.eye-btn[data-v-2ef4dba1]{position:absolute;right:.5rem;top:50%;transform:translateY(-50%);background:none;border:none;cursor:pointer;color:var(--text-secondary);padding:.2rem;display:flex;align-items:center}.eye-btn svg[data-v-2ef4dba1]{width:1rem;height:1rem}.eye-btn[data-v-2ef4dba1]:hover{color:var(--text-primary)}.actions[data-v-2ef4dba1]{margin-top:.25rem}.btn[data-v-2ef4dba1]{display:inline-flex;align-items:center;gap:.35rem;padding:.45rem .85rem;font-size:.875rem;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg-card);cursor:pointer;color:var(--text-primary);transition:background .15s}.btn[data-v-2ef4dba1]:hover:not(:disabled){background:#f1f5f9}.btn[data-v-2ef4dba1]:disabled{opacity:.5;cursor:not-allowed}.btn.primary[data-v-2ef4dba1]{background:var(--btn-primary);color:#fff;border-color:var(--btn-primary)}.btn.primary[data-v-2ef4dba1]:hover:not(:disabled){background:var(--btn-primary-hover);border-color:var(--btn-primary-hover)}.btn.danger[data-v-2ef4dba1]{color:#dc2626;border-color:#fecaca}.btn.danger[data-v-2ef4dba1]:hover:not(:disabled){background:#fef2f2}.btn.icon-only[data-v-2ef4dba1]{padding:.45rem}.btn.icon-only svg[data-v-2ef4dba1]{width:.9rem;height:.9rem}.btn-spinner[data-v-2ef4dba1]{width:.85rem;height:.85rem;border:2px solid rgba(255,255,255,.4);border-top-color:#fff;border-radius:50%;animation:spin-2ef4dba1 .6s linear infinite;flex-shrink:0}@keyframes spin-2ef4dba1{to{transform:rotate(360deg)}}.status-header[data-v-2ef4dba1]{display:flex;align-items:center;justify-content:space-between;margin-bottom:.75rem}.status-header h3[data-v-2ef4dba1]{margin-bottom:0}.status-actions[data-v-2ef4dba1]{display:flex;gap:.5rem}.status-line[data-v-2ef4dba1]{display:flex;align-items:center;gap:.5rem;margin-bottom:.35rem}.status-dot[data-v-2ef4dba1]{width:8px;height:8px;border-radius:50%;flex-shrink:0}.status-dot.on[data-v-2ef4dba1]{background:var(--switch-on);box-shadow:0 0 0 3px #22c55e33;animation:pulse-2ef4dba1 2s ease-in-out infinite}.status-dot.off[data-v-2ef4dba1]{background:#cbd5e1}@keyframes pulse-2ef4dba1{0%,to{box-shadow:0 0 0 3px #22c55e33}50%{box-shadow:0 0 0 5px #22c55e14}}.status-on[data-v-2ef4dba1]{color:var(--switch-on);font-weight:600;font-size:.9rem}.status-off[data-v-2ef4dba1]{color:var(--text-secondary);font-size:.9rem}.status-pid[data-v-2ef4dba1],.status-port[data-v-2ef4dba1]{font-size:.82rem;color:var(--text-secondary)}.status-error[data-v-2ef4dba1]{font-size:.875rem;color:#dc2626;margin-top:.35rem}.log-section[data-v-2ef4dba1]{margin-top:1rem;border-top:1px solid var(--border);padding-top:.75rem}.log-toggle[data-v-2ef4dba1]{display:flex;align-items:center;gap:.35rem;background:none;border:none;cursor:pointer;font-size:.875rem;color:var(--text-secondary);padding:0}.log-toggle svg[data-v-2ef4dba1]{width:.9rem;height:.9rem;transition:transform .15s}.log-toggle[data-v-2ef4dba1]:hover{color:var(--text-primary)}.log-count[data-v-2ef4dba1]{font-size:.78rem;background:#f1f5f9;padding:.1rem .4rem;border-radius:999px}.log-pre[data-v-2ef4dba1]{margin-top:.75rem;padding:.75rem;background:#0f172a;color:#94a3b8;border-radius:var(--radius);font-size:.78rem;line-height:1.5;max-height:240px;overflow-y:auto;white-space:pre-wrap;word-break:break-all}:root{--bg-sidebar: #1e293b;--bg-sidebar-hover: #334155;--bg-sidebar-active: #2563eb;--text-sidebar: #e2e8f0;--text-sidebar-muted: #94a3b8;--bg-main: #f8fafc;--bg-card: #ffffff;--text-primary: #0f172a;--text-secondary: #64748b;--border: #e2e8f0;--switch-on: #22c55e;--switch-off: #cbd5e1;--btn-primary: #2563eb;--btn-primary-hover: #1d4ed8;--radius: 8px;--font: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif}*{box-sizing:border-box;margin:0;padding:0}body{font-family:var(--font);background:var(--bg-main);color:var(--text-primary);line-height:1.5;min-height:100vh}#app{min-height:100vh;display:flex;flex-direction:column}button{font-family:inherit;cursor:pointer;border:none;border-radius:var(--radius)}input,select{font-family:inherit;border-radius:var(--radius);outline:none;transition:border-color .15s}input:focus,select:focus{border-color:var(--btn-primary);box-shadow:0 0 0 3px #2563eb1f}a{color:var(--btn-primary);text-decoration:none}a:hover{text-decoration:underline}
package/dist/index.html CHANGED
@@ -4,11 +4,8 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>PAC 代理控制台</title>
7
- <link rel="preconnect" href="https://fonts.googleapis.com" />
8
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
- <link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700&display=swap" rel="stylesheet" />
10
- <script type="module" crossorigin src="/assets/index-DD-_gVYC.js"></script>
11
- <link rel="stylesheet" crossorigin href="/assets/index-IXkmSGx6.css">
7
+ <script type="module" crossorigin src="/assets/index-CweQSJse.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-H0IGlOgE.css">
12
9
  </head>
13
10
  <body>
14
11
  <div id="app"></div>
package/lib/index.js CHANGED
@@ -1,18 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { createRequire } from 'module';
4
3
  import { Command } from 'commander';
4
+ import { createRequire } from 'module';
5
5
  import { startServer } from './server.js';
6
6
 
7
7
  const require = createRequire(import.meta.url);
8
- const pkg = require('../package.json');
9
8
 
10
9
  const program = new Command();
11
10
 
12
11
  program
13
12
  .name('pac-proxy')
14
13
  .description('PAC/全局代理本地控制台')
15
- .version(pkg.version);
14
+ .version(require('../package.json').version);
16
15
 
17
16
  program
18
17
  .command('serve')
@@ -10,12 +10,21 @@ const DEFAULT_CONFIG = {
10
10
  proxy: {
11
11
  enabled: false,
12
12
  mode: 'pac',
13
- upstream: '',
13
+ upstream: 'socks5://127.0.0.1:1080',
14
14
  httpPort: 5175,
15
15
  httpsPort: 5176,
16
16
  applySystemProxy: true,
17
17
  },
18
18
  pacRules: [],
19
+ sslocal: {
20
+ enabled: false,
21
+ server: '',
22
+ serverPort: 8388,
23
+ localPort: 1080,
24
+ password: '',
25
+ method: 'aes-256-gcm',
26
+ timeout: 300,
27
+ },
19
28
  };
20
29
 
21
30
  function getStoreDir() {
@@ -51,6 +60,7 @@ export function loadConfig() {
51
60
  mode,
52
61
  proxy: { ...DEFAULT_CONFIG.proxy, ...(data.proxy || {}) },
53
62
  pacRules: Array.isArray(data.pacRules) ? data.pacRules : DEFAULT_CONFIG.pacRules,
63
+ sslocal: { ...DEFAULT_CONFIG.sslocal, ...(data.sslocal || {}) },
54
64
  };
55
65
  } catch {
56
66
  return JSON.parse(JSON.stringify(DEFAULT_CONFIG));
@@ -92,7 +102,14 @@ export function getPacRules() {
92
102
  }
93
103
 
94
104
  export function setPacRules(rules) {
95
- const list = Array.isArray(rules) ? rules : (rules?.rules ?? []);
105
+ const raw = Array.isArray(rules) ? rules : (rules?.rules ?? []);
106
+ const seen = new Set();
107
+ const list = raw.filter((r) => {
108
+ const key = (r.pattern || '').trim();
109
+ if (!key || seen.has(key)) return false;
110
+ seen.add(key);
111
+ return true;
112
+ });
96
113
  const config = loadConfig();
97
114
  if (config.mode === 'remote') {
98
115
  saveRemoteConfig({ pacRules: list });
@@ -108,6 +125,9 @@ export function getProxyConfig() {
108
125
  const proxy = { ...DEFAULT_CONFIG.proxy, ...config.proxy };
109
126
  proxy.enabled = proxy.enabled === true || proxy.enabled === 'true';
110
127
  proxy.applySystemProxy = proxy.applySystemProxy !== false && proxy.applySystemProxy !== 'false';
128
+ if (!proxy.upstream || !proxy.upstream.trim()) {
129
+ proxy.upstream = DEFAULT_CONFIG.proxy.upstream;
130
+ }
111
131
  return proxy;
112
132
  }
113
133
 
@@ -120,3 +140,20 @@ export function setProxyConfig(proxy) {
120
140
  saveConfig(config);
121
141
  return config.proxy;
122
142
  }
143
+
144
+ export function getSslocalConfig() {
145
+ const config = loadConfig();
146
+ return { ...DEFAULT_CONFIG.sslocal, ...(config.sslocal || {}) };
147
+ }
148
+
149
+ export function setSslocalConfig(sslocal) {
150
+ const config = loadConfig();
151
+ config.sslocal = { ...DEFAULT_CONFIG.sslocal, ...sslocal };
152
+ config.sslocal.enabled = config.sslocal.enabled === true || config.sslocal.enabled === 'true';
153
+ saveConfig(config);
154
+ return config.sslocal;
155
+ }
156
+
157
+ export function getSslocalConfigPath() {
158
+ return path.join(getStoreDir(), 'sslocal-config.json');
159
+ }
package/lib/server.js CHANGED
@@ -5,7 +5,7 @@ import dotenv from 'dotenv';
5
5
  import express from 'express';
6
6
  import cors from 'cors';
7
7
  import { createServer } from 'http';
8
- import { loadConfig, saveConfig, getPacRules, setPacRules, getProxyConfig, setProxyConfig } from './local-store.js';
8
+ import { loadConfig, saveConfig, getPacRules, setPacRules, getProxyConfig, setProxyConfig, getSslocalConfig, setSslocalConfig } from './local-store.js';
9
9
  import { getRecords as getTrafficRecords } from './traffic-log.js';
10
10
  import { getCaptures } from './capture-log.js';
11
11
  import { getPort } from 'get-port-please';
@@ -13,6 +13,7 @@ import { startProxyServer, stopProxyServer, getProxyStatus } from './proxy-serve
13
13
  import { startMitmProxyServer, stopMitmProxyServer, getMitmProxyStatus, getCaCertPath, hasCaCert } from './mitm-proxy-server.js';
14
14
  import { setSystemProxy, clearSystemProxy } from './system-proxy.js';
15
15
  import { generatePacJs } from './pac-file.js';
16
+ import { startSslocal, stopSslocal, getSslocalStatus } from './sslocal-manager.js';
16
17
 
17
18
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
18
19
  const envDir = path.resolve(__dirname, '..');
@@ -25,6 +26,19 @@ let controlPanelPort = null;
25
26
 
26
27
  async function applyProxyService(proxyOverride) {
27
28
  const proxy = proxyOverride ?? getProxyConfig();
29
+ const sslocalCfg = getSslocalConfig();
30
+
31
+ // 若 sslocal 启用,先确保进程运行,并覆盖 upstream
32
+ if (sslocalCfg.enabled && sslocalCfg.server) {
33
+ try {
34
+ const status = getSslocalStatus();
35
+ if (!status.running) await startSslocal(sslocalCfg);
36
+ proxy.upstream = `socks5://127.0.0.1:${sslocalCfg.localPort || 1080}`;
37
+ } catch (e) {
38
+ console.error('sslocal 启动失败:', e.message);
39
+ }
40
+ }
41
+
28
42
  const isMitm = (proxy.mode || '') === 'mitm';
29
43
  const prevStatus = isMitm ? getMitmProxyStatus() : getProxyStatus();
30
44
  const wasProxyRunning = prevStatus.running === true;
@@ -226,9 +240,56 @@ export async function startServer(port) {
226
240
  res.sendFile(path.join(distPath, 'index.html'));
227
241
  });
228
242
 
243
+ // ---------- sslocal API ----------
244
+ app.get('/api/local/sslocal', (req, res) => {
245
+ try { res.json(getSslocalConfig()); }
246
+ catch (e) { res.status(500).json({ error: String(e.message) }); }
247
+ });
248
+
249
+ app.put('/api/local/sslocal', async (req, res) => {
250
+ try {
251
+ const cfg = setSslocalConfig(req.body);
252
+ // 若已启用且进程未运行则启动;若禁用则停止
253
+ if (cfg.enabled && cfg.server) {
254
+ const status = getSslocalStatus();
255
+ if (!status.running) await startSslocal(cfg);
256
+ } else if (!cfg.enabled) {
257
+ await stopSslocal();
258
+ }
259
+ res.json(cfg);
260
+ } catch (e) { res.status(500).json({ error: String(e.message) }); }
261
+ });
262
+
263
+ app.get('/api/local/sslocal-status', (req, res) => {
264
+ try { res.json(getSslocalStatus()); }
265
+ catch (e) { res.status(500).json({ running: false, pid: null, error: String(e.message), logs: [] }); }
266
+ });
267
+
268
+ app.post('/api/local/sslocal/start', async (req, res) => {
269
+ try {
270
+ const cfg = getSslocalConfig();
271
+ if (!cfg.server) return res.status(400).json({ error: '请先配置服务器地址' });
272
+ await startSslocal(cfg);
273
+ res.json(getSslocalStatus());
274
+ } catch (e) { res.status(500).json({ error: String(e.message) }); }
275
+ });
276
+
277
+ app.post('/api/local/sslocal/stop', async (req, res) => {
278
+ try {
279
+ await stopSslocal();
280
+ res.json({ ok: true });
281
+ } catch (e) { res.status(500).json({ error: String(e.message) }); }
282
+ });
283
+
229
284
  const actualPort = await getPort({ port, portRange: [port, port + 100] });
230
285
  const server = createServer(app);
231
286
 
287
+ // 进程退出时清理 sslocal 子进程
288
+ const cleanup = () => { stopSslocal().catch(() => {}); };
289
+ process.once('exit', cleanup);
290
+ process.once('SIGINT', () => { cleanup(); process.exit(0); });
291
+ process.once('SIGTERM', () => { cleanup(); process.exit(0); });
292
+
232
293
  server.listen(actualPort, '127.0.0.1', async () => {
233
294
  controlPanelPort = actualPort;
234
295
  console.log(`控制台已启动: http://127.0.0.1:${actualPort}`);
@@ -0,0 +1,146 @@
1
+ import { spawn } from 'child_process';
2
+ import fs from 'fs';
3
+ import net from 'net';
4
+ import path from 'path';
5
+ import { createRequire } from 'module';
6
+ import { getSslocalConfigPath } from './local-store.js';
7
+
8
+ const require = createRequire(import.meta.url);
9
+
10
+ const LOG_MAX = 200;
11
+
12
+ const PKG_MAP = {
13
+ 'win32-x64': 'pac-proxy-sslocal-win32-x64',
14
+ 'darwin-x64': 'pac-proxy-sslocal-darwin-x64',
15
+ 'linux-x64': 'pac-proxy-sslocal-linux-x64',
16
+ };
17
+
18
+ let _proc = null;
19
+ let _logs = [];
20
+ let _lastError = null;
21
+
22
+ function getBinaryPath() {
23
+ const key = `${process.platform}-${process.arch}`;
24
+ const pkgName = PKG_MAP[key];
25
+ if (pkgName) {
26
+ try {
27
+ const pkgDir = path.dirname(require.resolve(`${pkgName}/package.json`));
28
+ const bin = process.platform === 'win32' ? 'sslocal.exe' : 'sslocal';
29
+ const p = path.join(pkgDir, bin);
30
+ if (fs.existsSync(p)) return p;
31
+ } catch (_) {}
32
+ }
33
+ throw new Error(
34
+ `当前平台 ${process.platform}/${process.arch} 没有可用的 sslocal 二进制。\n` +
35
+ `请手动安装:npm install ${PKG_MAP[key] ?? '对应平台包'}`
36
+ );
37
+ }
38
+
39
+ function ensureExecutable(binPath) {
40
+ if (process.platform !== 'win32') {
41
+ try { fs.chmodSync(binPath, 0o755); } catch (_) {}
42
+ }
43
+ }
44
+
45
+ function pushLog(line) {
46
+ _logs.push(line);
47
+ if (_logs.length > LOG_MAX) _logs.shift();
48
+ }
49
+
50
+ function waitForPort(port, timeoutMs = 4000) {
51
+ return new Promise((resolve) => {
52
+ const deadline = Date.now() + timeoutMs;
53
+ function attempt() {
54
+ const sock = new net.Socket();
55
+ sock.setTimeout(300);
56
+ sock.connect(port, '127.0.0.1', () => {
57
+ sock.destroy();
58
+ resolve(true);
59
+ });
60
+ sock.on('error', () => {
61
+ sock.destroy();
62
+ if (Date.now() < deadline) setTimeout(attempt, 200);
63
+ else resolve(false);
64
+ });
65
+ sock.on('timeout', () => {
66
+ sock.destroy();
67
+ if (Date.now() < deadline) setTimeout(attempt, 200);
68
+ else resolve(false);
69
+ });
70
+ }
71
+ attempt();
72
+ });
73
+ }
74
+
75
+ export async function startSslocal(cfg) {
76
+ if (_proc) await stopSslocal();
77
+
78
+ const binPath = getBinaryPath();
79
+ if (!fs.existsSync(binPath)) {
80
+ throw new Error(`sslocal 二进制不存在: ${binPath}(当前平台: ${process.platform})`);
81
+ }
82
+ ensureExecutable(binPath);
83
+
84
+ // 写入临时 config 文件
85
+ const configPath = getSslocalConfigPath();
86
+ const sslocalCfg = {
87
+ server: cfg.server,
88
+ server_port: Number(cfg.serverPort) || 8388,
89
+ local_address: '127.0.0.1',
90
+ local_port: Number(cfg.localPort) || 1080,
91
+ password: cfg.password,
92
+ method: cfg.method || 'aes-256-gcm',
93
+ timeout: Number(cfg.timeout) || 300,
94
+ };
95
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
96
+ fs.writeFileSync(configPath, JSON.stringify(sslocalCfg, null, 2), 'utf-8');
97
+
98
+ _logs = [];
99
+ _lastError = null;
100
+
101
+ _proc = spawn(binPath, ['-c', configPath], {
102
+ detached: false,
103
+ stdio: ['ignore', 'pipe', 'pipe'],
104
+ });
105
+
106
+ _proc.stdout.on('data', (d) => {
107
+ String(d).split('\n').filter(Boolean).forEach(pushLog);
108
+ });
109
+ _proc.stderr.on('data', (d) => {
110
+ String(d).split('\n').filter(Boolean).forEach(pushLog);
111
+ });
112
+ _proc.on('exit', (code, signal) => {
113
+ _lastError = code !== 0 ? `进程退出 code=${code} signal=${signal}` : null;
114
+ _proc = null;
115
+ });
116
+ _proc.on('error', (e) => {
117
+ _lastError = e.message;
118
+ _proc = null;
119
+ });
120
+
121
+ const localPort = Number(cfg.localPort) || 1080;
122
+ const ready = await waitForPort(localPort);
123
+ if (!ready) {
124
+ const recentLogs = _logs.slice(-10).join('\n');
125
+ throw new Error(`sslocal 启动超时,端口 ${localPort} 未就绪。\n${recentLogs}`);
126
+ }
127
+ }
128
+
129
+ export async function stopSslocal() {
130
+ if (!_proc) return;
131
+ try {
132
+ _proc.kill('SIGTERM');
133
+ await new Promise((r) => setTimeout(r, 300));
134
+ if (_proc) _proc.kill('SIGKILL');
135
+ } catch (_) {}
136
+ _proc = null;
137
+ }
138
+
139
+ export function getSslocalStatus() {
140
+ return {
141
+ running: _proc !== null && !_proc.killed,
142
+ pid: _proc?.pid ?? null,
143
+ error: _lastError,
144
+ logs: [..._logs],
145
+ };
146
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pac-proxy-cli",
3
- "version": "1.0.4",
3
+ "version": "1.1.2",
4
4
  "description": "node pac proxy and web control panel",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -16,7 +16,12 @@
16
16
  "scripts": {
17
17
  "dev": "node lib/index.js serve --port 5174",
18
18
  "build": "vite build",
19
- "prepublishOnly": "pnpm run build"
19
+ "prepublishOnly": "npm run build"
20
+ },
21
+ "optionalDependencies": {
22
+ "pac-proxy-sslocal-win32-x64": "workspace:*",
23
+ "pac-proxy-sslocal-darwin-x64": "workspace:*",
24
+ "pac-proxy-sslocal-linux-x64": "workspace:*"
20
25
  },
21
26
  "dependencies": {
22
27
  "commander": "^12.0.0",