apiskill 0.1.5 → 0.1.6

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
+ :root{color-scheme:light;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;background:#eef1f4;color:#18202a;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased}*{box-sizing:border-box}body{margin:0;min-width:320px;min-height:100vh}button,input,select{font:inherit}button{cursor:pointer}.app-shell{display:flex;flex-direction:column;height:100svh;min-height:100vh;padding:18px 18px 0;overflow:hidden}.topbar{display:flex;align-items:center;justify-content:space-between;gap:20px;padding:16px 20px;border:1px solid #d7dde4;border-radius:8px;background:#fff}.eyebrow{margin:0 0 4px;color:#667282;font-size:12px;font-weight:700;letter-spacing:0;text-transform:uppercase}h1,h2,h3,h4,p{margin-top:0}.topbar h1{margin:0;font-size:24px;line-height:1.2}.topbar-actions{display:flex;align-items:center;justify-content:flex-end;flex-wrap:wrap;gap:14px;min-width:0}.version-switcher{display:inline-flex;align-items:center;gap:8px;color:#647182;font-size:12px;font-weight:800}.version-switcher select{width:min(320px,32vw);min-height:36px;padding:0 10px;border:1px solid #cfd7e1;border-radius:6px;outline:0;color:#15202c;background:#fff}.version-manager{min-width:min(420px,38vw)}.version-manager-trigger{display:grid;grid-template-columns:auto minmax(0,1fr);align-items:center;gap:9px;width:100%;min-height:44px;padding:7px 10px;border:1px solid #cfd7e1;border-radius:7px;color:#23303e;background:#fff;text-align:left}.version-manager-trigger:disabled{cursor:not-allowed;opacity:.72}.version-manager-trigger span{display:grid;gap:2px;min-width:0}.version-manager-trigger strong,.version-manager-trigger small{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.version-manager-trigger strong{font-size:13px}.version-manager-trigger small{color:#657181;font-size:11px;font-weight:800}.version-manager-dialog{width:min(900px,100%);max-height:calc(100vh - 40px);overflow:auto;border-radius:8px;border:1px solid #d7dde4;background:#fff;box-shadow:0 18px 60px #12192338}.version-manager-body{display:grid;gap:12px;padding:16px 20px 20px;background:#f7f9fb}.version-message{padding:9px 11px;border:1px solid #cbe5df;border-radius:7px;color:#176b4f;background:#eef9f5;font-size:13px;font-weight:800}.version-message.error{border-color:#f1c1bd;color:#9a2d27;background:#fff4f3}.version-item{display:grid;gap:12px;padding:13px;border:1px solid #dfe5eb;border-radius:8px;background:#fff}.version-item.active{border-color:#7ab6ad;box-shadow:inset 3px 0 #1b6b65}.version-item-main{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:12px;align-items:start}.version-item-title{display:flex;align-items:center;gap:8px;min-width:0}.version-item-title strong{overflow:hidden;color:#202a36;text-overflow:ellipsis;white-space:nowrap}.version-item-title span{padding:3px 7px;border-radius:999px;color:#176b4f;background:#e5f6ef;font-size:11px;font-weight:900}.version-item-main p{margin:4px 0 0;overflow-wrap:anywhere;color:#657181;font-family:SFMono-Regular,Consolas,monospace;font-size:12px}.version-item-stats{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:6px}.version-item-stats span,.version-env-summary span{padding:5px 7px;border-radius:6px;color:#4d5b6c;background:#eef2f6;font-size:12px;font-weight:800}.version-env-summary{display:grid;grid-template-columns:auto minmax(0,1fr);align-items:center;gap:8px}.version-env-summary code{overflow:hidden;padding:7px 9px;border:1px solid #dfe5eb;border-radius:6px;color:#2f3d4d;background:#fbfcfd;text-overflow:ellipsis;white-space:nowrap}.version-env-editor{display:grid;grid-template-columns:minmax(160px,.5fr) minmax(0,1fr);gap:10px}.version-env-editor label{display:grid;gap:5px;min-width:0;color:#647182;font-size:12px;font-weight:800}.version-env-editor input{width:100%;min-height:36px;padding:0 10px;border:1px solid #cfd7e1;border-radius:6px;outline:0;color:#15202c;background:#fff}.version-item-actions{display:flex;flex-wrap:wrap;gap:8px;justify-content:flex-end}.sync-meta{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px;color:#5c6877;font-size:13px}.sync-meta span{padding:5px 8px;border:1px solid #dce2e9;border-radius:6px;background:#f7f9fb}.primary-button,.copy-inline-button{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:36px;border:0;border-radius:6px;color:#fff;background:#1b6b65;font-weight:700;white-space:nowrap}.primary-button{padding:0 14px}.primary-button:disabled{cursor:wait;opacity:.72}.copy-inline-button{padding:0 12px;background:#202a36}.secondary-button{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:36px;padding:0 14px;border:1px solid #ccd5df;border-radius:6px;color:#22303d;background:#fff;font-weight:700;white-space:nowrap}.secondary-button:disabled{cursor:wait;opacity:.72}.danger-button{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:36px;padding:0 14px;border:1px solid #efcbc8;border-radius:6px;color:#b63831;background:#fff7f6;font-weight:700;white-space:nowrap}.icon-secondary-button{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;padding:0;border:1px solid #ccd5df;border-radius:6px;color:#22303d;background:#fff}.source-panel{display:grid;gap:10px;margin-top:12px;padding:12px;border:1px solid #d7dde4;border-radius:8px;background:#fff}.source-panel-header{display:flex;align-items:center;justify-content:space-between;gap:10px}.source-panel-actions{display:inline-flex;flex-wrap:wrap;gap:8px;justify-content:flex-end}.document-toolbar{grid-template-columns:minmax(0,1fr) auto;align-items:center}.document-source-summary{display:grid;min-width:0;gap:3px}.document-source-summary>span{color:#6a7685;font-size:12px;font-weight:700}.document-source-summary strong,.document-source-summary small{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.document-source-summary strong{color:#1d2936;font-size:13px}.document-source-summary small{color:#176b4f;font-size:12px}.document-toolbar>.version-message{grid-column:1 / -1}.source-tabs{display:inline-flex;width:fit-content;gap:4px;padding:3px;border:1px solid #d4dce5;border-radius:7px;background:#fff}.source-tabs button{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:32px;padding:0 12px;border:0;border-radius:5px;color:#566373;background:transparent;font-size:13px;font-weight:800}.source-tabs button.active{color:#fff;background:#1b6b65}.source-form{display:grid;grid-template-columns:minmax(0,1fr) auto auto;gap:10px}.document-dialog{width:min(760px,100%);max-height:calc(100vh - 40px);overflow:auto;border:1px solid #d7dde4;border-radius:8px;background:#fff;box-shadow:0 18px 60px #12192338;animation:dialog-enter .16s ease-out}.document-dialog-tabs{display:flex;gap:2px;overflow-x:auto;padding:10px 20px 0;border-bottom:1px solid #e5eaf0}.document-dialog-tabs button{display:inline-flex;flex:0 0 auto;align-items:center;justify-content:center;gap:6px;min-height:38px;padding:0 12px;border:0;border-bottom:2px solid transparent;color:#5c6877;background:transparent;font-size:13px;font-weight:800}.document-dialog-tabs button.active{border-bottom-color:#1b6b65;color:#155a55}.document-dialog-body{min-height:174px;padding:20px;background:#f7f9fb}.blank-document-fields{display:grid;gap:12px}.document-source-input{min-height:46px;background:#fff}.document-file-picker{display:grid;grid-template-columns:auto minmax(0,1fr);align-content:center;min-height:134px;padding:22px;background:#fff}.document-file-picker>svg{grid-row:1 / span 2}.document-file-picker small{color:#748091}.document-dialog-actions{gap:8px;padding-top:18px}.document-dialog-actions .clear-document-source{margin-right:auto}.source-input,.source-file-picker{display:flex;align-items:center;gap:8px;min-width:0;min-height:38px;padding:0 12px;border:1px solid #cfd7e1;border-radius:7px;background:#f9fafb}.source-file-picker{cursor:pointer}.source-file-picker input{display:none}.source-file-picker span{overflow:hidden;min-width:0;color:#15202c;text-overflow:ellipsis;white-space:nowrap}.source-input input{width:100%;min-width:0;border:0;outline:0;background:transparent;color:#15202c}.source-input textarea{width:100%;min-width:0;min-height:118px;resize:vertical;border:0;outline:0;background:transparent;color:#15202c;font-family:SFMono-Regular,Consolas,monospace;font-size:12px;line-height:1.5}.source-input-multiline{align-items:flex-start;padding-top:10px;padding-bottom:10px}.source-input-multiline svg{margin-top:2px}.source-auth{grid-column:1 / -1;display:grid;grid-template-columns:auto minmax(0,1fr);align-items:center;gap:10px}.source-auth-fields{display:grid;grid-template-columns:repeat(2,minmax(0,220px));gap:8px}.source-auth-fields label{display:flex;align-items:center;gap:8px;min-width:0;min-height:36px;padding:0 10px;border:1px solid #cfd7e1;border-radius:7px;background:#f9fafb}.source-auth-fields input{width:100%;min-width:0;border:0;outline:0;background:transparent;color:#15202c}.source-meta{grid-column:1 / -1;overflow:hidden;color:#657181;font-size:12px;text-overflow:ellipsis;white-space:nowrap}.source-meta span{color:#2f3d4d;font-family:SFMono-Regular,Consolas,monospace}.source-meta small{margin-left:8px;color:#728094;font-weight:800}.workspace{display:grid;flex:1 1 auto;grid-template-columns:minmax(320px,380px) minmax(0,1fr);gap:16px;min-height:0;margin-top:16px}.sidebar,.detail-pane{min-height:0;border:1px solid #d7dde4;border-radius:8px;background:#fff}.sidebar{display:flex;flex-direction:column;padding:14px}.search-box{display:flex;align-items:center;gap:8px;min-height:42px;padding:0 12px;border:1px solid #cfd7e1;border-radius:7px;background:#f9fafb}.search-box input{width:100%;min-width:0;border:0;outline:0;background:transparent;color:#15202c}.search-clear-button{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;width:24px;height:24px;padding:0;border:0;border-radius:5px;color:#647182;background:transparent}.search-clear-button:hover{color:#b63831;background:#fff0ef}.filters{display:grid;grid-template-columns:1fr auto;gap:10px;margin-top:12px}.filters label:first-child{display:flex;align-items:center;gap:8px;min-width:0;padding:0 10px;border:1px solid #d5dce5;border-radius:7px;background:#fff}.filters select{width:100%;min-height:38px;border:0;outline:0;background:transparent}.checkbox-line{display:inline-flex;align-items:center;gap:6px;color:#566373;font-size:13px;white-space:nowrap}.method-tabs{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:6px;margin-top:12px}.method-tabs button{min-height:32px;border:1px solid #d5dce5;border-radius:6px;color:#536171;background:#fff;font-size:12px;font-weight:800}.method-tabs button.active{color:#fff;border-color:#1b6b65;background:#1b6b65}.endpoint-count{margin:12px 2px 8px;color:#6b7582;font-size:13px}.endpoint-list{flex:1 1 auto;min-height:0;overflow:auto;display:flex;flex-direction:column;gap:6px;padding-right:4px}.endpoint-item{display:grid;grid-template-columns:64px minmax(0,1fr);align-items:start;gap:10px;width:100%;min-height:66px;padding:10px;border:1px solid transparent;border-radius:7px;text-align:left;background:#fff}.endpoint-item:hover{background:#f6f8fa}.endpoint-item.selected{border-color:#1b6b65;background:#edf7f5}.endpoint-main{min-width:0}.endpoint-main strong,.endpoint-main small{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.endpoint-main strong{margin-bottom:4px;color:#1d2732;font-size:14px}.endpoint-main small{color:#647182;font-family:SFMono-Regular,Consolas,monospace;font-size:12px}.method{display:inline-flex;align-items:center;justify-content:center;width:58px;height:24px;border-radius:5px;color:#fff;font-size:11px;font-weight:900}.method-get{background:#246bce}.method-post{background:#168456}.method-put{background:#9a6200}.method-delete{background:#c23131}.method-patch{background:#7953b8}.method-options,.method-head{background:#52606f}.detail-pane{overflow:auto}.opened-api-tabs{position:sticky;top:0;z-index:5;display:flex;align-items:flex-end;gap:4px;overflow-x:auto;overflow-y:hidden;min-height:47px;padding:10px 12px 0;border-bottom:1px solid #dfe5eb;background:#f7f9fb}.opened-api-tab{--tab-accent: #52606f;--tab-soft: #eef2f6;display:inline-grid;grid-template-columns:auto minmax(100px,210px) 20px;align-items:center;gap:7px;min-width:180px;max-width:280px;height:36px;padding:7px 8px 8px;border:1px solid color-mix(in srgb,var(--tab-accent) 45%,#d5dce5);border-bottom-color:#dfe5eb;border-radius:7px 7px 0 0;text-align:left;background:var(--tab-soft);cursor:pointer}.opened-api-tab-get{--tab-accent: #246bce;--tab-soft: #eef5ff}.opened-api-tab-post{--tab-accent: #168456;--tab-soft: #edf8f2}.opened-api-tab-put{--tab-accent: #9a6200;--tab-soft: #fff5e4}.opened-api-tab-delete{--tab-accent: #c23131;--tab-soft: #fff0f0}.opened-api-tab-patch{--tab-accent: #7953b8;--tab-soft: #f4efff}.opened-api-tab-options,.opened-api-tab-head{--tab-accent: #52606f;--tab-soft: #f0f3f6}.opened-api-tab.active{position:relative;margin-bottom:-1px;border-color:var(--tab-accent);border-bottom-color:#fff;background:#fff;box-shadow:inset 0 3px 0 var(--tab-accent)}.opened-api-tab-text{min-width:0}.opened-api-tab-text strong{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.opened-api-tab-text strong{color:#1d2732;font-size:12px;font-weight:800}.opened-api-tab-method{display:inline-flex;align-items:center;justify-content:center;min-width:34px;height:18px;padding:0 5px;border-radius:4px;color:#fff;font-size:9px;font-weight:900;line-height:1}.opened-api-tab-close{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;border:0;border-radius:5px;color:var(--tab-accent);background:transparent;cursor:pointer}.opened-api-tab-close:hover{color:#b63831;background:#fff0ef}.endpoint-detail{padding:22px}.detail-header{display:flex;align-items:flex-start;justify-content:space-between;gap:18px;padding-bottom:18px;border-bottom:1px solid #e1e6ec}.detail-actions{display:flex;align-items:center;gap:8px;margin-left:auto}.endpoint-title-line{display:flex;align-items:center;gap:10px}.endpoint-title-line h2{margin:0;color:#121923;font-size:22px;line-height:1.25}.path-row{display:flex;align-items:center;gap:8px;min-width:0;margin-top:10px}.path-line{display:block;min-width:0;color:#354253;font-size:13px;white-space:normal;word-break:break-all}.path-copy-button{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;gap:4px;min-height:28px;padding:0 8px;border:1px solid #d4dce5;border-radius:6px;color:#415166;background:#fff;font-size:12px;font-weight:800}.path-copy-button:hover{border-color:#9fb0c1;background:#f7f9fb}.tag-row{display:flex;flex-wrap:wrap;gap:6px;margin-top:12px}.tag-row span{padding:4px 8px;border-radius:5px;color:#475364;background:#edf1f5;font-size:12px;font-weight:700}.description{margin:16px 0 0;color:#4f5c6a;line-height:1.7}.copy-preview{margin-top:18px;border:1px solid #d9e0e7;border-radius:8px;background:#f8fafb}.copy-preview-toolbar{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px;border-bottom:1px solid #dfe5eb}.copy-tabs{display:inline-flex;gap:4px;padding:3px;border:1px solid #d4dce5;border-radius:7px;background:#fff}.copy-tabs button{display:inline-flex;align-items:center;gap:6px;min-height:30px;padding:0 10px;border:0;border-radius:5px;color:#566373;background:transparent;font-size:13px;font-weight:800}.copy-tabs button.active{color:#fff;background:#1b6b65}.copy-preview-content{overflow:auto;min-height:calc(3 * 1.6em + 28px);max-height:calc(25.6em + 28px);margin:0;padding:14px;color:#e5edf6;background:#111827;font-family:SFMono-Regular,Consolas,monospace;font-size:12px;line-height:1.6;white-space:pre-wrap;overflow-wrap:anywhere}.request-tester{display:grid;gap:14px;padding:14px;background:#fff}.endpoint-links-config{display:grid;gap:12px;padding:14px;background:#fff}.links-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.links-actions,.link-editor-actions{display:flex;flex-wrap:wrap;gap:8px;justify-content:flex-end}.link-editor-panel{min-width:0}.link-button{text-decoration:none}.request-line{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:end;gap:10px}.request-actions{display:flex;flex-wrap:wrap;gap:8px;justify-content:flex-end}.request-line label,.tester-form-grid label,.param-editor label{display:grid;gap:5px;min-width:0;color:#647182;font-size:12px;font-weight:800}.request-line input,.tester-form-grid input,.tester-form-grid select,.param-editor input{width:100%;min-height:36px;padding:0 10px;border:1px solid #cfd7e1;border-radius:6px;outline:0;color:#15202c;background:#fff}.request-preview{display:block;overflow:hidden;padding:9px 10px;border:1px solid #dfe5eb;border-radius:6px;color:#334254;background:#f7f9fb;font-size:12px;text-overflow:ellipsis;white-space:nowrap}.tester-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.tester-panel{min-width:0;padding:12px;border:1px solid #dfe5eb;border-radius:7px;background:#fbfcfd}.tester-panel-title{display:flex;align-items:center;gap:6px;margin-bottom:10px;color:#243242;font-size:13px;font-weight:900}.tester-form-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.param-editor+.param-editor{margin-top:12px}.param-editor h4{margin:0 0 8px;color:#516070;font-size:12px}.param-editor label+label{margin-top:8px}.param-editor span{display:inline-flex;align-items:center;gap:3px}.param-editor b{color:#c23131}.tester-panel textarea{width:100%;min-height:168px;resize:vertical;padding:10px;border:1px solid #cfd7e1;border-radius:6px;outline:0;color:#1f2b38;background:#fff;font-family:SFMono-Regular,Consolas,monospace;font-size:12px;line-height:1.5}.tester-panel textarea:disabled{color:#8792a1;background:#f2f5f8}.response-panel{background:#fff}.response-meta-line{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:10px}.response-meta-line span{padding:5px 8px;border-radius:5px;color:#536171;background:#eef2f6;font-size:12px;font-weight:800}.response-meta-line .status-ok{color:#176b4f;background:#e5f6ef}.response-meta-line .status-error{color:#a8322b;background:#fff0ef}.response-panel pre{overflow:auto;max-height:360px;margin:0;padding:12px;border-radius:6px;color:#202a36;background:#f7f9fb;font-family:SFMono-Regular,Consolas,monospace;font-size:12px;line-height:1.55}.detail-section{padding:20px 0;border-bottom:1px solid #e8edf2}.section-title{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}.section-title h3{margin:0;font-size:16px}.section-title span{min-width:28px;padding:3px 8px;border-radius:999px;color:#1b6b65;background:#e7f4f2;text-align:center;font-size:12px;font-weight:800}.media-block+.media-block,.response-block+.response-block{margin-top:18px}.media-block h4{margin:0 0 8px;color:#516070;font-family:SFMono-Regular,Consolas,monospace;font-size:13px}.response-heading{display:flex;align-items:center;gap:10px;margin-bottom:10px}.response-heading strong{padding:4px 8px;border-radius:5px;color:#fff;background:#202a36;font-size:12px}.response-heading span{color:#657181;font-size:13px}.table-wrap{overflow:auto;border:1px solid #dfe5eb;border-radius:7px}table{width:100%;min-width:920px;border-collapse:collapse;font-size:13px}th,td{padding:10px 12px;border-bottom:1px solid #edf1f4;text-align:left;vertical-align:top}th{position:sticky;top:0;z-index:1;color:#526171;background:#f7f9fb;font-size:12px;font-weight:800}tr:last-child td{border-bottom:0}td code{color:#17202b;font-family:SFMono-Regular,Consolas,monospace;font-size:12px}.muted{margin:0;color:#798596}.inline-error{display:flex;align-items:center;gap:8px;margin-top:12px;padding:10px 12px;border:1px solid #f1c1bd;border-radius:7px;color:#9a2d27;background:#fff4f3}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:30;display:grid;place-items:center;padding:20px;background:#1219237a;animation:backdrop-enter .14s ease-out}.import-dialog{width:min(720px,100%);max-height:calc(100vh - 40px);overflow:auto;border-radius:8px;border:1px solid #d7dde4;background:#fff;box-shadow:0 18px 60px #12192338}.auth-dialog{width:min(560px,100%);max-height:calc(100vh - 40px);overflow:auto;border-radius:8px;border:1px solid #d7dde4;background:#fff;box-shadow:0 18px 60px #12192338}.cache-settings-dialog{width:min(680px,100%);max-height:calc(100vh - 40px);overflow:auto;border:1px solid #d7dde4;border-radius:8px;background:#fff;box-shadow:0 18px 60px #12192338}.cache-settings-body{display:grid;gap:16px;padding:18px 20px}.cache-location-row,.cache-settings-details>div{display:grid;grid-template-columns:150px minmax(0,1fr);align-items:start;gap:14px}.cache-location-row>span,.cache-settings-details dt{color:#657181;font-size:12px;font-weight:800}.cache-location-row code,.cache-settings-details dd{min-width:0;margin:0;overflow-wrap:anywhere;color:#1d2a36;font-size:12px}.cache-settings-field{display:grid;gap:7px;color:#334152;font-size:12px;font-weight:800}.cache-settings-field input{width:100%;min-height:40px;padding:0 11px;border:1px solid #bdc8d4;border-radius:6px;outline:0;color:#15202c;background:#fff}.cache-settings-field input:focus{border-color:#1b6b65;box-shadow:0 0 0 3px #1b6b651f}.cache-settings-notice{display:grid;grid-template-columns:auto minmax(0,1fr);gap:10px;padding:12px 0;border-block:1px solid #dbe7e4;color:#285c55}.cache-settings-notice.warning{border-color:#ead9ad;color:#765b16}.cache-settings-notice strong{font-size:13px}.cache-settings-notice p{margin:4px 0 0;color:#657181;font-size:12px;line-height:1.55}.cache-settings-details{display:grid;gap:9px;margin:0}.cache-settings-actions{justify-content:space-between}.cache-settings-actions>div{display:flex;gap:8px}.api-config-dialog{width:80vw;min-width:1000px;max-width:80vw;max-height:calc(100vh - 40px);overflow:auto;border-radius:8px;border:1px solid #d7dde4;background:#fff;box-shadow:0 18px 60px #12192338}.import-dialog-header{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;padding:18px 20px 14px;border-bottom:1px solid #e5eaf0}.import-dialog-header h2{margin:0;font-size:20px}.import-badge{display:inline-flex;align-items:center;min-height:28px;padding:0 10px;border-radius:999px;font-size:12px;font-weight:800;white-space:nowrap}.import-badge.running{color:#8a5b00;background:#fff4d8}.import-badge.success{color:#176b4f;background:#e5f6ef}.import-badge.error{color:#a8322b;background:#fff0ef}.progress-track{height:8px;margin:16px 20px 0;overflow:hidden;border-radius:999px;background:#e9eef3}.progress-bar{height:100%;border-radius:inherit;background:#1b6b65;transition:width .24s ease}.progress-bar.running{background:linear-gradient(90deg,#1b6b65,#4f8fcb)}.progress-bar.success{background:#168456}.progress-bar.error{background:#c23131}.import-summary{display:grid;gap:12px;padding:18px 20px}.auth-dialog-body{display:grid;gap:14px;padding:18px 20px}.api-config-body{display:grid;gap:14px;padding:18px 20px;background:#f7f9fb}.api-config-tabs{display:inline-flex;width:fit-content;gap:4px;padding:3px;border:1px solid #d4dce5;border-radius:7px;background:#fff}.api-config-tabs button{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:32px;padding:0 12px;border:0;border-radius:5px;color:#566373;background:transparent;font-size:13px;font-weight:800}.api-config-tabs button.active{color:#fff;background:#1b6b65}.api-config-errors{display:flex;align-items:flex-start;gap:10px;padding:10px 12px;border:1px solid #efcbc8;border-radius:7px;color:#8f2f29;background:#fff7f6}.api-config-errors strong{display:block;margin-bottom:4px;font-size:13px}.api-config-errors p{margin:0;font-size:12px;line-height:1.45}.api-cli-editor{width:100%;min-height:calc(4.65em + 24px);max-height:calc(24.8em + 24px);overflow-y:auto;resize:vertical;padding:12px;border:1px solid #334155;border-radius:7px;outline:0;color:#e5edf6;background:#111827;font-family:SFMono-Regular,Consolas,monospace;font-size:12px;line-height:1.55}.api-cli-editor::placeholder{color:#94a3b8}.api-cli-editor:focus{border-color:#5aa69d;box-shadow:0 0 0 3px #5aa69d38}.api-config-section{display:grid;gap:12px;padding:14px;border:1px solid #dfe5eb;border-radius:8px;background:#fff}.section-title-row{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.section-title-row h3{margin:0;color:#243242;font-size:15px}.section-title-row p{margin:4px 0 0;color:#657181;font-size:12px}.section-title-row code{max-width:420px;overflow:hidden;padding:5px 7px;border:1px solid #dce2e9;border-radius:6px;color:#415166;background:#f7f9fb;text-overflow:ellipsis;white-space:nowrap}.api-config-grid{display:grid;grid-template-columns:140px repeat(2,minmax(0,1fr));gap:10px}.api-config-grid.compact{grid-template-columns:repeat(3,minmax(0,1fr)) auto}.api-config-grid label,.field-config-row label{display:grid;gap:5px;min-width:0;color:#647182;font-size:12px;font-weight:800}.api-config-grid input,.api-config-grid select,.api-config-grid textarea,.field-config-row input,.field-config-row select{width:100%;min-height:36px;padding:0 10px;border:1px solid #cfd7e1;border-radius:6px;outline:0;color:#15202c;background:#fff}.api-config-grid textarea{min-height:82px;padding:10px;resize:vertical;line-height:1.45}.span-2{grid-column:span 2}.span-all{grid-column:1 / -1}.inline-check{display:inline-flex;align-items:center;gap:6px;min-height:32px;color:#415166;font-size:13px;font-weight:800}.field-config-table{display:grid;gap:8px;overflow-x:auto}.field-config-item{display:grid;gap:8px}.field-config-head,.field-config-row{display:grid;grid-template-columns:minmax(150px,1.1fr) 120px 58px minmax(180px,1.2fr) minmax(120px,.8fr) 38px;gap:8px;align-items:center;min-width:760px}.field-config-head.with-location,.field-config-row.with-location{grid-template-columns:98px minmax(150px,1.1fr) 120px 58px minmax(180px,1.2fr) minmax(120px,.8fr) 38px}.field-config-head{color:#657181;font-size:12px;font-weight:900}.field-check{place-items:center}.field-check input,.inline-check input{width:16px;min-height:16px}.icon-danger-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;border:1px solid #efcbc8;border-radius:6px;color:#b63831;background:#fff7f6}.field-add-row{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:34px;border:1px dashed #b8c4d0;border-radius:6px;color:#415166;background:#fbfcfd;font-weight:800}.child-field-config{display:grid;gap:8px;margin-left:18px;padding:10px;border-left:3px solid #b9d7d3;border-radius:0 7px 7px 0;background:#f7fbfa}.child-field-title{color:#415166;font-size:12px;font-weight:900}.response-config{display:grid;gap:10px;padding:12px;border:1px solid #e3e8ee;border-radius:7px;background:#fbfcfd}.auth-message{display:flex;align-items:flex-start;gap:8px;padding:10px 12px;border:1px solid #d9e0e7;border-radius:7px;color:#2f3d4d;background:#f7f9fb}.auth-target{display:grid;gap:4px}.auth-target span,.auth-fields label{color:#657181;font-size:12px;font-weight:800}.auth-target strong{overflow-wrap:anywhere;color:#1f2b38;font-family:SFMono-Regular,Consolas,monospace;font-size:13px;line-height:1.5}.auth-fields{display:grid;gap:10px}.auth-fields label{display:grid;gap:6px}.auth-fields input{width:100%;min-height:38px;padding:0 10px;border:1px solid #cfd7e1;border-radius:6px;outline:0;color:#15202c;background:#fff}.import-summary div:not(.import-counts):not(.import-error){display:grid;gap:4px}.import-summary span{color:#657181;font-size:12px;font-weight:700}.import-summary strong{overflow-wrap:anywhere;color:#1f2b38;font-family:SFMono-Regular,Consolas,monospace;font-size:13px;line-height:1.5}.import-counts{display:flex;flex-wrap:wrap;gap:8px}.import-counts span{padding:6px 9px;border-radius:6px;color:#1b6b65;background:#e7f4f2}.import-error{display:flex;align-items:flex-start;gap:8px;padding:10px 12px;border:1px solid #f1c1bd;border-radius:7px;color:#9a2d27;background:#fff4f3}.import-dialog-actions{display:flex;justify-content:flex-end;padding:0 20px 18px}.state-screen,.empty-state{display:grid;place-items:center;align-content:center;min-height:100vh;padding:24px;text-align:center}.empty-state{min-height:60vh}.state-icon{width:42px;height:42px;margin-bottom:14px;color:#1b6b65}.state-icon.error{color:#b8312d}.state-screen h1,.empty-state h2{margin-bottom:8px}.spin{animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}@keyframes backdrop-enter{0%{opacity:0}}@keyframes dialog-enter{0%{opacity:0;transform:translateY(8px)}}@media(max-width:980px){.app-shell{height:auto;padding:12px;overflow:visible}.topbar{align-items:flex-start;flex-direction:column}.topbar-actions{width:100%;justify-content:space-between}.version-switcher,.version-switcher select{width:100%}.version-manager{width:100%;min-width:0}.version-item-main,.version-env-editor,.version-env-summary{grid-template-columns:1fr}.version-item-stats,.version-item-actions{justify-content:flex-start}.source-panel{grid-template-columns:1fr}.source-panel-header{align-items:stretch;flex-direction:column}.source-panel-header>.secondary-button,.source-panel-actions,.source-tabs{width:100%}.source-panel-actions{justify-content:stretch}.source-panel-actions .secondary-button{flex:1 1 180px}.source-tabs{flex-wrap:wrap}.document-dialog-tabs{width:100%}.source-form,.source-auth,.source-auth-fields,.api-config-grid,.api-config-grid.compact{grid-template-columns:1fr}.span-2,.span-all{grid-column:auto}.workspace{grid-template-columns:1fr;flex:none;height:auto}.sidebar{max-height:46vh}.detail-pane{min-height:60vh}.detail-header{flex-direction:column}.copy-preview-toolbar{align-items:stretch;flex-direction:column}.copy-tabs,.copy-inline-button{width:100%}.copy-tabs button{flex:1;justify-content:center}.request-line,.tester-grid,.links-grid,.tester-form-grid{grid-template-columns:1fr}}@media(max-width:640px){.filters{grid-template-columns:1fr}.method-tabs{grid-template-columns:repeat(3,minmax(0,1fr))}.topbar-actions,.sync-meta{align-items:stretch;flex-direction:column;width:100%}.primary-button,.secondary-button,.copy-inline-button,.source-tabs{width:100%}.source-tabs button{flex:1}.document-dialog{max-height:calc(100vh - 20px)}.document-dialog-tabs{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));overflow:visible;padding-inline:12px}.document-dialog-tabs button{min-width:0;padding-inline:6px}.document-dialog-body{min-height:210px;padding:16px}.document-dialog-actions .clear-document-source{margin-right:0}.import-dialog-actions .secondary-button{width:100%}.import-dialog-actions{flex-direction:column-reverse;gap:8px}.cache-location-row,.cache-settings-details>div{grid-template-columns:1fr;gap:5px}.cache-settings-actions,.cache-settings-actions>div{display:grid;width:100%}.endpoint-title-line{align-items:flex-start;flex-direction:column}}
package/dist/index.html CHANGED
@@ -4,8 +4,8 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>API Skill Console</title>
7
- <script type="module" crossorigin src="/assets/index-CV6e3HAP.js"></script>
8
- <link rel="stylesheet" crossorigin href="/assets/index-slFOragU.css">
7
+ <script type="module" crossorigin src="/assets/index-B_wAJLu_.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-CHOQ6xhP.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
package/docs/cli.md CHANGED
@@ -35,6 +35,19 @@ You can also run the executable directly:
35
35
  node scripts/apiskill-cli.mjs --help
36
36
  ```
37
37
 
38
+ ## Cache Settings
39
+
40
+ Web, CLI, and MCP share the saved default in `~/.apiskill/config.json`:
41
+
42
+ ```bash
43
+ apiskill cache show
44
+ apiskill cache show --json
45
+ apiskill cache set /absolute/path/to/apiskill-cache
46
+ apiskill cache reset
47
+ ```
48
+
49
+ Changing the default never moves or deletes existing files. Future CLI and MCP processes use the new location immediately; restart Web or use Update Cache Location in version management to copy selected versions. `APISKILL_CACHE_DIR` has priority over the saved setting.
50
+
38
51
  ## Import Documents
39
52
 
40
53
  Check whether a usable cache is available:
package/docs/cli.zh.md CHANGED
@@ -35,6 +35,19 @@ npm run cli -- --help
35
35
  node scripts/apiskill-cli.mjs --help
36
36
  ```
37
37
 
38
+ ## 缓存设置
39
+
40
+ Web、CLI 和 MCP 共用保存在 `~/.apiskill/config.json` 中的默认缓存地址:
41
+
42
+ ```bash
43
+ apiskill cache show
44
+ apiskill cache show --json
45
+ apiskill cache set /absolute/path/to/apiskill-cache
46
+ apiskill cache reset
47
+ ```
48
+
49
+ 修改默认地址不会移动或删除已有缓存文件。后续 CLI、MCP 进程会直接使用新地址,Web 需要重启;也可以在 Web 版本管理中选择“更新缓存地址”,将需要的版本复制到新目录。`APISKILL_CACHE_DIR` 环境变量优先于保存的默认设置。
50
+
38
51
  ## 导入文档
39
52
 
40
53
  先检查当前是否有可用缓存:
package/docs/web.md CHANGED
@@ -20,6 +20,8 @@ npm run dev
20
20
 
21
21
  Open the local URL printed by the terminal.
22
22
 
23
+ Cache Settings in the upper-right corner shows the active session location and changes the saved default. Existing files are never moved or deleted. Restart Web to use the new location; Update Cache Location on a version shows the source and destination before copying while retaining the source file.
24
+
23
25
  ## Import Sources
24
26
 
25
27
  The web app supports four import paths:
package/docs/web.zh.md CHANGED
@@ -20,6 +20,8 @@ npm run dev
20
20
 
21
21
  打开终端输出的本地地址。
22
22
 
23
+ 页面右上角“缓存设置”用于查看当前会话位置和修改默认缓存地址。修改设置不会移动或删除已有文件;Web 重启后使用新地址。版本管理中每个版本的“更新缓存地址”会先展示当前位置和目标位置,确认后复制该版本,并保留源文件。
24
+
23
25
  ## 导入来源
24
26
 
25
27
  “新建文档”弹窗通过 Tab 支持空白文档和四种导入方式:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apiskill",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src",
@@ -34,13 +34,13 @@
34
34
  "lucide-react": "^0.468.0",
35
35
  "react": "^18.3.1",
36
36
  "react-dom": "^18.3.1",
37
+ "typescript": "^5.8.3",
37
38
  "vite": "^6.0.7",
38
39
  "yaml": "^2.8.3"
39
40
  },
40
41
  "devDependencies": {
41
42
  "@types/node": "^22.10.2",
42
43
  "@types/react": "^18.3.18",
43
- "@types/react-dom": "^18.3.5",
44
- "typescript": "^7.0.2"
44
+ "@types/react-dom": "^18.3.5"
45
45
  }
46
46
  }
@@ -19,7 +19,7 @@ import {
19
19
  import { crawlOpenApi, importFromCurl, importFromLocalFile, importFromUrl } from './lib/openapi-importer.mjs';
20
20
  import { checkCache, formatCheckText, queryEndpoints } from './lib/apiskill-core.mjs';
21
21
  import { startMockServer } from './lib/mock-server.mjs';
22
- import { getDefaultCacheDir } from './lib/cache-paths.mjs';
22
+ import { getCacheSettings, getDefaultCacheDir, resetDefaultCacheDir, setDefaultCacheDir } from './lib/cache-paths.mjs';
23
23
 
24
24
  const program = new Command();
25
25
  const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
@@ -41,6 +41,8 @@ Examples:
41
41
  apiskill import-file ./openapi.json
42
42
  apiskill import-curl --file ./request.curl
43
43
  apiskill run web
44
+ apiskill cache show
45
+ apiskill cache set ~/.apiskill/cache
44
46
  apiskill mock
45
47
  apiskill document create --title "My API" --doc-version 1.0.0
46
48
  apiskill check
@@ -59,6 +61,36 @@ CLI config format:
59
61
  `,
60
62
  );
61
63
 
64
+ const cache = program.command('cache').description('View or change the shared default document cache directory.');
65
+
66
+ cache
67
+ .command('show')
68
+ .description('Show the active and saved default cache directories.')
69
+ .option('-j, --json', 'Print JSON')
70
+ .action((options) => {
71
+ const settings = getCacheSettings();
72
+ print(options.json ? settings : formatCacheSettingsText(settings));
73
+ });
74
+
75
+ cache
76
+ .command('set')
77
+ .description('Save the default cache directory used by future CLI, MCP, and Web processes. Existing files are not moved.')
78
+ .argument('<directory>', 'Absolute path or a path beginning with ~/')
79
+ .option('-j, --json', 'Print JSON')
80
+ .action(async (directory, options) => {
81
+ const settings = await setDefaultCacheDir(directory);
82
+ print(options.json ? settings : `${formatCacheSettingsText(settings)}\nExisting cached files were not moved.`);
83
+ });
84
+
85
+ cache
86
+ .command('reset')
87
+ .description('Restore the built-in ~/.apiskill/cache default. Existing files are not moved.')
88
+ .option('-j, --json', 'Print JSON')
89
+ .action(async (options) => {
90
+ const settings = await resetDefaultCacheDir();
91
+ print(options.json ? settings : `${formatCacheSettingsText(settings)}\nExisting cached files were not moved.`);
92
+ });
93
+
62
94
  const run = program.command('run').description('Run API Skill local services.');
63
95
 
64
96
  run
@@ -301,7 +333,6 @@ async function runWeb(options) {
301
333
  const host = options.host || '127.0.0.1';
302
334
 
303
335
  process.env.APISKILL_ROOT = projectRoot;
304
- process.env.APISKILL_CACHE_DIR = cacheDir;
305
336
 
306
337
  const { createServer } = await import('vite');
307
338
  const server = await createServer({
@@ -374,3 +405,16 @@ function print(value) {
374
405
  if (typeof value === 'string') console.log(value);
375
406
  else console.log(JSON.stringify(value, null, 2));
376
407
  }
408
+
409
+ function formatCacheSettingsText(settings) {
410
+ return [
411
+ `Active cache: ${settings.cacheDir}`,
412
+ `Saved default: ${settings.defaultCacheDir}`,
413
+ `Built-in default: ${settings.builtInCacheDir}`,
414
+ `Source: ${settings.source}`,
415
+ settings.environmentOverride ? `Environment override: ${settings.environmentCacheDir}` : '',
416
+ `Config: ${settings.configPath}`,
417
+ ]
418
+ .filter(Boolean)
419
+ .join('\n');
420
+ }
@@ -1,7 +1,98 @@
1
1
  import { homedir } from 'node:os';
2
- import { resolve } from 'node:path';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { mkdir, rename, rm, writeFile } from 'node:fs/promises';
4
+ import { dirname, resolve } from 'node:path';
5
+
6
+ const CONFIG_FILE_NAME = 'config.json';
7
+
8
+ export function getCacheSettings(env = process.env) {
9
+ const appDataDir = getAppDataDir(env);
10
+ const configPath = resolve(appDataDir, CONFIG_FILE_NAME);
11
+ const builtInCacheDir = resolve(homedir(), '.apiskill', 'cache');
12
+ const configuredCacheDir = readConfiguredCacheDir(configPath);
13
+ const defaultCacheDir = configuredCacheDir || builtInCacheDir;
14
+ const environmentCacheDir = env.APISKILL_CACHE_DIR ? normalizeCacheDir(env.APISKILL_CACHE_DIR) : '';
15
+
16
+ return {
17
+ cacheDir: environmentCacheDir || defaultCacheDir,
18
+ defaultCacheDir,
19
+ configuredCacheDir,
20
+ builtInCacheDir,
21
+ environmentCacheDir,
22
+ environmentOverride: Boolean(environmentCacheDir),
23
+ source: environmentCacheDir ? 'environment' : configuredCacheDir ? 'config' : 'default',
24
+ configPath,
25
+ };
26
+ }
3
27
 
4
28
  export function getDefaultCacheDir(env = process.env) {
5
- if (env.APISKILL_CACHE_DIR) return resolve(env.APISKILL_CACHE_DIR);
6
- return resolve(homedir(), '.apiskill', 'cache');
29
+ return getCacheSettings(env).cacheDir;
30
+ }
31
+
32
+ export async function setDefaultCacheDir(value, env = process.env) {
33
+ const cacheDir = normalizeCacheDir(value);
34
+ const settings = getCacheSettings(env);
35
+ await mkdir(cacheDir, { recursive: true });
36
+ const writeProbePath = resolve(cacheDir, `.apiskill-write-test-${process.pid}-${Date.now()}`);
37
+ try {
38
+ await writeFile(writeProbePath, 'ok');
39
+ } finally {
40
+ await rm(writeProbePath, { force: true }).catch(() => {});
41
+ }
42
+ await mkdir(dirname(settings.configPath), { recursive: true });
43
+
44
+ const currentConfig = readConfig(settings.configPath);
45
+ const nextConfig = {
46
+ ...currentConfig,
47
+ cacheDir,
48
+ cacheDirUpdatedAt: new Date().toISOString(),
49
+ };
50
+ const temporaryPath = `${settings.configPath}.${process.pid}.${Date.now()}.tmp`;
51
+ await writeFile(temporaryPath, JSON.stringify(nextConfig, null, 2));
52
+ await rename(temporaryPath, settings.configPath);
53
+ return getCacheSettings(env);
54
+ }
55
+
56
+ export async function resetDefaultCacheDir(env = process.env) {
57
+ const settings = getCacheSettings(env);
58
+ const currentConfig = readConfig(settings.configPath);
59
+ delete currentConfig.cacheDir;
60
+ currentConfig.cacheDirUpdatedAt = new Date().toISOString();
61
+ await mkdir(dirname(settings.configPath), { recursive: true });
62
+ const temporaryPath = `${settings.configPath}.${process.pid}.${Date.now()}.tmp`;
63
+ await writeFile(temporaryPath, JSON.stringify(currentConfig, null, 2));
64
+ await rename(temporaryPath, settings.configPath);
65
+ return getCacheSettings(env);
66
+ }
67
+
68
+ export function normalizeCacheDir(value) {
69
+ const input = String(value || '').trim();
70
+ if (!input) throw new Error('缓存目录不能为空');
71
+ if (input === '~') return homedir();
72
+ if (input.startsWith('~/')) return resolve(homedir(), input.slice(2));
73
+ return resolve(input);
74
+ }
75
+
76
+ function getAppDataDir(env) {
77
+ return env.APISKILL_CONFIG_DIR ? normalizeCacheDir(env.APISKILL_CONFIG_DIR) : resolve(homedir(), '.apiskill');
78
+ }
79
+
80
+ function readConfiguredCacheDir(configPath) {
81
+ const value = readConfig(configPath).cacheDir;
82
+ if (typeof value !== 'string' || !value.trim()) return '';
83
+ try {
84
+ return normalizeCacheDir(value);
85
+ } catch {
86
+ return '';
87
+ }
88
+ }
89
+
90
+ function readConfig(configPath) {
91
+ if (!existsSync(configPath)) return {};
92
+ try {
93
+ const parsed = JSON.parse(readFileSync(configPath, 'utf8'));
94
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
95
+ } catch {
96
+ return {};
97
+ }
7
98
  }
@@ -1,6 +1,7 @@
1
1
  import assert from 'node:assert/strict';
2
2
  import { createServer } from 'node:net';
3
- import { mkdtemp, rm } from 'node:fs/promises';
3
+ import { existsSync } from 'node:fs';
4
+ import { mkdir, mkdtemp, rm } from 'node:fs/promises';
4
5
  import { homedir, tmpdir } from 'node:os';
5
6
  import { dirname, join, resolve } from 'node:path';
6
7
  import { fileURLToPath } from 'node:url';
@@ -9,11 +10,15 @@ import { getDefaultCacheDir } from './lib/cache-paths.mjs';
9
10
 
10
11
  const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
11
12
  const cliPath = resolve(projectRoot, 'scripts/apiskill-cli.mjs');
12
- const cacheDir = await mkdtemp(join(tmpdir(), 'apiskill-shared-cache-'));
13
+ const testRoot = await mkdtemp(join(tmpdir(), 'apiskill-shared-cache-'));
14
+ const cacheDir = resolve(testRoot, 'active-cache');
15
+ const defaultCacheDir = resolve(testRoot, 'default-cache');
16
+ const configDir = resolve(testRoot, 'config');
13
17
  const port = await findFreePort();
14
18
  let webProcess;
15
19
 
16
20
  try {
21
+ await mkdir(cacheDir, { recursive: true });
17
22
  assert.equal(getDefaultCacheDir({ APISKILL_ROOT: '/tmp/another-project' }), resolve(homedir(), '.apiskill', 'cache'));
18
23
 
19
24
  const created = JSON.parse(
@@ -42,7 +47,7 @@ try {
42
47
  [cliPath, 'run', 'web', '--host', '127.0.0.1', '--port', String(port), '--strict-port', '--cwd', projectRoot],
43
48
  {
44
49
  cwd: projectRoot,
45
- env: { ...process.env, APISKILL_CACHE_DIR: cacheDir },
50
+ env: { ...process.env, APISKILL_CACHE_DIR: cacheDir, APISKILL_CONFIG_DIR: configDir },
46
51
  stdio: ['ignore', 'pipe', 'pipe'],
47
52
  },
48
53
  );
@@ -76,20 +81,50 @@ try {
76
81
 
77
82
  const check = JSON.parse(await runCli(['check', '--json']));
78
83
  assert.equal(check.cacheDir, cacheDir);
79
- console.log(`Shared cache test passed: CLI and Web both read and wrote ${cacheDir}.`);
84
+
85
+ const settingsResponse = await fetch(`http://127.0.0.1:${port}/api/cache-settings`, {
86
+ method: 'POST',
87
+ headers: { 'content-type': 'application/json' },
88
+ body: JSON.stringify({ cacheDir: defaultCacheDir }),
89
+ });
90
+ const settings = await settingsResponse.json();
91
+ assert.equal(settingsResponse.status, 200, JSON.stringify(settings));
92
+ assert.equal(settings.activeCacheDir, cacheDir);
93
+ assert.equal(settings.defaultCacheDir, defaultCacheDir);
94
+ assert.equal(settings.environmentOverride, true);
95
+
96
+ const syncResponse = await fetch(`http://127.0.0.1:${port}/api/openapi/version-sync-cache`, {
97
+ method: 'POST',
98
+ headers: { 'content-type': 'application/json' },
99
+ body: JSON.stringify({ versionId }),
100
+ });
101
+ const synced = await syncResponse.json();
102
+ assert.equal(syncResponse.status, 200, JSON.stringify(synced));
103
+ assert.equal(synced.targetCacheDir, defaultCacheDir);
104
+ assert.ok(existsSync(resolve(cacheDir, 'versions', `${versionId}.json`)), 'Sync removed the source document');
105
+ assert.ok(existsSync(resolve(defaultCacheDir, 'versions', `${versionId}.json`)), 'Sync did not create the target document');
106
+
107
+ const cliFromNewDefault = JSON.parse(
108
+ await runCli(['api', 'query', 'POST', '/api/v1/from-web'], { cacheOverride: false }),
109
+ );
110
+ assert.equal(cliFromNewDefault.path, '/api/v1/from-web');
111
+ console.log(`Shared cache test passed: Web and CLI synchronized ${versionId} from ${cacheDir} to ${defaultCacheDir}.`);
80
112
  } finally {
81
113
  if (webProcess && webProcess.exitCode === null) {
82
114
  webProcess.kill('SIGTERM');
83
115
  await new Promise((resolvePromise) => webProcess.once('close', resolvePromise));
84
116
  }
85
- await rm(cacheDir, { recursive: true, force: true });
117
+ await rm(testRoot, { recursive: true, force: true });
86
118
  }
87
119
 
88
- function runCli(args) {
120
+ function runCli(args, { cacheOverride = true } = {}) {
89
121
  return new Promise((resolvePromise, rejectPromise) => {
122
+ const env = { ...process.env, APISKILL_CONFIG_DIR: configDir };
123
+ if (cacheOverride) env.APISKILL_CACHE_DIR = cacheDir;
124
+ else delete env.APISKILL_CACHE_DIR;
90
125
  const child = spawn(process.execPath, [cliPath, ...args], {
91
126
  cwd: projectRoot,
92
- env: { ...process.env, APISKILL_CACHE_DIR: cacheDir },
127
+ env,
93
128
  stdio: ['ignore', 'pipe', 'pipe'],
94
129
  });
95
130
  const output = collectProcessOutput(child);
package/src/App.tsx CHANGED
@@ -44,9 +44,12 @@ import {
44
44
  VERSION_DELETE_API_URL,
45
45
  VERSION_META_API_URL,
46
46
  VERSION_SELECT_API_URL,
47
+ VERSION_SYNC_CACHE_API_URL,
47
48
  VERSIONS_API_URL,
48
49
  } from './swagger';
49
50
  import { AddApiDialog } from './AddApiDialog';
51
+ import { CacheSettingsDialog, loadCacheSettings } from './CacheSettingsDialog';
52
+ import type { CacheSettings } from './CacheSettingsDialog';
50
53
  import { DocumentVersionManager } from './DocumentVersionManager';
51
54
  import type { DocumentVersionMeta } from './DocumentVersionManager';
52
55
  import type { ManualApiOperationConfig } from './manualApiConfig';
@@ -125,6 +128,16 @@ type VersionDeleteResponse = {
125
128
  nextVersionId?: string;
126
129
  };
127
130
 
131
+ type VersionCacheSyncResponse = {
132
+ status: 'synced' | 'already-current';
133
+ versionId: string;
134
+ sourceCacheDir: string;
135
+ sourceDocumentPath: string;
136
+ targetCacheDir: string;
137
+ targetDocumentPath: string;
138
+ syncedAt?: string;
139
+ };
140
+
128
141
  type AuthCheckResponse = {
129
142
  requiresAuth: boolean;
130
143
  status?: number;
@@ -220,6 +233,7 @@ export function App() {
220
233
  const [editingApiTarget, setEditingApiTarget] = useState<{ method: HttpMethod; path: string } | undefined>(undefined);
221
234
  const [operationLinkDrafts, setOperationLinkDrafts] = useState<Record<string, EndpointAssociationLinks>>({});
222
235
  const [savingApi, setSavingApi] = useState(false);
236
+ const [cacheSettings, setCacheSettings] = useState<CacheSettings | undefined>(undefined);
223
237
  const uploadInputRef = useRef<HTMLInputElement | null>(null);
224
238
 
225
239
  useEffect(() => {
@@ -236,6 +250,7 @@ export function App() {
236
250
 
237
251
  loadInitialCache();
238
252
  void loadMockStatus();
253
+ void loadCacheSettings().then(setCacheSettings).catch(() => {});
239
254
  }, []);
240
255
 
241
256
  const endpoints = useMemo(() => (doc ? listEndpoints(doc) : []), [doc]);
@@ -756,6 +771,17 @@ export function App() {
756
771
  setLoadState('ready');
757
772
  }
758
773
 
774
+ async function syncVersionToDefaultCache(versionId: string) {
775
+ const response = await fetch(VERSION_SYNC_CACHE_API_URL, {
776
+ method: 'POST',
777
+ headers: { 'content-type': 'application/json' },
778
+ body: JSON.stringify({ versionId }),
779
+ });
780
+ const payload = await response.json();
781
+ if (!response.ok) throw new Error(payload.error || '文档同步到默认缓存地址失败');
782
+ return payload as VersionCacheSyncResponse;
783
+ }
784
+
759
785
  async function loadMockStatus() {
760
786
  try {
761
787
  const response = await fetch(MOCK_STATUS_API_URL, { cache: 'no-store' });
@@ -940,6 +966,8 @@ export function App() {
940
966
  onPrepareUpdate={prepareVersionUpdate}
941
967
  onUpdateEnvironment={updateVersionEnvironment}
942
968
  onDelete={deleteVersion}
969
+ cacheSettings={cacheSettings}
970
+ onSyncCache={syncVersionToDefaultCache}
943
971
  />
944
972
  <button
945
973
  className="primary-button"
@@ -957,6 +985,7 @@ export function App() {
957
985
  <span>Schema {stats.schemas}</span>
958
986
  <span>{lastSync ? `同步 ${formatDate(lastSync)}` : '未加载文档'}</span>
959
987
  </div>
988
+ <CacheSettingsDialog settings={cacheSettings} onSettingsChange={setCacheSettings} />
960
989
  </div>
961
990
  </header>
962
991