@fonlan/dsh-quick-commands 0.1.3 → 0.1.4
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.
- package/lib/client.js +3 -3
- package/lib/client.js.map +1 -1
- package/package.json +1 -1
- package/src/client/header.tsx +1 -1
- package/src/client/quick-commands.css +8 -5
package/lib/client.js
CHANGED
|
@@ -267,7 +267,7 @@ window.__ModuleLoader__.load({
|
|
|
267
267
|
onClick: () => {
|
|
268
268
|
popover.menuOpen ? closeMenu() : openMenu();
|
|
269
269
|
},
|
|
270
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlayOutline16, { size:
|
|
270
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlayOutline16, { size: 15 })
|
|
271
271
|
}),
|
|
272
272
|
popover.menuOpen && workspace !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
273
273
|
className: "qc-menu",
|
|
@@ -994,8 +994,8 @@ window.__ModuleLoader__.load({
|
|
|
994
994
|
};
|
|
995
995
|
//#endregion
|
|
996
996
|
//#region \0dsh-css:/home/runner/work/dsh-quick-commands/dsh-quick-commands/src/client/quick-commands.css.mjs
|
|
997
|
-
const css = "/* @fonlan/dsh-quick-commands client styles.\n Uses theme CSS variables (--dsw-alias-*); falls back to neutral literals.\n Constraint: this deployment's theme defines only these text tokens —\n label-primary, label-secondary, label-tertiary, label-dimmed. READABLE\n description copy uses label-secondary / label-tertiary (never label-dimmed,\n which is intentionally near-invisible and reserved for disabled states). */\n\n/* ── header button (icon-only, left of Session log pill) ─────────────────── */\n.qc-head-wrap {\n position: relative;\n display: inline-flex;\n align-items: center;\n}\n.qc-head-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));\n border-radius: 18px;\n background: transparent;\n color: var(--dsw-alias-label-primary, #f2f2f2);\n cursor: pointer;\n padding: 0;\n}\n.qc-head-btn:hover:not(:disabled) {\n background: color-mix(in srgb, var(--dsw-alias-label-primary, #f2f2f2) 10%, transparent);\n}\n.qc-head-btn:disabled {\n color: var(--dsw-alias-label-dimmed, #666);\n cursor: not-allowed;\n}\n.qc-head-btn svg {\n flex: none;\n}\n\n/* ── command menu popover (anchored under the button) ────────────────────── */\n.qc-menu {\n position: absolute;\n top: calc(100% + 6px);\n right: 12px;\n min-width: 280px;\n max-height: 420px;\n overflow: auto;\n background: var(--dsw-alias-bg-overlay, #1b1b1c);\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));\n border-radius: 10px;\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);\n z-index: 50;\n padding: 6px;\n display: flex;\n flex-direction: column;\n}\n.qc-menu-head {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 4px 8px 6px;\n border-bottom: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.25));\n}\n.qc-menu-title {\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary, #f2f2f2);\n}\n.qc-menu-ws {\n flex: 1;\n font-size: 11px;\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n text-align: right;\n margin-right: 2px;\n}\n.qc-menu-remote {\n flex: 0 0 auto;\n font-size: 10px;\n font-weight: 600;\n line-height: 1;\n padding: 2px 5px;\n border-radius: 5px;\n color: var(--dsw-alias-fg-on-accent, #fff);\n background: var(--dsw-alias-accent, #4a8dff);\n letter-spacing: 0.04em;\n}\n.qc-menu-close {\n border: 0;\n background: transparent;\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n cursor: pointer;\n padding: 2px;\n display: inline-flex;\n align-items: center;\n}\n.qc-menu-empty {\n font-size: 12px;\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n padding: 10px 8px;\n margin: 0;\n}\n.qc-menu-item {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 2px;\n text-align: left;\n border: 0;\n background: transparent;\n border-radius: 8px;\n padding: 6px 8px;\n cursor: pointer;\n width: 100%;\n}\n.qc-menu-item:hover:not(:disabled) {\n background: color-mix(in srgb, var(--dsw-alias-label-primary, #f2f2f2) 10%, transparent);\n}\n.qc-menu-item:disabled {\n opacity: 0.6;\n cursor: wait;\n}\n.qc-menu-item-name {\n font-size: 13px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary, #f2f2f2);\n}\n.qc-menu-item-cmd {\n font-family: var(--dsw-font-family-mono, monospace);\n font-size: 11px;\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n max-width: 100%;\n}\n\n/* ── live output popup ───────────────────────────────────────────────────── */\n.qc-popup {\n position: relative; /* anchors the absolutely-positioned resize grip */\n display: flex;\n flex-direction: column;\n background: var(--dsw-alias-bg-overlay, #1b1b1c);\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));\n border-radius: 12px;\n box-shadow: 0 12px 36px rgba(0, 0, 0, 0.45);\n z-index: 60;\n overflow: hidden;\n}\n.qc-popup-corner {\n position: fixed;\n right: 20px;\n bottom: 20px;\n width: 520px;\n max-width: calc(100vw - 40px);\n height: 340px;\n max-height: calc(100vh - 120px);\n}\n.qc-popup-button {\n position: absolute;\n top: calc(100% + 6px);\n right: 12px;\n width: 440px;\n max-width: calc(100vw - 40px);\n height: 300px;\n max-height: calc(100vh - 120px);\n}\n.qc-popup-head {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 10px;\n border-bottom: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.25));\n}\n.qc-popup-title {\n flex: 1;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary, #f2f2f2);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.qc-popup-cmd {\n font-family: var(--dsw-font-family-mono, monospace);\n font-size: 11px;\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n max-width: 40%;\n}\n.qc-popup-remote {\n flex: 0 0 auto;\n font-size: 10px;\n font-weight: 600;\n line-height: 1;\n padding: 3px 6px;\n border-radius: 6px;\n color: var(--dsw-alias-fg-on-accent, #fff);\n background: var(--dsw-alias-accent, #4a8dff);\n letter-spacing: 0.04em;\n}\n.qc-popup-icon {\n border: 0;\n background: transparent;\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n cursor: pointer;\n padding: 4px;\n display: inline-flex;\n align-items: center;\n border-radius: 6px;\n}\n.qc-popup-icon:hover:not(:disabled) {\n background: color-mix(in srgb, var(--dsw-alias-label-primary, #f2f2f2) 10%, transparent);\n}\n.qc-popup-icon:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n.qc-popup-kill {\n color: var(--dsw-alias-state-error-primary, #e5484d);\n}\n.qc-popup-confirm {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n padding: 8px 10px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary, #f5a623) 12%, transparent);\n color: var(--dsw-alias-label-primary, #f2f2f2);\n border-bottom: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.25));\n}\n.qc-popup-btn {\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));\n border-radius: 8px;\n background: transparent;\n color: var(--dsw-alias-label-primary, #f2f2f2);\n font-size: 12px;\n padding: 4px 10px;\n cursor: pointer;\n}\n.qc-popup-btn:hover {\n background: color-mix(in srgb, var(--dsw-alias-label-primary, #f2f2f2) 10%, transparent);\n}\n.qc-popup-btn-plain {\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n}\n.qc-popup-tabs {\n display: flex;\n gap: 2px;\n padding: 6px 10px 0;\n border-bottom: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.25));\n}\n.qc-popup-tab {\n border: 0;\n background: transparent;\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n font-size: 12px;\n padding: 5px 10px;\n cursor: pointer;\n border-bottom: 2px solid transparent;\n}\n.qc-popup-tab-active {\n color: var(--dsw-alias-label-primary, #f2f2f2);\n border-bottom-color: var(--dsw-alias-brand-primary, #4a9eff);\n}\n.qc-popup-output {\n flex: 1;\n overflow: auto;\n padding: 8px 10px;\n font-family: var(--dsw-font-family-mono, monospace);\n font-size: 12px;\n line-height: 1.5;\n}\n.qc-popup-pre {\n margin: 0;\n white-space: pre-wrap;\n word-break: break-word;\n color: var(--dsw-alias-label-primary, #f2f2f2);\n}\n.qc-popup-empty {\n margin: 0;\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n font-size: 12px;\n}\n.qc-popup-error {\n margin: 0 0 6px;\n color: var(--dsw-alias-state-error-primary, #e5484d);\n font-size: 12px;\n}\n.qc-popup-foot {\n display: flex;\n align-items: center;\n padding: 6px 10px;\n border-top: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.25));\n font-size: 11px;\n}\n.qc-popup-state {\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n}\n\n/* Resize handles. The popup pins its RIGHT edge (both anchors); width is\n always dragged from the LEFT border. Height is dragged from the TOP border\n (corner anchor, bottom pinned) or the BOTTOM border (button anchor, top\n pinned below the button). The free corner (top-left / bottom-left) carries\n the diagonal grip. Pointer capture lives in header.tsx; touch-action:none\n lets touch drags resize instead of scrolling. */\n.qc-popup-resize {\n position: absolute;\n z-index: 3;\n touch-action: none;\n user-select: none;\n}\n.qc-popup-resize-left {\n left: 0;\n top: 10px;\n bottom: 10px;\n width: 7px;\n cursor: ew-resize;\n}\n.qc-popup-resize-top {\n top: 0;\n left: 10px;\n right: 10px;\n height: 7px;\n cursor: ns-resize;\n}\n.qc-popup-resize-bottom {\n bottom: 0;\n left: 10px;\n right: 10px;\n height: 7px;\n cursor: ns-resize;\n}\n.qc-popup-resize-left:hover,\n.qc-popup-resize-top:hover,\n.qc-popup-resize-bottom:hover {\n background: color-mix(in srgb, var(--dsw-alias-label-primary, #f2f2f2) 14%, transparent);\n}\n.qc-popup-resize-corner {\n width: 22px;\n height: 22px;\n z-index: 4;\n display: flex;\n padding: 3px;\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n opacity: 0.6;\n transition: opacity .15s ease;\n}\n.qc-popup-resize-corner-tl {\n top: 0;\n left: 0;\n cursor: nwse-resize;\n align-items: flex-start;\n justify-content: flex-start;\n}\n.qc-popup-resize-corner-bl {\n bottom: 0;\n left: 0;\n cursor: nesw-resize;\n align-items: flex-end;\n justify-content: flex-start;\n}\n.qc-popup-resize-corner:hover,\n.qc-popup-resize-corner:active {\n opacity: 1;\n}\n\n/* ── settings card (设置 → 插件配置) — mirrors official PluginCard disclosure ── */\n.qc-settings-card {\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));\n background: var(--dsw-alias-bg-layer-2, #1f1f21);\n border-radius: 12px;\n list-style: none;\n transition: border-color .16s, background .16s;\n}\n.qc-settings-card:hover {\n border-color: var(--dsw-alias-label-secondary, #cfd3d6);\n}\n.qc-settings-card-open {\n background: var(--dsw-alias-bg-layer-1, #27272a);\n border-color: var(--dsw-alias-label-secondary, #cfd3d6);\n}\n.qc-settings-header {\n appearance: none;\n width: 100%;\n font: inherit;\n color: inherit;\n text-align: left;\n cursor: pointer;\n background: transparent;\n border: 0;\n border-radius: 12px;\n align-items: center;\n gap: 12px;\n padding: 14px 16px;\n display: flex;\n}\n.qc-settings-header:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary, #4a9eff);\n outline-offset: -2px;\n}\n.qc-settings-head-text {\n flex-direction: column;\n flex: 1;\n gap: 4px;\n min-width: 0;\n display: flex;\n}\n.qc-settings-name {\n color: var(--dsw-alias-label-primary, #f2f2f2);\n font-size: 15px;\n font-weight: 600;\n line-height: 1.4;\n}\n.qc-settings-description {\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n font-size: 13px;\n line-height: 1.5;\n}\n.qc-settings-chevron {\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n flex: none;\n transition: transform .16s;\n}\n.qc-settings-chevron-open {\n transform: rotate(180deg);\n}\n.qc-settings-body {\n border-top: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));\n margin: 0 16px;\n padding-bottom: 12px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n.qc-settings-intro {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n margin: 8px 0 0;\n line-height: 1.5;\n}\n.qc-settings-anchor {\n display: flex;\n align-items: center;\n gap: 12px;\n font-size: 12px;\n}\n.qc-settings-anchor-label {\n color: var(--dsw-alias-label-primary, #f2f2f2);\n font-weight: 500;\n}\n.qc-settings-radio {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n cursor: pointer;\n}\n.qc-settings-error {\n font-size: 12px;\n color: var(--dsw-alias-state-error-primary, #e5484d);\n}\n.qc-settings-empty {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n margin: 0;\n}\n.qc-settings-workspace {\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));\n border-radius: 10px;\n padding: 10px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.qc-settings-workspace-head {\n display: flex;\n align-items: baseline;\n gap: 8px;\n}\n.qc-settings-workspace-title {\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary, #f2f2f2);\n}\n.qc-settings-workspace-path {\n font-size: 11px;\n font-family: var(--dsw-font-family-mono, monospace);\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.qc-settings-no-commands {\n font-size: 12px;\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n margin: 0;\n}\n.qc-settings-command {\n display: flex;\n gap: 6px;\n align-items: center;\n}\n.qc-settings-field {\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1, #2a2a2d);\n color: var(--dsw-alias-label-primary, #f2f2f2);\n font-size: 12px;\n padding: 6px 8px;\n outline: none;\n}\n.qc-settings-field::placeholder {\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n}\n.qc-settings-field:focus {\n border-color: var(--dsw-alias-brand-primary, #4a9eff);\n}\n.qc-settings-field-name {\n width: 160px;\n flex: none;\n}\n.qc-settings-field-command {\n flex: 1;\n font-family: var(--dsw-font-family-mono, monospace);\n}\n.qc-settings-icon-btn {\n border: 0;\n background: transparent;\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n cursor: pointer;\n padding: 4px;\n display: inline-flex;\n align-items: center;\n border-radius: 6px;\n}\n.qc-settings-icon-btn:hover {\n background: color-mix(in srgb, var(--dsw-alias-label-primary, #f2f2f2) 10%, transparent);\n color: var(--dsw-alias-state-error-primary, #e5484d);\n}\n.qc-settings-add-btn {\n align-self: flex-start;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));\n border-radius: 8px;\n background: transparent;\n color: var(--dsw-alias-label-primary, #f2f2f2);\n font-size: 12px;\n padding: 4px 10px;\n cursor: pointer;\n}\n.qc-settings-add-btn:hover {\n background: color-mix(in srgb, var(--dsw-alias-label-primary, #f2f2f2) 10%, transparent);\n}\n\n/* ── mobile fallback (dsh-mobile-nav) ──────────────────────────────────────\n The dsh-mobile-nav plugin (≤1023px) hides the ENTIRE header utilities seat\n with `[data-mobile-nav=\"frame\"] [data-phase] header > :first-child >\n :last-child { display: none !important; }`, because the seat used to hold\n only the session-log-export capsule (which it relocates to the drawer\n footer). Our quick-run ▶ button rides that same seat, so it vanished on\n phones.\n\n Re-show ONLY our button: restore the seat's flex layout (out-ranking the\n host rule via `:has(.qc-head-wrap)` specificity (0,5,1) > (0,4,1)) and keep\n every other seat occupant hidden (the session-log capsule stays relocated\n to the drawer footer). Desktop (no [data-mobile-nav]) is untouched. */\n[data-mobile-nav=\"frame\"] [data-phase] header > :first-child > :last-child:has(.qc-head-wrap) {\n display: flex !important;\n margin-left: 4px !important;\n gap: 0 !important;\n}\n[data-mobile-nav=\"frame\"] [data-phase] header > :first-child > :last-child:has(.qc-head-wrap)\n [data-slot=\"conversation.session.header.utilities\"] > :not(.qc-head-wrap) {\n display: none !important;\n}\n";
|
|
998
|
-
const tagId = "@fonlan/dsh-quick-commands/quick-commands.css#
|
|
997
|
+
const css = "/* @fonlan/dsh-quick-commands client styles.\n Uses theme CSS variables (--dsw-alias-*); falls back to neutral literals.\n Constraint: this deployment's theme defines only these text tokens —\n label-primary, label-secondary, label-tertiary, label-dimmed. READABLE\n description copy uses label-secondary / label-tertiary (never label-dimmed,\n which is intentionally near-invisible and reserved for disabled states). */\n\n/* ── header button (icon-only, left of Session log pill) ─────────────────── */\n.qc-head-wrap {\n position: relative;\n display: inline-flex;\n align-items: center;\n}\n/* Sized to DSH built-in header buttons (e.g. the open-in-app split button on\n the left): 26px tall pill, 13px radius, hairline border-l4, hover uses the\n shared --dsw-alias-interactive-bg-hover token. */\n.qc-head-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n border: 0.5px solid var(--dsw-alias-border-l4, rgba(128, 128, 128, 0.35));\n border-radius: 13px;\n background: transparent;\n color: var(--dsw-alias-label-primary, #f2f2f2);\n cursor: pointer;\n padding: 0;\n}\n.qc-head-btn:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover, color-mix(in srgb, var(--dsw-alias-label-primary, #f2f2f2) 10%, transparent));\n}\n.qc-head-btn:disabled {\n color: var(--dsw-alias-label-dimmed, #666);\n cursor: not-allowed;\n}\n.qc-head-btn svg {\n flex: none;\n}\n\n/* ── command menu popover (anchored under the button) ────────────────────── */\n.qc-menu {\n position: absolute;\n top: calc(100% + 6px);\n right: 12px;\n min-width: 280px;\n max-height: 420px;\n overflow: auto;\n background: var(--dsw-alias-bg-overlay, #1b1b1c);\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));\n border-radius: 10px;\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);\n z-index: 50;\n padding: 6px;\n display: flex;\n flex-direction: column;\n}\n.qc-menu-head {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 4px 8px 6px;\n border-bottom: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.25));\n}\n.qc-menu-title {\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary, #f2f2f2);\n}\n.qc-menu-ws {\n flex: 1;\n font-size: 11px;\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n text-align: right;\n margin-right: 2px;\n}\n.qc-menu-remote {\n flex: 0 0 auto;\n font-size: 10px;\n font-weight: 600;\n line-height: 1;\n padding: 2px 5px;\n border-radius: 5px;\n color: var(--dsw-alias-fg-on-accent, #fff);\n background: var(--dsw-alias-accent, #4a8dff);\n letter-spacing: 0.04em;\n}\n.qc-menu-close {\n border: 0;\n background: transparent;\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n cursor: pointer;\n padding: 2px;\n display: inline-flex;\n align-items: center;\n}\n.qc-menu-empty {\n font-size: 12px;\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n padding: 10px 8px;\n margin: 0;\n}\n.qc-menu-item {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 2px;\n text-align: left;\n border: 0;\n background: transparent;\n border-radius: 8px;\n padding: 6px 8px;\n cursor: pointer;\n width: 100%;\n}\n.qc-menu-item:hover:not(:disabled) {\n background: color-mix(in srgb, var(--dsw-alias-label-primary, #f2f2f2) 10%, transparent);\n}\n.qc-menu-item:disabled {\n opacity: 0.6;\n cursor: wait;\n}\n.qc-menu-item-name {\n font-size: 13px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary, #f2f2f2);\n}\n.qc-menu-item-cmd {\n font-family: var(--dsw-font-family-mono, monospace);\n font-size: 11px;\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n max-width: 100%;\n}\n\n/* ── live output popup ───────────────────────────────────────────────────── */\n.qc-popup {\n position: relative; /* anchors the absolutely-positioned resize grip */\n display: flex;\n flex-direction: column;\n background: var(--dsw-alias-bg-overlay, #1b1b1c);\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));\n border-radius: 12px;\n box-shadow: 0 12px 36px rgba(0, 0, 0, 0.45);\n z-index: 60;\n overflow: hidden;\n}\n.qc-popup-corner {\n position: fixed;\n right: 20px;\n bottom: 20px;\n width: 520px;\n max-width: calc(100vw - 40px);\n height: 340px;\n max-height: calc(100vh - 120px);\n}\n.qc-popup-button {\n position: absolute;\n top: calc(100% + 6px);\n right: 12px;\n width: 440px;\n max-width: calc(100vw - 40px);\n height: 300px;\n max-height: calc(100vh - 120px);\n}\n.qc-popup-head {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 10px;\n border-bottom: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.25));\n}\n.qc-popup-title {\n flex: 1;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary, #f2f2f2);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.qc-popup-cmd {\n font-family: var(--dsw-font-family-mono, monospace);\n font-size: 11px;\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n max-width: 40%;\n}\n.qc-popup-remote {\n flex: 0 0 auto;\n font-size: 10px;\n font-weight: 600;\n line-height: 1;\n padding: 3px 6px;\n border-radius: 6px;\n color: var(--dsw-alias-fg-on-accent, #fff);\n background: var(--dsw-alias-accent, #4a8dff);\n letter-spacing: 0.04em;\n}\n.qc-popup-icon {\n border: 0;\n background: transparent;\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n cursor: pointer;\n padding: 4px;\n display: inline-flex;\n align-items: center;\n border-radius: 6px;\n}\n.qc-popup-icon:hover:not(:disabled) {\n background: color-mix(in srgb, var(--dsw-alias-label-primary, #f2f2f2) 10%, transparent);\n}\n.qc-popup-icon:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n.qc-popup-kill {\n color: var(--dsw-alias-state-error-primary, #e5484d);\n}\n.qc-popup-confirm {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n padding: 8px 10px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary, #f5a623) 12%, transparent);\n color: var(--dsw-alias-label-primary, #f2f2f2);\n border-bottom: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.25));\n}\n.qc-popup-btn {\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));\n border-radius: 8px;\n background: transparent;\n color: var(--dsw-alias-label-primary, #f2f2f2);\n font-size: 12px;\n padding: 4px 10px;\n cursor: pointer;\n}\n.qc-popup-btn:hover {\n background: color-mix(in srgb, var(--dsw-alias-label-primary, #f2f2f2) 10%, transparent);\n}\n.qc-popup-btn-plain {\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n}\n.qc-popup-tabs {\n display: flex;\n gap: 2px;\n padding: 6px 10px 0;\n border-bottom: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.25));\n}\n.qc-popup-tab {\n border: 0;\n background: transparent;\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n font-size: 12px;\n padding: 5px 10px;\n cursor: pointer;\n border-bottom: 2px solid transparent;\n}\n.qc-popup-tab-active {\n color: var(--dsw-alias-label-primary, #f2f2f2);\n border-bottom-color: var(--dsw-alias-brand-primary, #4a9eff);\n}\n.qc-popup-output {\n flex: 1;\n overflow: auto;\n padding: 8px 10px;\n font-family: var(--dsw-font-family-mono, monospace);\n font-size: 12px;\n line-height: 1.5;\n}\n.qc-popup-pre {\n margin: 0;\n white-space: pre-wrap;\n word-break: break-word;\n color: var(--dsw-alias-label-primary, #f2f2f2);\n}\n.qc-popup-empty {\n margin: 0;\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n font-size: 12px;\n}\n.qc-popup-error {\n margin: 0 0 6px;\n color: var(--dsw-alias-state-error-primary, #e5484d);\n font-size: 12px;\n}\n.qc-popup-foot {\n display: flex;\n align-items: center;\n padding: 6px 10px;\n border-top: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.25));\n font-size: 11px;\n}\n.qc-popup-state {\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n}\n\n/* Resize handles. The popup pins its RIGHT edge (both anchors); width is\n always dragged from the LEFT border. Height is dragged from the TOP border\n (corner anchor, bottom pinned) or the BOTTOM border (button anchor, top\n pinned below the button). The free corner (top-left / bottom-left) carries\n the diagonal grip. Pointer capture lives in header.tsx; touch-action:none\n lets touch drags resize instead of scrolling. */\n.qc-popup-resize {\n position: absolute;\n z-index: 3;\n touch-action: none;\n user-select: none;\n}\n.qc-popup-resize-left {\n left: 0;\n top: 10px;\n bottom: 10px;\n width: 7px;\n cursor: ew-resize;\n}\n.qc-popup-resize-top {\n top: 0;\n left: 10px;\n right: 10px;\n height: 7px;\n cursor: ns-resize;\n}\n.qc-popup-resize-bottom {\n bottom: 0;\n left: 10px;\n right: 10px;\n height: 7px;\n cursor: ns-resize;\n}\n.qc-popup-resize-left:hover,\n.qc-popup-resize-top:hover,\n.qc-popup-resize-bottom:hover {\n background: color-mix(in srgb, var(--dsw-alias-label-primary, #f2f2f2) 14%, transparent);\n}\n.qc-popup-resize-corner {\n width: 22px;\n height: 22px;\n z-index: 4;\n display: flex;\n padding: 3px;\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n opacity: 0.6;\n transition: opacity .15s ease;\n}\n.qc-popup-resize-corner-tl {\n top: 0;\n left: 0;\n cursor: nwse-resize;\n align-items: flex-start;\n justify-content: flex-start;\n}\n.qc-popup-resize-corner-bl {\n bottom: 0;\n left: 0;\n cursor: nesw-resize;\n align-items: flex-end;\n justify-content: flex-start;\n}\n.qc-popup-resize-corner:hover,\n.qc-popup-resize-corner:active {\n opacity: 1;\n}\n\n/* ── settings card (设置 → 插件配置) — mirrors official PluginCard disclosure ── */\n.qc-settings-card {\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));\n background: var(--dsw-alias-bg-layer-2, #1f1f21);\n border-radius: 12px;\n list-style: none;\n transition: border-color .16s, background .16s;\n}\n.qc-settings-card:hover {\n border-color: var(--dsw-alias-label-secondary, #cfd3d6);\n}\n.qc-settings-card-open {\n background: var(--dsw-alias-bg-layer-1, #27272a);\n border-color: var(--dsw-alias-label-secondary, #cfd3d6);\n}\n.qc-settings-header {\n appearance: none;\n width: 100%;\n font: inherit;\n color: inherit;\n text-align: left;\n cursor: pointer;\n background: transparent;\n border: 0;\n border-radius: 12px;\n align-items: center;\n gap: 12px;\n padding: 14px 16px;\n display: flex;\n}\n.qc-settings-header:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary, #4a9eff);\n outline-offset: -2px;\n}\n.qc-settings-head-text {\n flex-direction: column;\n flex: 1;\n gap: 4px;\n min-width: 0;\n display: flex;\n}\n.qc-settings-name {\n color: var(--dsw-alias-label-primary, #f2f2f2);\n font-size: 15px;\n font-weight: 600;\n line-height: 1.4;\n}\n.qc-settings-description {\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n font-size: 13px;\n line-height: 1.5;\n}\n.qc-settings-chevron {\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n flex: none;\n transition: transform .16s;\n}\n.qc-settings-chevron-open {\n transform: rotate(180deg);\n}\n.qc-settings-body {\n border-top: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));\n margin: 0 16px;\n padding-bottom: 12px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n.qc-settings-intro {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n margin: 8px 0 0;\n line-height: 1.5;\n}\n.qc-settings-anchor {\n display: flex;\n align-items: center;\n gap: 12px;\n font-size: 12px;\n}\n.qc-settings-anchor-label {\n color: var(--dsw-alias-label-primary, #f2f2f2);\n font-weight: 500;\n}\n.qc-settings-radio {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n cursor: pointer;\n}\n.qc-settings-error {\n font-size: 12px;\n color: var(--dsw-alias-state-error-primary, #e5484d);\n}\n.qc-settings-empty {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n margin: 0;\n}\n.qc-settings-workspace {\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));\n border-radius: 10px;\n padding: 10px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.qc-settings-workspace-head {\n display: flex;\n align-items: baseline;\n gap: 8px;\n}\n.qc-settings-workspace-title {\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary, #f2f2f2);\n}\n.qc-settings-workspace-path {\n font-size: 11px;\n font-family: var(--dsw-font-family-mono, monospace);\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.qc-settings-no-commands {\n font-size: 12px;\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n margin: 0;\n}\n.qc-settings-command {\n display: flex;\n gap: 6px;\n align-items: center;\n}\n.qc-settings-field {\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1, #2a2a2d);\n color: var(--dsw-alias-label-primary, #f2f2f2);\n font-size: 12px;\n padding: 6px 8px;\n outline: none;\n}\n.qc-settings-field::placeholder {\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n}\n.qc-settings-field:focus {\n border-color: var(--dsw-alias-brand-primary, #4a9eff);\n}\n.qc-settings-field-name {\n width: 160px;\n flex: none;\n}\n.qc-settings-field-command {\n flex: 1;\n font-family: var(--dsw-font-family-mono, monospace);\n}\n.qc-settings-icon-btn {\n border: 0;\n background: transparent;\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n cursor: pointer;\n padding: 4px;\n display: inline-flex;\n align-items: center;\n border-radius: 6px;\n}\n.qc-settings-icon-btn:hover {\n background: color-mix(in srgb, var(--dsw-alias-label-primary, #f2f2f2) 10%, transparent);\n color: var(--dsw-alias-state-error-primary, #e5484d);\n}\n.qc-settings-add-btn {\n align-self: flex-start;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));\n border-radius: 8px;\n background: transparent;\n color: var(--dsw-alias-label-primary, #f2f2f2);\n font-size: 12px;\n padding: 4px 10px;\n cursor: pointer;\n}\n.qc-settings-add-btn:hover {\n background: color-mix(in srgb, var(--dsw-alias-label-primary, #f2f2f2) 10%, transparent);\n}\n\n/* ── mobile fallback (dsh-mobile-nav) ──────────────────────────────────────\n The dsh-mobile-nav plugin (≤1023px) hides the ENTIRE header utilities seat\n with `[data-mobile-nav=\"frame\"] [data-phase] header > :first-child >\n :last-child { display: none !important; }`, because the seat used to hold\n only the session-log-export capsule (which it relocates to the drawer\n footer). Our quick-run ▶ button rides that same seat, so it vanished on\n phones.\n\n Re-show ONLY our button: restore the seat's flex layout (out-ranking the\n host rule via `:has(.qc-head-wrap)` specificity (0,5,1) > (0,4,1)) and keep\n every other seat occupant hidden (the session-log capsule stays relocated\n to the drawer footer). Desktop (no [data-mobile-nav]) is untouched. */\n[data-mobile-nav=\"frame\"] [data-phase] header > :first-child > :last-child:has(.qc-head-wrap) {\n display: flex !important;\n margin-left: 4px !important;\n gap: 0 !important;\n}\n[data-mobile-nav=\"frame\"] [data-phase] header > :first-child > :last-child:has(.qc-head-wrap)\n [data-slot=\"conversation.session.header.utilities\"] > :not(.qc-head-wrap) {\n display: none !important;\n}\n";
|
|
998
|
+
const tagId = "@fonlan/dsh-quick-commands/quick-commands.css#8fa6888e";
|
|
999
999
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
1000
1000
|
const tag = document.createElement("style");
|
|
1001
1001
|
tag.dataset.plugin = "@fonlan/dsh-quick-commands";
|
package/lib/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":["useSyncExternalStore","useState","useRef","IconPlayOutline16","IconCloseOutline16","IconStopFill16","useState","useRef","IconChevronDownOutline14","IconTrashOutline16","IconPlusOutline16"],"sources":["../../../src/client/api.ts","../../../src/client/run-state.ts","../../../src/client/header.tsx","../../../src/client/settings-card.tsx","../../../src/client/locales.ts","../../../src/client/index.tsx"],"sourcesContent":["/**\n * Browser client for the plugin's fenced JSON API\n * (/plugins/@fonlan/dsh-quick-commands/api/<method>). Same-origin fetch with\n * the task-kanban envelope shape: { ok, value } | { ok:false, error }.\n */\nimport type {\n ApiEnvelope,\n QuickCommandsSettings,\n QuickPopupSize,\n QuickRosterEntry,\n QuickRunView,\n} from '../shared/contract'\n\nexport class QuickCommandsApiError extends Error {\n constructor(readonly code: string, message: string) {\n super(message)\n }\n}\n\nasync function call<T>(method: string, payload: Record<string, unknown> = {}): Promise<T> {\n let response: Response\n try {\n response = await fetch(`/plugins/@fonlan/dsh-quick-commands/api/${method}`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n })\n } catch (error) {\n throw new QuickCommandsApiError('network', error instanceof Error ? error.message : String(error))\n }\n const parsed = (await response.json().catch(() => null)) as ApiEnvelope<T> | null\n if (!response.ok || parsed === null || parsed.ok !== true || parsed.value === undefined) {\n throw new QuickCommandsApiError(\n (parsed?.error?.code as string | undefined) ?? 'http',\n parsed?.error?.message ?? `HTTP ${response.status}`,\n )\n }\n return parsed.value as T\n}\n\n/** Current session cwd of a session row (SessionSummary carries cwd). */\nexport interface SessionRow {\n id: string\n cwd?: string\n}\n\n/** One delta read result (mirrors the host RunPollResult). */\nexport interface RunPoll {\n status: 'running' | 'exited'\n exitCode: number | null\n signal: string | null\n stdoutEnd: number\n stderrEnd: number\n stdoutDelta: string\n stderrDelta: string\n stdoutLossy: boolean\n stderrLossy: boolean\n}\n\nexport const quickApi = {\n settingsGet: () => call<QuickCommandsSettings>('settings.get'),\n settingsSetCommands: (workspaceId: string, commands: Array<{ name: string; command: string }>) =>\n call<QuickCommandsSettings>('settings.setCommands', { workspaceId, commands }),\n settingsSetAnchor: (anchor: QuickCommandsSettings['popupAnchor']) =>\n call<QuickCommandsSettings>('settings.setAnchor', { anchor }),\n settingsSetPopupSize: (size: QuickPopupSize) =>\n call<QuickCommandsSettings>('settings.setPopupSize', { width: size.width, height: size.height }),\n workspacesList: () => call<QuickRosterEntry[]>('workspaces.list'),\n runStart: (workspaceId: string, commandName: string, sessionCwd: string) =>\n call<QuickRunView>('run.start', { workspaceId, commandName, sessionCwd }),\n runPoll: (runId: string, stdoutFrom: number, stderrFrom: number) =>\n call<RunPoll>('run.poll', { runId, stdoutFrom, stderrFrom }),\n runKill: (runId: string) => call<{ killed: boolean }>('run.kill', { runId }),\n runList: (workspaceId: string) =>\n call<Array<{ runId: string; commandName: string; startedAt: number; status: 'running' | 'exited' }>>('run.list', { workspaceId }),\n}\n","/**\n * HMR-safe shared run state for the quick-commands header button and output\n * popup. Module-level fields live on one globalThis-backed record keyed with\n * Symbol.for so every client-bundle copy (client-hmr hot swap re-evaluates the\n * bundle without reloading the page) reads/writes the same state cell.\n *\n * The popup handles ONE run at a time (serial per workspace by design); a\n * second workspace can run in parallel with its own popup, so the store keys\n * popups by runId.\n */\nimport { useSyncExternalStore } from 'react'\n\nconst STATE_KEY = Symbol.for('@fonlan/dsh-quick-commands/run-state')\n\n/** One popup slot: menu open flag + the live run the popup streams. */\nexport interface RunPopoverState {\n /** Whether the header command menu is open. */\n menuOpen: boolean\n /** Active run id (undefined when no run is open). */\n runId: string | null\n /** Workspace id owning the active run (for kill/cwd display). */\n workspaceId: string | null\n /** Command display name. */\n commandName: string | null\n /** SSH host id when the run executes on a remote workspace (else null). */\n remoteHost: string | null\n /** Whether the run popup should stream (false while minimized). */\n streaming: boolean\n}\n\ninterface RunPopoverStateInternal extends RunPopoverState {\n listeners: Set<() => void>\n}\n\ninterface QuickCommandsState {\n popover: RunPopoverStateInternal\n /** Cached published snapshot — STABLE reference; rebuilt only after a patch. */\n snapshot: RunPopoverState | null\n}\n\nfunction getState(): QuickCommandsState {\n const g = globalThis as unknown as Record<symbol, QuickCommandsState | undefined>\n let state = g[STATE_KEY]\n if (state === undefined) {\n state = {\n popover: {\n menuOpen: false,\n runId: null,\n workspaceId: null,\n commandName: null,\n remoteHost: null,\n streaming: true,\n listeners: new Set(),\n },\n // Seed the cache with values our equality check will see as unchanged.\n snapshot: {\n menuOpen: false,\n runId: null,\n workspaceId: null,\n commandName: null,\n remoteHost: null,\n streaming: true,\n },\n }\n g[STATE_KEY] = state\n }\n return state\n}\n\nfunction notify(): void {\n // Invalidate the cached snapshot FIRST: useSyncExternalStore reads\n // getSnapshot after notification, so a new stable object must be ready.\n getState().snapshot = null\n for (const listener of getState().popover.listeners) listener()\n}\n\nfunction subscribe(cb: () => void): () => void {\n const listeners = getState().popover.listeners\n listeners.add(cb)\n return () => { listeners.delete(cb) }\n}\n\n/**\n * getSnapshot for useSyncExternalStore. MUST return a stable reference while\n * the underlying fields are unchanged (React compares with Object.is) — return\n * the CACHED object; only rebuild it when a patch invalidated it.\n */\nfunction snapshot(): RunPopoverState {\n const state = getState()\n if (state.snapshot !== null) return state.snapshot\n const p = state.popover\n state.snapshot = {\n menuOpen: p.menuOpen,\n runId: p.runId,\n workspaceId: p.workspaceId,\n commandName: p.commandName,\n remoteHost: p.remoteHost,\n streaming: p.streaming,\n }\n return state.snapshot\n}\n\nfunction patch(partial: Partial<RunPopoverState>): void {\n const state = getState()\n Object.assign(state.popover, partial)\n // Invalidate the cached snapshot so equality flips exactly on real changes.\n state.snapshot = null\n notify()\n}\n\nexport function useRunPopover(): RunPopoverState {\n return useSyncExternalStore(subscribe, snapshot, snapshot)\n}\n\nexport function openMenu(): void { patch({ menuOpen: true }) }\nexport function closeMenu(): void { patch({ menuOpen: false }) }\nexport function toggleMenu(open: boolean): void { patch({ menuOpen: open }) }\n\nexport function openRun(runId: string, workspaceId: string, commandName: string, remoteHost?: string): void {\n patch({ runId, workspaceId, commandName, remoteHost: remoteHost ?? null, menuOpen: false, streaming: true })\n}\n\nexport function closeRun(): void {\n patch({ runId: null, workspaceId: null, commandName: null, remoteHost: null, streaming: false })\n}\n","/**\n * Session-header quick-command surface: the ▶ play icon button (left of the\n * Session log pill), the command menu popover (current workspace's commands),\n * and the live output popup that streams stdout/stderr at ~200ms cadence.\n *\n * Props are the standard session/global kit of `conversation.session.header.utilities`\n * plus this plugin's injected hooks (anchor preference + resolver).\n */\nimport { useEffect, useRef, useState } from 'react'\nimport {\n IconPlayOutline16,\n IconCloseOutline16,\n IconStopFill16,\n} from '@deepseek-ai/dsh-client-ui-primitives'\nimport { quickApi } from './api'\nimport { useRunPopover, openMenu, closeMenu, openRun, closeRun } from './run-state'\nimport type { QuickPopupSize } from '../shared/contract'\n\n/** Workspace row shape the standard useWorkspaces hook exposes. */\ninterface WorkspaceRow {\n workspaceId: string\n path: string\n title: string\n}\n\n/** Session list state subset we read (cwd + id). */\ninterface SessionRow {\n id: string\n cwd?: string\n}\n\ninterface HeaderProps {\n sessionId: string\n t: (key: string) => string\n useSessions?: (selector: (s: { byId: Record<string, SessionRow>; current?: string }) => unknown) => unknown\n useWorkspaces?: (selector: (s: { items: WorkspaceRow[] }) => unknown) => unknown\n}\n\n/** Longest-path workspace match for a cwd (subdirectory allowed). */\nfunction matchWorkspace(cwd: string | undefined, workspaces: WorkspaceRow[]): WorkspaceRow | undefined {\n if (cwd === undefined) return undefined\n let best: WorkspaceRow | undefined\n for (const ws of workspaces) {\n const norm = ws.path.replace(/\\\\/g, '/').replace(/\\/+$/, '')\n const nc = cwd.replace(/\\\\/g, '/')\n if (nc === norm || nc.startsWith(norm + '/')) {\n if (best === undefined || norm.length > best.path.length) best = ws\n }\n }\n return best\n}\n\n/** Resolve the current session's workspace from the standard kits. */\nfunction useCurrentWorkspace(\n sessionId: string,\n useSessions?: HeaderProps['useSessions'],\n useWorkspaces?: HeaderProps['useWorkspaces'],\n): { workspace: WorkspaceRow | undefined; cwd: string | undefined } {\n const row = useSessions?.((s) => s.byId[sessionId]) as SessionRow | undefined\n const workspaces = useWorkspaces?.((s) => s.items) as WorkspaceRow[] | undefined\n return { workspace: matchWorkspace(row?.cwd, workspaces ?? []), cwd: row?.cwd }\n}\n\n/** Header icon button + command menu + live output popup. */\nexport function QuickCommandsHeaderAction(props: HeaderProps): JSX.Element {\n const { sessionId, t } = props\n const { workspace, cwd } = useCurrentWorkspace(sessionId, props.useSessions, props.useWorkspaces)\n const popover = useRunPopover()\n const [commands, setCommands] = useState<Array<{ name: string; command: string }>>([])\n const [menuLoadError, setMenuLoadError] = useState<string | null>(null)\n const [menuRemoteHost, setMenuRemoteHost] = useState<string | null>(null)\n const [busy, setBusy] = useState(false)\n const [anchor, setAnchor] = useState<'corner' | 'button'>('corner')\n const [popupSize, setPopupSize] = useState<QuickPopupSize | undefined>(undefined)\n const wrapRef = useRef<HTMLDivElement | null>(null)\n\n // Click-outside dismissal: while the menu is open, a pointerdown on the\n // document that lands OUTSIDE the wrap (button / menu / run popup all live\n // inside the wrap) collapses the menu. pointerdown (not click) covers mouse,\n // touch and pen, and fires before the button's own click toggle.\n useEffect(() => {\n if (!popover.menuOpen) return\n const onPointerDown = (e: PointerEvent): void => {\n const wrap = wrapRef.current\n if (wrap === null) return\n if (e.target instanceof Node && wrap.contains(e.target)) return\n closeMenu()\n }\n document.addEventListener('pointerdown', onPointerDown)\n return () => { document.removeEventListener('pointerdown', onPointerDown) }\n }, [popover.menuOpen])\n\n // Load the workspace's command list + anchor when the menu opens.\n useEffect(() => {\n if (!popover.menuOpen) return\n let cancelled = false\n void (async () => {\n try {\n const [entries, settings] = await Promise.all([quickApi.workspacesList(), quickApi.settingsGet()])\n if (cancelled) return\n const own = entries.find((e) => e.workspaceId === workspace?.workspaceId)\n setCommands(own === undefined ? [] : own.commands)\n setMenuRemoteHost(own?.remoteHost ?? null)\n setAnchor(settings.popupAnchor)\n setPopupSize(settings.popupSize)\n setMenuLoadError(null)\n } catch (e) {\n if (cancelled) return\n setCommands([])\n setMenuLoadError(e instanceof Error ? e.message : String(e))\n }\n })()\n return () => { cancelled = true }\n }, [popover.menuOpen, workspace?.workspaceId])\n\n const startRun = async (commandName: string): Promise<void> => {\n setBusy(true)\n closeMenu()\n try {\n const run = await quickApi.runStart(workspace!.workspaceId, commandName, cwd ?? workspace!.path)\n openRun(run.runId, run.workspaceId, run.commandName, run.remoteHost)\n } catch (e) {\n const message = e instanceof Error ? e.message : String(e)\n if (message.includes('run-busy') || message.includes('already running')) {\n setMenuLoadError(t('runBusy'))\n } else {\n setMenuLoadError(t('runStartFailed') + ': ' + message)\n }\n openMenu()\n } finally {\n setBusy(false)\n }\n }\n\n return (\n <div ref={wrapRef} className=\"qc-head-wrap\">\n <button\n type=\"button\"\n className=\"qc-head-btn\"\n title={workspace === undefined ? t('noWorkspace') : t('buttonTooltip')}\n aria-label={t('buttonTitle')}\n disabled={workspace === undefined}\n onClick={() => { popover.menuOpen ? closeMenu() : openMenu() }}\n >\n <IconPlayOutline16 size={16} />\n </button>\n\n {popover.menuOpen && workspace !== undefined && (\n <div className=\"qc-menu\" role=\"menu\">\n <div className=\"qc-menu-head\">\n <span className=\"qc-menu-title\">{t('menuTitle')}</span>\n {menuRemoteHost !== null && <span className=\"qc-menu-remote\" title={`SSH · ${menuRemoteHost}`}>SSH</span>}\n <span className=\"qc-menu-ws\">{workspace.title}</span>\n <button type=\"button\" className=\"qc-menu-close\" aria-label={t('runClose')} onClick={closeMenu}>\n <IconCloseOutline16 size={12} />\n </button>\n </div>\n {commands.length === 0 && (\n <p className=\"qc-menu-empty\">{menuLoadError ?? t('noCommands')}</p>\n )}\n {commands.filter((c) => c.name.trim() !== '').map((command) => (\n <button\n key={command.name}\n type=\"button\"\n role=\"menuitem\"\n className=\"qc-menu-item\"\n disabled={busy}\n onClick={() => void startRun(command.name)}\n >\n <span className=\"qc-menu-item-name\">{command.name}</span>\n <span className=\"qc-menu-item-cmd\">{command.command}</span>\n </button>\n ))}\n </div>\n )}\n\n {popover.runId !== null && (\n <RunPopup\n runId={popover.runId}\n workspaceId={popover.workspaceId ?? ''}\n commandName={popover.commandName ?? ''}\n remoteHost={popover.remoteHost ?? undefined}\n t={t}\n anchor={anchor}\n popupSize={popupSize}\n sessionId={sessionId}\n onClose={closeRun}\n />\n )}\n </div>\n )\n}\n\n// ── popup drag-resize ───────────────────────────────────────────────────────\n// The popup pins its RIGHT edge (both anchors), plus its BOTTOM edge (corner\n// anchor) or TOP edge (button anchor, pinned below the button). So the MOVABLE\n// borders are the LEFT border (width) and the TOP border (corner anchor) /\n// BOTTOM border (button anchor) (height); the free corner (top-left /\n// bottom-left) carries a diagonal grip for simultaneous two-axis resizing.\n// Every handle follows the cursor edge-for-edge — pulling the left border LEFT\n// widens, pulling the top border UP grows taller — nothing feels inverted.\n// The size is clamped to viewport bounds and persisted to settings.\n\nconst POPUP_MIN_W = 320\nconst POPUP_MIN_H = 200\n/** Total horizontal margin kept around the popup (both anchors). */\nconst POPUP_VIEWPORT_MARGIN = 40\n/** Vertical space reserved for header/footer chrome (both anchors). */\nconst POPUP_VIEWPORT_RESERVED = 120\n\nfunction popupDefaultSize(anchor: 'corner' | 'button'): QuickPopupSize {\n return anchor === 'corner' ? { width: 520, height: 340 } : { width: 440, height: 300 }\n}\n\nfunction popupClamp(size: QuickPopupSize): QuickPopupSize {\n const maxW = Math.max(POPUP_MIN_W, window.innerWidth - POPUP_VIEWPORT_MARGIN)\n const maxH = Math.max(POPUP_MIN_H, window.innerHeight - POPUP_VIEWPORT_RESERVED)\n return {\n width: Math.min(Math.max(POPUP_MIN_W, Math.round(size.width)), maxW),\n height: Math.min(Math.max(POPUP_MIN_H, Math.round(size.height)), maxH),\n }\n}\n\n/**\n * Initial popup size: the persisted one when it holds sane numbers, else the\n * per-anchor default. Defensive because schemastery resolves a MISSING object\n * field to `{}` (object schemas carry an implicit `default: {}`), so a\n * never-resized popup can arrive as `popupSize: {}` from settings.get().\n */\nfunction popupSizeOrDefault(size: QuickPopupSize | undefined, anchor: 'corner' | 'button'): QuickPopupSize {\n if (\n size === undefined\n || !Number.isFinite(size.width)\n || !Number.isFinite(size.height)\n || size.width <= 0\n || size.height <= 0\n ) {\n return popupDefaultSize(anchor)\n }\n return popupClamp(size)\n}\n\n/** The streaming output popup. Polls stdout/stderr deltas every ~200ms. */\nfunction RunPopup(props: {\n runId: string\n workspaceId: string\n commandName: string\n remoteHost?: string\n t: (key: string) => string\n anchor: 'corner' | 'button'\n popupSize?: QuickPopupSize\n sessionId: string\n onClose: () => void\n}): JSX.Element {\n const { runId, t, anchor, remoteHost, onClose } = props\n const [size, setSize] = useState<QuickPopupSize>(() => popupSizeOrDefault(props.popupSize, anchor))\n // Mirrors `size` for drag handlers: pointer events can arrive faster than\n // React commits, so pointerup must read the latest value, not the render's.\n const sizeRef = useRef<QuickPopupSize>(size)\n const sizeDragRef = useRef<{\n pointerId: number\n startX: number\n startY: number\n startW: number\n startH: number\n /** Which handle started the drag (controls which axes move). */\n mode: 'left' | 'height' | 'corner'\n } | null>(null)\n const [state, setState] = useState(() => ({\n status: 'running' as 'running' | 'exited',\n exitCode: null as number | null,\n signal: null as string | null,\n stdoutEnd: 0,\n stderrEnd: 0,\n stdout: '',\n stderr: '',\n stdoutLossy: false,\n stderrLossy: false,\n }))\n const [view, setView] = useState<'stdout' | 'stderr'>('stdout')\n const [autoFollow, setAutoFollow] = useState(true)\n const [killConfirm, setKillConfirm] = useState(false)\n const [pollError, setPollError] = useState<string | null>(null)\n const [closing, setClosing] = useState(false)\n const outputRef = useRef<HTMLDivElement | null>(null)\n const autoFollowRef = useRef(true)\n\n // Auto-follow: scroll the output pane to the bottom on new output while the\n // user has not scrolled up (autoFollow mirror keeps the effect cheap).\n useEffect(() => {\n autoFollowRef.current = autoFollow\n }, [autoFollow])\n useEffect(() => {\n if (!autoFollowRef.current) return\n const el = outputRef.current\n if (el === null) return\n el.scrollTop = el.scrollHeight\n }, [state.stdout, state.stderr, view])\n\n // Poll deltas; stop polling at exit (the run stays readable in memory).\n useEffect(() => {\n let cancelled = false\n let outEnd = 0\n let errEnd = 0\n const tick = async (): Promise<void> => {\n try {\n const poll = await quickApi.runPoll(runId, outEnd, errEnd)\n if (cancelled) return\n outEnd = poll.stdoutEnd\n errEnd = poll.stderrEnd\n setState((s) => ({\n status: poll.status,\n exitCode: poll.exitCode,\n signal: poll.signal,\n stdoutEnd: poll.stdoutEnd,\n stderrEnd: poll.stderrEnd,\n stdout: poll.stdoutLossy ? poll.stdoutDelta : s.stdout + poll.stdoutDelta,\n stderr: poll.stderrLossy ? poll.stderrDelta : s.stderr + poll.stderrDelta,\n stdoutLossy: poll.stdoutLossy,\n stderrLossy: poll.stderrLossy,\n }))\n setPollError(null)\n if (poll.status === 'exited') return\n timer = window.setTimeout(() => void tick(), 200)\n } catch (e) {\n if (cancelled) return\n setPollError(e instanceof Error ? e.message : String(e))\n timer = window.setTimeout(() => void tick(), 500)\n }\n }\n let timer: number\n void tick()\n return () => { cancelled = true; window.clearTimeout(timer) }\n }, [runId])\n\n const kill = async (): Promise<void> => {\n setClosing(true)\n try {\n await quickApi.runKill(runId)\n } finally {\n onClose()\n }\n }\n\n const close = (): void => {\n // Already exited: there is nothing left to terminate, close directly\n // instead of asking \"terminate this command?\".\n if (state.status === 'exited') {\n onClose()\n return\n }\n setKillConfirm(true)\n }\n\n const confirmClose = (): void => {\n void kill()\n }\n\n // ── drag-resize: pointer capture on the handles keeps moving/cancelling\n // events flowing even when the cursor leaves the handle or the window.\n // Which border is free to move depends on the anchor: the corner anchor\n // pins bottom+right (top border moves for height), the button anchor pins\n // top+right (bottom border moves for height).\n const heightFree: 'top' | 'bottom' = anchor === 'corner' ? 'top' : 'bottom'\n\n const onResizePointerDown = (e: React.PointerEvent<HTMLDivElement>): void => {\n e.preventDefault() // keep the handle from starting a text-selection gesture\n e.currentTarget.setPointerCapture(e.pointerId)\n sizeDragRef.current = {\n pointerId: e.pointerId,\n startX: e.clientX,\n startY: e.clientY,\n startW: sizeRef.current.width,\n startH: sizeRef.current.height,\n mode: e.currentTarget.dataset.resize as 'left' | 'height' | 'corner',\n }\n }\n\n const onResizePointerMove = (e: React.PointerEvent<HTMLDivElement>): void => {\n const drag = sizeDragRef.current\n if (drag === null || drag.pointerId !== e.pointerId) return\n const dx = e.clientX - drag.startX\n const dy = e.clientY - drag.startY\n const controlsW = drag.mode === 'left' || drag.mode === 'corner'\n const controlsH = drag.mode === 'height' || drag.mode === 'corner'\n const next = popupClamp({\n // Left border follows the cursor: dragging LEFT widens, RIGHT narrows.\n width: controlsW ? drag.startW - dx : drag.startW,\n // Top-free (corner): dragging UP grows. Bottom-free (button): DOWN grows.\n height: controlsH ? drag.startH + (heightFree === 'top' ? -dy : dy) : drag.startH,\n })\n sizeRef.current = next\n setSize(next)\n }\n\n const onResizePointerUp = (e: React.PointerEvent<HTMLDivElement>): void => {\n const drag = sizeDragRef.current\n if (drag === null || drag.pointerId !== e.pointerId) return\n sizeDragRef.current = null\n if (e.currentTarget.hasPointerCapture(e.pointerId)) {\n e.currentTarget.releasePointerCapture(e.pointerId)\n }\n // Persist best-effort: a failed save just means the next popup reopens at\n // the default size (or the last successfully saved one).\n void quickApi.settingsSetPopupSize(sizeRef.current).catch(() => undefined)\n }\n\n /** Pointer-handler trio for one resize handle (mode = which axes it drives). */\n const resizeProps = (mode: 'left' | 'height' | 'corner') => ({\n 'data-resize': mode,\n onPointerDown: onResizePointerDown,\n onPointerMove: onResizePointerMove,\n onPointerUp: onResizePointerUp,\n onPointerCancel: onResizePointerUp,\n })\n\n const active = state.status === 'running'\n const title = active ? `${props.commandName} · ${t('runStateRunning')}` : `${props.commandName} · ${t('runStateExited')} (${state.exitCode ?? '?'})`\n\n return (\n <div className={`qc-popup qc-popup-${anchor}`} role=\"dialog\" aria-label={title} style={{ width: size.width, height: size.height }}>\n <header className=\"qc-popup-head\">\n <span className=\"qc-popup-title\" title={title}>{title}</span>\n {remoteHost !== undefined && <span className=\"qc-popup-remote\" title={`SSH · ${remoteHost}`}>SSH</span>}\n <span className=\"qc-popup-cmd\">{props.commandName}</span>\n <button\n type=\"button\"\n className=\"qc-popup-icon qc-popup-kill\"\n disabled={!active || closing}\n title={t('runKill')}\n aria-label={t('runKill')}\n onClick={() => void kill()}\n >\n <IconStopFill16 size={14} />\n </button>\n <button\n type=\"button\"\n className=\"qc-popup-icon qc-popup-close\"\n title={t('runClose')}\n aria-label={t('runClose')}\n onClick={close}\n >\n <IconCloseOutline16 size={14} />\n </button>\n </header>\n\n {killConfirm && (\n <div className=\"qc-popup-confirm\">\n <span>{t('runKillConfirm')}</span>\n <button type=\"button\" className=\"qc-popup-btn\" onClick={confirmClose}>{t('runKill')}</button>\n <button type=\"button\" className=\"qc-popup-btn qc-popup-btn-plain\" onClick={() => setKillConfirm(false)}>\n {t('runClose')}\n </button>\n </div>\n )}\n\n <div className=\"qc-popup-tabs\">\n <button\n type=\"button\"\n className={'qc-popup-tab' + (view === 'stdout' ? ' qc-popup-tab-active' : '')}\n onClick={() => setView('stdout')}\n >\n {t('runStdout')}\n </button>\n <button\n type=\"button\"\n className={'qc-popup-tab' + (view === 'stderr' ? ' qc-popup-tab-active' : '')}\n onClick={() => setView('stderr')}\n >\n {t('runStderr')}\n </button>\n </div>\n\n <div\n ref={outputRef}\n className=\"qc-popup-output\"\n onScroll={(e) => {\n const el = e.currentTarget\n const atEnd = Math.abs(el.scrollHeight - el.scrollTop - el.clientHeight) < 24\n setAutoFollow(atEnd)\n }}\n >\n {pollError !== null && <p className=\"qc-popup-error\">{t('runPollFailed')}: {pollError}</p>}\n {view === 'stdout' ? (\n state.stdout === '' && !active ? <p className=\"qc-popup-empty\">{t('runEmpty')}</p>\n : <pre className=\"qc-popup-pre\">{state.stdout}</pre>\n ) : (\n state.stderr === '' && !active ? <p className=\"qc-popup-empty\">{t('runEmpty')}</p>\n : <pre className=\"qc-popup-pre\">{state.stderr}</pre>\n )}\n </div>\n\n <footer className=\"qc-popup-foot\">\n <span className=\"qc-popup-state\">\n {active ? t('runStateRunning') : `${t('runStateExited')} · ${t('runExitCode')} ${state.exitCode ?? '-'}${state.signal !== null ? ` · ${state.signal}` : ''}`}\n </span>\n </footer>\n\n {/* Resize handles: left border = width; top (corner anchor) / bottom\n (button anchor) border = height; free corner = diagonal grip. */}\n <div className=\"qc-popup-resize qc-popup-resize-left\" title={t('runResize')} {...resizeProps('left')} />\n {heightFree === 'top'\n ? <div className=\"qc-popup-resize qc-popup-resize-top\" title={t('runResize')} {...resizeProps('height')} />\n : <div className=\"qc-popup-resize qc-popup-resize-bottom\" title={t('runResize')} {...resizeProps('height')} />}\n {heightFree === 'top' ? (\n <div className=\"qc-popup-resize qc-popup-resize-corner qc-popup-resize-corner-tl\" title={t('runResize')} {...resizeProps('corner')}>\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" aria-hidden=\"true\">\n <path\n d=\"M9 9 3 3 M9 5.5 5.5 3 M5.5 9 3 5.5\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.4\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </div>\n ) : (\n <div className=\"qc-popup-resize qc-popup-resize-corner qc-popup-resize-corner-bl\" title={t('runResize')} {...resizeProps('corner')}>\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" aria-hidden=\"true\">\n <path\n d=\"M9 3 3 9 M9 6.5 6.5 9 M5.5 3 3 5.5\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.4\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </div>\n )}\n </div>\n )\n}\n","/**\n * Quick-commands settings card (设置 → 插件配置): an expandable plugin card —\n * header (title + description + chevron) collapsed by default, body reveals\n * the workspace × command-set editor and the popup anchor preference. Body\n * edits instant-save per change; partial rows (empty name/command) persist\n * while the user types.\n *\n * Mirrors the official PluginCard disclosure pattern (header button with\n * aria-expanded, rotate-180 chevron, body separated by a top border).\n */\nimport { useEffect, useRef, useState } from 'react'\nimport {\n IconChevronDownOutline14,\n IconPlusOutline16,\n IconTrashOutline16,\n} from '@deepseek-ai/dsh-client-ui-primitives'\nimport { quickApi } from './api'\nimport type { QuickRosterEntry, QuickCommandsSettings } from '../shared/contract'\n\ninterface Props {\n t: (key: string) => string\n}\n\ninterface RosterState {\n loaded: boolean\n entries: QuickRosterEntry[]\n anchor: QuickCommandsSettings['popupAnchor']\n}\n\ntype CommandDraft = { name: string; command: string }\n\nexport function QuickCommandsSettingsCard({ t }: Props): JSX.Element {\n // Card-local disclosure state (a reading gesture, like the official card).\n const [open, setOpen] = useState(false)\n const [state, setState] = useState<RosterState>({ loaded: false, entries: [], anchor: 'corner' })\n const [error, setError] = useState<string | null>(null)\n // Guard concurrent saves: the last write wins via a monotonically bumped token.\n const saveToken = useRef(0)\n\n const load = async (): Promise<void> => {\n try {\n const [entries, settings] = await Promise.all([quickApi.workspacesList(), quickApi.settingsGet()])\n setState({ loaded: true, entries, anchor: settings.popupAnchor })\n setError(null)\n } catch (e) {\n setState((s) => ({ ...s, loaded: true }))\n setError(e instanceof Error ? e.message : String(e))\n }\n }\n\n useEffect(() => {\n void load()\n }, [])\n\n /** Instant-save the workspace's whole command set; optimistic UI. */\n const saveCommands = async (workspaceId: string, commands: CommandDraft[]): Promise<void> => {\n const token = ++saveToken.current\n try {\n const next = await quickApi.settingsSetCommands(workspaceId, commands)\n if (token !== saveToken.current) return\n setState((s) => ({\n ...s,\n anchor: next.popupAnchor,\n entries: s.entries.map((e) => (e.workspaceId === workspaceId ? { ...e, commands } : e)),\n }))\n setError(null)\n } catch (e) {\n setError(e instanceof Error ? e.message : String(e))\n // Re-sync to server truth on failure.\n void load()\n }\n }\n\n /** Compute the next command list for a workspace, THEN save + update state. */\n const mutateCommands = (workspaceId: string, next: (current: CommandDraft[]) => CommandDraft[]): void => {\n const entry = state.entries.find((e) => e.workspaceId === workspaceId)\n if (entry === undefined) return\n const commands = next(entry.commands)\n // Optimistic update first (row stays visible immediately), then persist.\n setState((s) => ({\n ...s,\n entries: s.entries.map((e) => (e.workspaceId === workspaceId ? { ...e, commands } : e)),\n }))\n void saveCommands(workspaceId, commands)\n }\n\n const patchEntry = (workspaceId: string, index: number, field: 'name' | 'command', value: string): void => {\n mutateCommands(workspaceId, (current) =>\n current.map((c, i) => (i === index ? { ...c, [field]: value } : c)),\n )\n }\n\n const addCommand = (workspaceId: string): void => {\n mutateCommands(workspaceId, (current) => [...current, { name: '', command: '' }])\n }\n\n const removeCommand = (workspaceId: string, index: number): void => {\n mutateCommands(workspaceId, (current) => current.filter((_, i) => i !== index))\n }\n\n const setAnchor = async (anchor: QuickCommandsSettings['popupAnchor']): Promise<void> => {\n setState((s) => ({ ...s, anchor }))\n try {\n const next = await quickApi.settingsSetAnchor(anchor)\n setState((s) => ({ ...s, anchor: next.popupAnchor }))\n } catch (e) {\n setError(e instanceof Error ? e.message : String(e))\n void load()\n }\n }\n\n const title = t('cardTitle')\n const description = t('cardDescription')\n\n return (\n <li className={'qc-settings-card' + (open ? ' qc-settings-card-open' : '')}>\n <button\n type=\"button\"\n className=\"qc-settings-header\"\n aria-expanded={open}\n aria-label={`${t('collapseOrExpand')}: ${title}`}\n onClick={() => setOpen((v) => !v)}\n >\n <span className=\"qc-settings-head-text\">\n <span className=\"qc-settings-name\">{title}</span>\n <span className=\"qc-settings-description\">{description}</span>\n </span>\n <IconChevronDownOutline14 className={'qc-settings-chevron' + (open ? ' qc-settings-chevron-open' : '')} />\n </button>\n\n {open && (\n <div className=\"qc-settings-body\">\n <p className=\"qc-settings-intro\">{t('cardIntro')}</p>\n\n <div className=\"qc-settings-anchor\">\n <span className=\"qc-settings-anchor-label\">{t('cardAnchor')}</span>\n <label className=\"qc-settings-radio\">\n <input\n type=\"radio\"\n name=\"qc-anchor\"\n checked={state.anchor === 'corner'}\n onChange={() => void setAnchor('corner')}\n />\n {t('cardAnchorCorner')}\n </label>\n <label className=\"qc-settings-radio\">\n <input\n type=\"radio\"\n name=\"qc-anchor\"\n checked={state.anchor === 'button'}\n onChange={() => void setAnchor('button')}\n />\n {t('cardAnchorButton')}\n </label>\n </div>\n\n {error !== null && <div className=\"qc-settings-error\">{t('cardSaveError')}: {error}</div>}\n\n {state.loaded && state.entries.length === 0 && (\n <p className=\"qc-settings-empty\">{t('cardNoWorkspaces')}</p>\n )}\n\n {state.entries.map((entry) => (\n <section key={entry.workspaceId} className=\"qc-settings-workspace\">\n <header className=\"qc-settings-workspace-head\">\n <span className=\"qc-settings-workspace-title\">{entry.title || entry.path || entry.workspaceId}</span>\n <span className=\"qc-settings-workspace-path\">{entry.path}</span>\n </header>\n {entry.commands.length === 0 && (\n <p className=\"qc-settings-no-commands\">{t('cardNoCommands')}</p>\n )}\n {entry.commands.map((command, index) => (\n <div key={`${entry.workspaceId}-${index}`} className=\"qc-settings-command\">\n <input\n className=\"qc-settings-field qc-settings-field-name\"\n placeholder={t('cardName')}\n value={command.name}\n aria-label={t('cardName')}\n onChange={(e) => patchEntry(entry.workspaceId, index, 'name', e.target.value)}\n />\n <input\n className=\"qc-settings-field qc-settings-field-command\"\n placeholder={t('cardCommand')}\n value={command.command}\n aria-label={t('cardCommand')}\n onChange={(e) => patchEntry(entry.workspaceId, index, 'command', e.target.value)}\n />\n <button\n type=\"button\"\n className=\"qc-settings-icon-btn\"\n aria-label={t('cardRemove')}\n title={t('cardRemove')}\n onClick={() => removeCommand(entry.workspaceId, index)}\n >\n <IconTrashOutline16 size={14} />\n </button>\n </div>\n ))}\n <button type=\"button\" className=\"qc-settings-add-btn\" onClick={() => addCommand(entry.workspaceId)}>\n <IconPlusOutline16 size={14} />\n {t('cardAddCommand')}\n </button>\n </section>\n ))}\n </div>\n )}\n </li>\n )\n}\n","/**\n * Client dictionary namespace for @fonlan/dsh-quick-commands.\n */\nexport const LOCALE_NS = 'quick-commands'\n\nexport const zh = {\n // header button\n buttonTitle: '快速命令',\n buttonTooltip: '快速命令(当前工作区)',\n noWorkspace: '当前会话不在任何已配置的工作区中',\n noCommands: '当前工作区未配置命令',\n openSettings: '去设置配置命令',\n\n // command popup menu\n menuTitle: '快速命令',\n menuEmpty: '(无命令)',\n\n // run popup\n runTitle: '运行中',\n runExited: '已退出',\n runExitCode: '退出码',\n runSignal: '信号',\n runStdout: '输出 (stdout)',\n runStderr: '错误 (stderr)',\n runEmpty: '(无输出)',\n runCommand: '命令',\n runCwd: '工作区',\n runStateRunning: '运行中…',\n runStateExited: '已退出',\n runKill: '终止',\n runClose: '关闭',\n runResize: '拖动调整弹窗大小',\n runKillConfirm: '终止后输出弹窗将关闭,确定终止该命令?',\n runBusy: '该工作区已有命令在运行',\n runStartFailed: '命令启动失败',\n runPollFailed: '输出读取失败',\n runLost: '运行记录已失效',\n runNotFound: '工作区或命令未找到',\n\n // settings card\n cardTitle: '快速命令',\n cardDescription: '为每个工作区配置常用命令,会话头部一键运行',\n collapseOrExpand: '展开 / 收起',\n cardIntro: '为每个工作区配置常用命令,运行于工作区目录。支持占位符:{workspace} 工作区路径,{cwd} 会话目录,{title} 工作区标题。',\n cardWorkspaces: '工作区',\n cardNoWorkspaces: '没有工作区。请在侧边栏先添加工作区。',\n cardCommands: '命令',\n cardNoCommands: '未配置命令',\n cardAddCommand: '添加命令',\n cardName: '名称',\n cardCommand: '命令',\n cardRemove: '删除',\n cardSaveError: '保存失败',\n cardAnchor: '输出弹窗位置',\n cardAnchorCorner: '右下角',\n cardAnchorButton: '锚定按钮下方',\n}\n\nexport const en = {\n buttonTitle: 'Quick commands',\n buttonTooltip: 'Quick commands (current workspace)',\n noWorkspace: 'This session is not in any configured workspace',\n noCommands: 'No commands configured for this workspace',\n openSettings: 'Configure commands in Settings',\n\n menuTitle: 'Quick commands',\n menuEmpty: '(no commands)',\n\n runTitle: 'Running',\n runExited: 'Exited',\n runExitCode: 'Exit code',\n runSignal: 'Signal',\n runStdout: 'Output (stdout)',\n runStderr: 'Errors (stderr)',\n runEmpty: '(no output)',\n runCommand: 'Command',\n runCwd: 'Workspace',\n runStateRunning: 'Running…',\n runStateExited: 'Exited',\n runKill: 'Terminate',\n runClose: 'Close',\n runResize: 'Drag to resize the popup',\n runKillConfirm: 'Terminating closes the output popup. Terminate this command?',\n runBusy: 'A command is already running for this workspace',\n runStartFailed: 'Command failed to start',\n runPollFailed: 'Failed to read output',\n runLost: 'Run record expired',\n runNotFound: 'Workspace or command not found',\n\n cardTitle: 'Quick commands',\n cardDescription: 'Configure common commands per workspace, run them from the session header',\n collapseOrExpand: 'Expand / collapse',\n cardIntro: 'Configure common commands per workspace; they run in the workspace directory. Placeholders: {workspace} workspace path, {cwd} session cwd, {title} workspace title.',\n cardWorkspaces: 'Workspaces',\n cardNoWorkspaces: 'No workspaces. Add one in the sidebar first.',\n cardCommands: 'Commands',\n cardNoCommands: 'No commands configured',\n cardAddCommand: 'Add command',\n cardName: 'Name',\n cardCommand: 'Command',\n cardRemove: 'Remove',\n cardSaveError: 'Save failed',\n cardAnchor: 'Output popup position',\n cardAnchorCorner: 'Bottom right',\n cardAnchorButton: 'Anchored at button',\n}\n","/**\n * @fonlan/dsh-quick-commands client half.\n *\n * Registers:\n * - `settings.plugin.item` card (keyed by the `quick-commands` settings\n * namespace) — per-workspace command CRUD + popup anchor preference.\n * - `conversation.session.header.utilities` entry (id `quick-commands`,\n * order -1 → left of the Session log pill) — the quick-run ▶ button, command\n * menu, and live output popup.\n */\nimport type { Context } from '@deepseek-ai/cordis'\ntype ClientContext = Context\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-settings-plugins/client'\nimport { QuickCommandsHeaderAction } from './header'\nimport { QuickCommandsSettingsCard } from './settings-card'\nimport { LOCALE_NS, zh, en } from './locales'\nimport './quick-commands.css'\n\n/** Slots face (local, erased at build). */\ninterface Slots {\n inject(name: string, callback: () => unknown): unknown\n register(\n def: { name: string; key?: string; id?: string; order?: number; locale?: string },\n component: unknown,\n ): unknown\n}\n\n/** Services required before mounting (provided by the client runtime). */\nexport const inject = ['slots', 'locale']\n\n/** Client plugin body. */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(LOCALE_NS, { zh, en }), 'quick-commands: dictionaries')\n const t = ctx.locale.bind(LOCALE_NS)\n const slots = (ctx as unknown as { slots: Slots }).slots\n\n // The plugin's own Settings Card (设置 → 插件配置) rides the `quick-commands`\n // settings namespace: registering into the keyed `settings.plugin.item` slot\n // with the namespace string makes the configurable-plugins tab dispatch the\n // card next to the built-in ones (bash / agent loop / web search).\n slots.inject('settings.plugin.item', () =>\n slots.register({\n name: 'settings.plugin.item',\n key: LOCALE_NS,\n locale: LOCALE_NS,\n }, QuickCommandsSettingsCard as never),\n )\n\n // Session-header util: order -1 renders LEFT of the Session log pill\n // (session-log-download registers with default order 0).\n slots.inject('conversation.session.header.utilities', () =>\n slots.register({\n name: 'conversation.session.header.utilities',\n id: 'quick-commands',\n order: -1,\n locale: LOCALE_NS,\n }, QuickCommandsHeaderAction as never),\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":["useSyncExternalStore","useState","useRef","IconPlayOutline16","IconCloseOutline16","IconStopFill16","useState","useRef","IconChevronDownOutline14","IconTrashOutline16","IconPlusOutline16"],"sources":["../../../src/client/api.ts","../../../src/client/run-state.ts","../../../src/client/header.tsx","../../../src/client/settings-card.tsx","../../../src/client/locales.ts","../../../src/client/index.tsx"],"sourcesContent":["/**\n * Browser client for the plugin's fenced JSON API\n * (/plugins/@fonlan/dsh-quick-commands/api/<method>). Same-origin fetch with\n * the task-kanban envelope shape: { ok, value } | { ok:false, error }.\n */\nimport type {\n ApiEnvelope,\n QuickCommandsSettings,\n QuickPopupSize,\n QuickRosterEntry,\n QuickRunView,\n} from '../shared/contract'\n\nexport class QuickCommandsApiError extends Error {\n constructor(readonly code: string, message: string) {\n super(message)\n }\n}\n\nasync function call<T>(method: string, payload: Record<string, unknown> = {}): Promise<T> {\n let response: Response\n try {\n response = await fetch(`/plugins/@fonlan/dsh-quick-commands/api/${method}`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n })\n } catch (error) {\n throw new QuickCommandsApiError('network', error instanceof Error ? error.message : String(error))\n }\n const parsed = (await response.json().catch(() => null)) as ApiEnvelope<T> | null\n if (!response.ok || parsed === null || parsed.ok !== true || parsed.value === undefined) {\n throw new QuickCommandsApiError(\n (parsed?.error?.code as string | undefined) ?? 'http',\n parsed?.error?.message ?? `HTTP ${response.status}`,\n )\n }\n return parsed.value as T\n}\n\n/** Current session cwd of a session row (SessionSummary carries cwd). */\nexport interface SessionRow {\n id: string\n cwd?: string\n}\n\n/** One delta read result (mirrors the host RunPollResult). */\nexport interface RunPoll {\n status: 'running' | 'exited'\n exitCode: number | null\n signal: string | null\n stdoutEnd: number\n stderrEnd: number\n stdoutDelta: string\n stderrDelta: string\n stdoutLossy: boolean\n stderrLossy: boolean\n}\n\nexport const quickApi = {\n settingsGet: () => call<QuickCommandsSettings>('settings.get'),\n settingsSetCommands: (workspaceId: string, commands: Array<{ name: string; command: string }>) =>\n call<QuickCommandsSettings>('settings.setCommands', { workspaceId, commands }),\n settingsSetAnchor: (anchor: QuickCommandsSettings['popupAnchor']) =>\n call<QuickCommandsSettings>('settings.setAnchor', { anchor }),\n settingsSetPopupSize: (size: QuickPopupSize) =>\n call<QuickCommandsSettings>('settings.setPopupSize', { width: size.width, height: size.height }),\n workspacesList: () => call<QuickRosterEntry[]>('workspaces.list'),\n runStart: (workspaceId: string, commandName: string, sessionCwd: string) =>\n call<QuickRunView>('run.start', { workspaceId, commandName, sessionCwd }),\n runPoll: (runId: string, stdoutFrom: number, stderrFrom: number) =>\n call<RunPoll>('run.poll', { runId, stdoutFrom, stderrFrom }),\n runKill: (runId: string) => call<{ killed: boolean }>('run.kill', { runId }),\n runList: (workspaceId: string) =>\n call<Array<{ runId: string; commandName: string; startedAt: number; status: 'running' | 'exited' }>>('run.list', { workspaceId }),\n}\n","/**\n * HMR-safe shared run state for the quick-commands header button and output\n * popup. Module-level fields live on one globalThis-backed record keyed with\n * Symbol.for so every client-bundle copy (client-hmr hot swap re-evaluates the\n * bundle without reloading the page) reads/writes the same state cell.\n *\n * The popup handles ONE run at a time (serial per workspace by design); a\n * second workspace can run in parallel with its own popup, so the store keys\n * popups by runId.\n */\nimport { useSyncExternalStore } from 'react'\n\nconst STATE_KEY = Symbol.for('@fonlan/dsh-quick-commands/run-state')\n\n/** One popup slot: menu open flag + the live run the popup streams. */\nexport interface RunPopoverState {\n /** Whether the header command menu is open. */\n menuOpen: boolean\n /** Active run id (undefined when no run is open). */\n runId: string | null\n /** Workspace id owning the active run (for kill/cwd display). */\n workspaceId: string | null\n /** Command display name. */\n commandName: string | null\n /** SSH host id when the run executes on a remote workspace (else null). */\n remoteHost: string | null\n /** Whether the run popup should stream (false while minimized). */\n streaming: boolean\n}\n\ninterface RunPopoverStateInternal extends RunPopoverState {\n listeners: Set<() => void>\n}\n\ninterface QuickCommandsState {\n popover: RunPopoverStateInternal\n /** Cached published snapshot — STABLE reference; rebuilt only after a patch. */\n snapshot: RunPopoverState | null\n}\n\nfunction getState(): QuickCommandsState {\n const g = globalThis as unknown as Record<symbol, QuickCommandsState | undefined>\n let state = g[STATE_KEY]\n if (state === undefined) {\n state = {\n popover: {\n menuOpen: false,\n runId: null,\n workspaceId: null,\n commandName: null,\n remoteHost: null,\n streaming: true,\n listeners: new Set(),\n },\n // Seed the cache with values our equality check will see as unchanged.\n snapshot: {\n menuOpen: false,\n runId: null,\n workspaceId: null,\n commandName: null,\n remoteHost: null,\n streaming: true,\n },\n }\n g[STATE_KEY] = state\n }\n return state\n}\n\nfunction notify(): void {\n // Invalidate the cached snapshot FIRST: useSyncExternalStore reads\n // getSnapshot after notification, so a new stable object must be ready.\n getState().snapshot = null\n for (const listener of getState().popover.listeners) listener()\n}\n\nfunction subscribe(cb: () => void): () => void {\n const listeners = getState().popover.listeners\n listeners.add(cb)\n return () => { listeners.delete(cb) }\n}\n\n/**\n * getSnapshot for useSyncExternalStore. MUST return a stable reference while\n * the underlying fields are unchanged (React compares with Object.is) — return\n * the CACHED object; only rebuild it when a patch invalidated it.\n */\nfunction snapshot(): RunPopoverState {\n const state = getState()\n if (state.snapshot !== null) return state.snapshot\n const p = state.popover\n state.snapshot = {\n menuOpen: p.menuOpen,\n runId: p.runId,\n workspaceId: p.workspaceId,\n commandName: p.commandName,\n remoteHost: p.remoteHost,\n streaming: p.streaming,\n }\n return state.snapshot\n}\n\nfunction patch(partial: Partial<RunPopoverState>): void {\n const state = getState()\n Object.assign(state.popover, partial)\n // Invalidate the cached snapshot so equality flips exactly on real changes.\n state.snapshot = null\n notify()\n}\n\nexport function useRunPopover(): RunPopoverState {\n return useSyncExternalStore(subscribe, snapshot, snapshot)\n}\n\nexport function openMenu(): void { patch({ menuOpen: true }) }\nexport function closeMenu(): void { patch({ menuOpen: false }) }\nexport function toggleMenu(open: boolean): void { patch({ menuOpen: open }) }\n\nexport function openRun(runId: string, workspaceId: string, commandName: string, remoteHost?: string): void {\n patch({ runId, workspaceId, commandName, remoteHost: remoteHost ?? null, menuOpen: false, streaming: true })\n}\n\nexport function closeRun(): void {\n patch({ runId: null, workspaceId: null, commandName: null, remoteHost: null, streaming: false })\n}\n","/**\n * Session-header quick-command surface: the ▶ play icon button (left of the\n * Session log pill), the command menu popover (current workspace's commands),\n * and the live output popup that streams stdout/stderr at ~200ms cadence.\n *\n * Props are the standard session/global kit of `conversation.session.header.utilities`\n * plus this plugin's injected hooks (anchor preference + resolver).\n */\nimport { useEffect, useRef, useState } from 'react'\nimport {\n IconPlayOutline16,\n IconCloseOutline16,\n IconStopFill16,\n} from '@deepseek-ai/dsh-client-ui-primitives'\nimport { quickApi } from './api'\nimport { useRunPopover, openMenu, closeMenu, openRun, closeRun } from './run-state'\nimport type { QuickPopupSize } from '../shared/contract'\n\n/** Workspace row shape the standard useWorkspaces hook exposes. */\ninterface WorkspaceRow {\n workspaceId: string\n path: string\n title: string\n}\n\n/** Session list state subset we read (cwd + id). */\ninterface SessionRow {\n id: string\n cwd?: string\n}\n\ninterface HeaderProps {\n sessionId: string\n t: (key: string) => string\n useSessions?: (selector: (s: { byId: Record<string, SessionRow>; current?: string }) => unknown) => unknown\n useWorkspaces?: (selector: (s: { items: WorkspaceRow[] }) => unknown) => unknown\n}\n\n/** Longest-path workspace match for a cwd (subdirectory allowed). */\nfunction matchWorkspace(cwd: string | undefined, workspaces: WorkspaceRow[]): WorkspaceRow | undefined {\n if (cwd === undefined) return undefined\n let best: WorkspaceRow | undefined\n for (const ws of workspaces) {\n const norm = ws.path.replace(/\\\\/g, '/').replace(/\\/+$/, '')\n const nc = cwd.replace(/\\\\/g, '/')\n if (nc === norm || nc.startsWith(norm + '/')) {\n if (best === undefined || norm.length > best.path.length) best = ws\n }\n }\n return best\n}\n\n/** Resolve the current session's workspace from the standard kits. */\nfunction useCurrentWorkspace(\n sessionId: string,\n useSessions?: HeaderProps['useSessions'],\n useWorkspaces?: HeaderProps['useWorkspaces'],\n): { workspace: WorkspaceRow | undefined; cwd: string | undefined } {\n const row = useSessions?.((s) => s.byId[sessionId]) as SessionRow | undefined\n const workspaces = useWorkspaces?.((s) => s.items) as WorkspaceRow[] | undefined\n return { workspace: matchWorkspace(row?.cwd, workspaces ?? []), cwd: row?.cwd }\n}\n\n/** Header icon button + command menu + live output popup. */\nexport function QuickCommandsHeaderAction(props: HeaderProps): JSX.Element {\n const { sessionId, t } = props\n const { workspace, cwd } = useCurrentWorkspace(sessionId, props.useSessions, props.useWorkspaces)\n const popover = useRunPopover()\n const [commands, setCommands] = useState<Array<{ name: string; command: string }>>([])\n const [menuLoadError, setMenuLoadError] = useState<string | null>(null)\n const [menuRemoteHost, setMenuRemoteHost] = useState<string | null>(null)\n const [busy, setBusy] = useState(false)\n const [anchor, setAnchor] = useState<'corner' | 'button'>('corner')\n const [popupSize, setPopupSize] = useState<QuickPopupSize | undefined>(undefined)\n const wrapRef = useRef<HTMLDivElement | null>(null)\n\n // Click-outside dismissal: while the menu is open, a pointerdown on the\n // document that lands OUTSIDE the wrap (button / menu / run popup all live\n // inside the wrap) collapses the menu. pointerdown (not click) covers mouse,\n // touch and pen, and fires before the button's own click toggle.\n useEffect(() => {\n if (!popover.menuOpen) return\n const onPointerDown = (e: PointerEvent): void => {\n const wrap = wrapRef.current\n if (wrap === null) return\n if (e.target instanceof Node && wrap.contains(e.target)) return\n closeMenu()\n }\n document.addEventListener('pointerdown', onPointerDown)\n return () => { document.removeEventListener('pointerdown', onPointerDown) }\n }, [popover.menuOpen])\n\n // Load the workspace's command list + anchor when the menu opens.\n useEffect(() => {\n if (!popover.menuOpen) return\n let cancelled = false\n void (async () => {\n try {\n const [entries, settings] = await Promise.all([quickApi.workspacesList(), quickApi.settingsGet()])\n if (cancelled) return\n const own = entries.find((e) => e.workspaceId === workspace?.workspaceId)\n setCommands(own === undefined ? [] : own.commands)\n setMenuRemoteHost(own?.remoteHost ?? null)\n setAnchor(settings.popupAnchor)\n setPopupSize(settings.popupSize)\n setMenuLoadError(null)\n } catch (e) {\n if (cancelled) return\n setCommands([])\n setMenuLoadError(e instanceof Error ? e.message : String(e))\n }\n })()\n return () => { cancelled = true }\n }, [popover.menuOpen, workspace?.workspaceId])\n\n const startRun = async (commandName: string): Promise<void> => {\n setBusy(true)\n closeMenu()\n try {\n const run = await quickApi.runStart(workspace!.workspaceId, commandName, cwd ?? workspace!.path)\n openRun(run.runId, run.workspaceId, run.commandName, run.remoteHost)\n } catch (e) {\n const message = e instanceof Error ? e.message : String(e)\n if (message.includes('run-busy') || message.includes('already running')) {\n setMenuLoadError(t('runBusy'))\n } else {\n setMenuLoadError(t('runStartFailed') + ': ' + message)\n }\n openMenu()\n } finally {\n setBusy(false)\n }\n }\n\n return (\n <div ref={wrapRef} className=\"qc-head-wrap\">\n <button\n type=\"button\"\n className=\"qc-head-btn\"\n title={workspace === undefined ? t('noWorkspace') : t('buttonTooltip')}\n aria-label={t('buttonTitle')}\n disabled={workspace === undefined}\n onClick={() => { popover.menuOpen ? closeMenu() : openMenu() }}\n >\n <IconPlayOutline16 size={15} />\n </button>\n\n {popover.menuOpen && workspace !== undefined && (\n <div className=\"qc-menu\" role=\"menu\">\n <div className=\"qc-menu-head\">\n <span className=\"qc-menu-title\">{t('menuTitle')}</span>\n {menuRemoteHost !== null && <span className=\"qc-menu-remote\" title={`SSH · ${menuRemoteHost}`}>SSH</span>}\n <span className=\"qc-menu-ws\">{workspace.title}</span>\n <button type=\"button\" className=\"qc-menu-close\" aria-label={t('runClose')} onClick={closeMenu}>\n <IconCloseOutline16 size={12} />\n </button>\n </div>\n {commands.length === 0 && (\n <p className=\"qc-menu-empty\">{menuLoadError ?? t('noCommands')}</p>\n )}\n {commands.filter((c) => c.name.trim() !== '').map((command) => (\n <button\n key={command.name}\n type=\"button\"\n role=\"menuitem\"\n className=\"qc-menu-item\"\n disabled={busy}\n onClick={() => void startRun(command.name)}\n >\n <span className=\"qc-menu-item-name\">{command.name}</span>\n <span className=\"qc-menu-item-cmd\">{command.command}</span>\n </button>\n ))}\n </div>\n )}\n\n {popover.runId !== null && (\n <RunPopup\n runId={popover.runId}\n workspaceId={popover.workspaceId ?? ''}\n commandName={popover.commandName ?? ''}\n remoteHost={popover.remoteHost ?? undefined}\n t={t}\n anchor={anchor}\n popupSize={popupSize}\n sessionId={sessionId}\n onClose={closeRun}\n />\n )}\n </div>\n )\n}\n\n// ── popup drag-resize ───────────────────────────────────────────────────────\n// The popup pins its RIGHT edge (both anchors), plus its BOTTOM edge (corner\n// anchor) or TOP edge (button anchor, pinned below the button). So the MOVABLE\n// borders are the LEFT border (width) and the TOP border (corner anchor) /\n// BOTTOM border (button anchor) (height); the free corner (top-left /\n// bottom-left) carries a diagonal grip for simultaneous two-axis resizing.\n// Every handle follows the cursor edge-for-edge — pulling the left border LEFT\n// widens, pulling the top border UP grows taller — nothing feels inverted.\n// The size is clamped to viewport bounds and persisted to settings.\n\nconst POPUP_MIN_W = 320\nconst POPUP_MIN_H = 200\n/** Total horizontal margin kept around the popup (both anchors). */\nconst POPUP_VIEWPORT_MARGIN = 40\n/** Vertical space reserved for header/footer chrome (both anchors). */\nconst POPUP_VIEWPORT_RESERVED = 120\n\nfunction popupDefaultSize(anchor: 'corner' | 'button'): QuickPopupSize {\n return anchor === 'corner' ? { width: 520, height: 340 } : { width: 440, height: 300 }\n}\n\nfunction popupClamp(size: QuickPopupSize): QuickPopupSize {\n const maxW = Math.max(POPUP_MIN_W, window.innerWidth - POPUP_VIEWPORT_MARGIN)\n const maxH = Math.max(POPUP_MIN_H, window.innerHeight - POPUP_VIEWPORT_RESERVED)\n return {\n width: Math.min(Math.max(POPUP_MIN_W, Math.round(size.width)), maxW),\n height: Math.min(Math.max(POPUP_MIN_H, Math.round(size.height)), maxH),\n }\n}\n\n/**\n * Initial popup size: the persisted one when it holds sane numbers, else the\n * per-anchor default. Defensive because schemastery resolves a MISSING object\n * field to `{}` (object schemas carry an implicit `default: {}`), so a\n * never-resized popup can arrive as `popupSize: {}` from settings.get().\n */\nfunction popupSizeOrDefault(size: QuickPopupSize | undefined, anchor: 'corner' | 'button'): QuickPopupSize {\n if (\n size === undefined\n || !Number.isFinite(size.width)\n || !Number.isFinite(size.height)\n || size.width <= 0\n || size.height <= 0\n ) {\n return popupDefaultSize(anchor)\n }\n return popupClamp(size)\n}\n\n/** The streaming output popup. Polls stdout/stderr deltas every ~200ms. */\nfunction RunPopup(props: {\n runId: string\n workspaceId: string\n commandName: string\n remoteHost?: string\n t: (key: string) => string\n anchor: 'corner' | 'button'\n popupSize?: QuickPopupSize\n sessionId: string\n onClose: () => void\n}): JSX.Element {\n const { runId, t, anchor, remoteHost, onClose } = props\n const [size, setSize] = useState<QuickPopupSize>(() => popupSizeOrDefault(props.popupSize, anchor))\n // Mirrors `size` for drag handlers: pointer events can arrive faster than\n // React commits, so pointerup must read the latest value, not the render's.\n const sizeRef = useRef<QuickPopupSize>(size)\n const sizeDragRef = useRef<{\n pointerId: number\n startX: number\n startY: number\n startW: number\n startH: number\n /** Which handle started the drag (controls which axes move). */\n mode: 'left' | 'height' | 'corner'\n } | null>(null)\n const [state, setState] = useState(() => ({\n status: 'running' as 'running' | 'exited',\n exitCode: null as number | null,\n signal: null as string | null,\n stdoutEnd: 0,\n stderrEnd: 0,\n stdout: '',\n stderr: '',\n stdoutLossy: false,\n stderrLossy: false,\n }))\n const [view, setView] = useState<'stdout' | 'stderr'>('stdout')\n const [autoFollow, setAutoFollow] = useState(true)\n const [killConfirm, setKillConfirm] = useState(false)\n const [pollError, setPollError] = useState<string | null>(null)\n const [closing, setClosing] = useState(false)\n const outputRef = useRef<HTMLDivElement | null>(null)\n const autoFollowRef = useRef(true)\n\n // Auto-follow: scroll the output pane to the bottom on new output while the\n // user has not scrolled up (autoFollow mirror keeps the effect cheap).\n useEffect(() => {\n autoFollowRef.current = autoFollow\n }, [autoFollow])\n useEffect(() => {\n if (!autoFollowRef.current) return\n const el = outputRef.current\n if (el === null) return\n el.scrollTop = el.scrollHeight\n }, [state.stdout, state.stderr, view])\n\n // Poll deltas; stop polling at exit (the run stays readable in memory).\n useEffect(() => {\n let cancelled = false\n let outEnd = 0\n let errEnd = 0\n const tick = async (): Promise<void> => {\n try {\n const poll = await quickApi.runPoll(runId, outEnd, errEnd)\n if (cancelled) return\n outEnd = poll.stdoutEnd\n errEnd = poll.stderrEnd\n setState((s) => ({\n status: poll.status,\n exitCode: poll.exitCode,\n signal: poll.signal,\n stdoutEnd: poll.stdoutEnd,\n stderrEnd: poll.stderrEnd,\n stdout: poll.stdoutLossy ? poll.stdoutDelta : s.stdout + poll.stdoutDelta,\n stderr: poll.stderrLossy ? poll.stderrDelta : s.stderr + poll.stderrDelta,\n stdoutLossy: poll.stdoutLossy,\n stderrLossy: poll.stderrLossy,\n }))\n setPollError(null)\n if (poll.status === 'exited') return\n timer = window.setTimeout(() => void tick(), 200)\n } catch (e) {\n if (cancelled) return\n setPollError(e instanceof Error ? e.message : String(e))\n timer = window.setTimeout(() => void tick(), 500)\n }\n }\n let timer: number\n void tick()\n return () => { cancelled = true; window.clearTimeout(timer) }\n }, [runId])\n\n const kill = async (): Promise<void> => {\n setClosing(true)\n try {\n await quickApi.runKill(runId)\n } finally {\n onClose()\n }\n }\n\n const close = (): void => {\n // Already exited: there is nothing left to terminate, close directly\n // instead of asking \"terminate this command?\".\n if (state.status === 'exited') {\n onClose()\n return\n }\n setKillConfirm(true)\n }\n\n const confirmClose = (): void => {\n void kill()\n }\n\n // ── drag-resize: pointer capture on the handles keeps moving/cancelling\n // events flowing even when the cursor leaves the handle or the window.\n // Which border is free to move depends on the anchor: the corner anchor\n // pins bottom+right (top border moves for height), the button anchor pins\n // top+right (bottom border moves for height).\n const heightFree: 'top' | 'bottom' = anchor === 'corner' ? 'top' : 'bottom'\n\n const onResizePointerDown = (e: React.PointerEvent<HTMLDivElement>): void => {\n e.preventDefault() // keep the handle from starting a text-selection gesture\n e.currentTarget.setPointerCapture(e.pointerId)\n sizeDragRef.current = {\n pointerId: e.pointerId,\n startX: e.clientX,\n startY: e.clientY,\n startW: sizeRef.current.width,\n startH: sizeRef.current.height,\n mode: e.currentTarget.dataset.resize as 'left' | 'height' | 'corner',\n }\n }\n\n const onResizePointerMove = (e: React.PointerEvent<HTMLDivElement>): void => {\n const drag = sizeDragRef.current\n if (drag === null || drag.pointerId !== e.pointerId) return\n const dx = e.clientX - drag.startX\n const dy = e.clientY - drag.startY\n const controlsW = drag.mode === 'left' || drag.mode === 'corner'\n const controlsH = drag.mode === 'height' || drag.mode === 'corner'\n const next = popupClamp({\n // Left border follows the cursor: dragging LEFT widens, RIGHT narrows.\n width: controlsW ? drag.startW - dx : drag.startW,\n // Top-free (corner): dragging UP grows. Bottom-free (button): DOWN grows.\n height: controlsH ? drag.startH + (heightFree === 'top' ? -dy : dy) : drag.startH,\n })\n sizeRef.current = next\n setSize(next)\n }\n\n const onResizePointerUp = (e: React.PointerEvent<HTMLDivElement>): void => {\n const drag = sizeDragRef.current\n if (drag === null || drag.pointerId !== e.pointerId) return\n sizeDragRef.current = null\n if (e.currentTarget.hasPointerCapture(e.pointerId)) {\n e.currentTarget.releasePointerCapture(e.pointerId)\n }\n // Persist best-effort: a failed save just means the next popup reopens at\n // the default size (or the last successfully saved one).\n void quickApi.settingsSetPopupSize(sizeRef.current).catch(() => undefined)\n }\n\n /** Pointer-handler trio for one resize handle (mode = which axes it drives). */\n const resizeProps = (mode: 'left' | 'height' | 'corner') => ({\n 'data-resize': mode,\n onPointerDown: onResizePointerDown,\n onPointerMove: onResizePointerMove,\n onPointerUp: onResizePointerUp,\n onPointerCancel: onResizePointerUp,\n })\n\n const active = state.status === 'running'\n const title = active ? `${props.commandName} · ${t('runStateRunning')}` : `${props.commandName} · ${t('runStateExited')} (${state.exitCode ?? '?'})`\n\n return (\n <div className={`qc-popup qc-popup-${anchor}`} role=\"dialog\" aria-label={title} style={{ width: size.width, height: size.height }}>\n <header className=\"qc-popup-head\">\n <span className=\"qc-popup-title\" title={title}>{title}</span>\n {remoteHost !== undefined && <span className=\"qc-popup-remote\" title={`SSH · ${remoteHost}`}>SSH</span>}\n <span className=\"qc-popup-cmd\">{props.commandName}</span>\n <button\n type=\"button\"\n className=\"qc-popup-icon qc-popup-kill\"\n disabled={!active || closing}\n title={t('runKill')}\n aria-label={t('runKill')}\n onClick={() => void kill()}\n >\n <IconStopFill16 size={14} />\n </button>\n <button\n type=\"button\"\n className=\"qc-popup-icon qc-popup-close\"\n title={t('runClose')}\n aria-label={t('runClose')}\n onClick={close}\n >\n <IconCloseOutline16 size={14} />\n </button>\n </header>\n\n {killConfirm && (\n <div className=\"qc-popup-confirm\">\n <span>{t('runKillConfirm')}</span>\n <button type=\"button\" className=\"qc-popup-btn\" onClick={confirmClose}>{t('runKill')}</button>\n <button type=\"button\" className=\"qc-popup-btn qc-popup-btn-plain\" onClick={() => setKillConfirm(false)}>\n {t('runClose')}\n </button>\n </div>\n )}\n\n <div className=\"qc-popup-tabs\">\n <button\n type=\"button\"\n className={'qc-popup-tab' + (view === 'stdout' ? ' qc-popup-tab-active' : '')}\n onClick={() => setView('stdout')}\n >\n {t('runStdout')}\n </button>\n <button\n type=\"button\"\n className={'qc-popup-tab' + (view === 'stderr' ? ' qc-popup-tab-active' : '')}\n onClick={() => setView('stderr')}\n >\n {t('runStderr')}\n </button>\n </div>\n\n <div\n ref={outputRef}\n className=\"qc-popup-output\"\n onScroll={(e) => {\n const el = e.currentTarget\n const atEnd = Math.abs(el.scrollHeight - el.scrollTop - el.clientHeight) < 24\n setAutoFollow(atEnd)\n }}\n >\n {pollError !== null && <p className=\"qc-popup-error\">{t('runPollFailed')}: {pollError}</p>}\n {view === 'stdout' ? (\n state.stdout === '' && !active ? <p className=\"qc-popup-empty\">{t('runEmpty')}</p>\n : <pre className=\"qc-popup-pre\">{state.stdout}</pre>\n ) : (\n state.stderr === '' && !active ? <p className=\"qc-popup-empty\">{t('runEmpty')}</p>\n : <pre className=\"qc-popup-pre\">{state.stderr}</pre>\n )}\n </div>\n\n <footer className=\"qc-popup-foot\">\n <span className=\"qc-popup-state\">\n {active ? t('runStateRunning') : `${t('runStateExited')} · ${t('runExitCode')} ${state.exitCode ?? '-'}${state.signal !== null ? ` · ${state.signal}` : ''}`}\n </span>\n </footer>\n\n {/* Resize handles: left border = width; top (corner anchor) / bottom\n (button anchor) border = height; free corner = diagonal grip. */}\n <div className=\"qc-popup-resize qc-popup-resize-left\" title={t('runResize')} {...resizeProps('left')} />\n {heightFree === 'top'\n ? <div className=\"qc-popup-resize qc-popup-resize-top\" title={t('runResize')} {...resizeProps('height')} />\n : <div className=\"qc-popup-resize qc-popup-resize-bottom\" title={t('runResize')} {...resizeProps('height')} />}\n {heightFree === 'top' ? (\n <div className=\"qc-popup-resize qc-popup-resize-corner qc-popup-resize-corner-tl\" title={t('runResize')} {...resizeProps('corner')}>\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" aria-hidden=\"true\">\n <path\n d=\"M9 9 3 3 M9 5.5 5.5 3 M5.5 9 3 5.5\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.4\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </div>\n ) : (\n <div className=\"qc-popup-resize qc-popup-resize-corner qc-popup-resize-corner-bl\" title={t('runResize')} {...resizeProps('corner')}>\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" aria-hidden=\"true\">\n <path\n d=\"M9 3 3 9 M9 6.5 6.5 9 M5.5 3 3 5.5\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.4\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </div>\n )}\n </div>\n )\n}\n","/**\n * Quick-commands settings card (设置 → 插件配置): an expandable plugin card —\n * header (title + description + chevron) collapsed by default, body reveals\n * the workspace × command-set editor and the popup anchor preference. Body\n * edits instant-save per change; partial rows (empty name/command) persist\n * while the user types.\n *\n * Mirrors the official PluginCard disclosure pattern (header button with\n * aria-expanded, rotate-180 chevron, body separated by a top border).\n */\nimport { useEffect, useRef, useState } from 'react'\nimport {\n IconChevronDownOutline14,\n IconPlusOutline16,\n IconTrashOutline16,\n} from '@deepseek-ai/dsh-client-ui-primitives'\nimport { quickApi } from './api'\nimport type { QuickRosterEntry, QuickCommandsSettings } from '../shared/contract'\n\ninterface Props {\n t: (key: string) => string\n}\n\ninterface RosterState {\n loaded: boolean\n entries: QuickRosterEntry[]\n anchor: QuickCommandsSettings['popupAnchor']\n}\n\ntype CommandDraft = { name: string; command: string }\n\nexport function QuickCommandsSettingsCard({ t }: Props): JSX.Element {\n // Card-local disclosure state (a reading gesture, like the official card).\n const [open, setOpen] = useState(false)\n const [state, setState] = useState<RosterState>({ loaded: false, entries: [], anchor: 'corner' })\n const [error, setError] = useState<string | null>(null)\n // Guard concurrent saves: the last write wins via a monotonically bumped token.\n const saveToken = useRef(0)\n\n const load = async (): Promise<void> => {\n try {\n const [entries, settings] = await Promise.all([quickApi.workspacesList(), quickApi.settingsGet()])\n setState({ loaded: true, entries, anchor: settings.popupAnchor })\n setError(null)\n } catch (e) {\n setState((s) => ({ ...s, loaded: true }))\n setError(e instanceof Error ? e.message : String(e))\n }\n }\n\n useEffect(() => {\n void load()\n }, [])\n\n /** Instant-save the workspace's whole command set; optimistic UI. */\n const saveCommands = async (workspaceId: string, commands: CommandDraft[]): Promise<void> => {\n const token = ++saveToken.current\n try {\n const next = await quickApi.settingsSetCommands(workspaceId, commands)\n if (token !== saveToken.current) return\n setState((s) => ({\n ...s,\n anchor: next.popupAnchor,\n entries: s.entries.map((e) => (e.workspaceId === workspaceId ? { ...e, commands } : e)),\n }))\n setError(null)\n } catch (e) {\n setError(e instanceof Error ? e.message : String(e))\n // Re-sync to server truth on failure.\n void load()\n }\n }\n\n /** Compute the next command list for a workspace, THEN save + update state. */\n const mutateCommands = (workspaceId: string, next: (current: CommandDraft[]) => CommandDraft[]): void => {\n const entry = state.entries.find((e) => e.workspaceId === workspaceId)\n if (entry === undefined) return\n const commands = next(entry.commands)\n // Optimistic update first (row stays visible immediately), then persist.\n setState((s) => ({\n ...s,\n entries: s.entries.map((e) => (e.workspaceId === workspaceId ? { ...e, commands } : e)),\n }))\n void saveCommands(workspaceId, commands)\n }\n\n const patchEntry = (workspaceId: string, index: number, field: 'name' | 'command', value: string): void => {\n mutateCommands(workspaceId, (current) =>\n current.map((c, i) => (i === index ? { ...c, [field]: value } : c)),\n )\n }\n\n const addCommand = (workspaceId: string): void => {\n mutateCommands(workspaceId, (current) => [...current, { name: '', command: '' }])\n }\n\n const removeCommand = (workspaceId: string, index: number): void => {\n mutateCommands(workspaceId, (current) => current.filter((_, i) => i !== index))\n }\n\n const setAnchor = async (anchor: QuickCommandsSettings['popupAnchor']): Promise<void> => {\n setState((s) => ({ ...s, anchor }))\n try {\n const next = await quickApi.settingsSetAnchor(anchor)\n setState((s) => ({ ...s, anchor: next.popupAnchor }))\n } catch (e) {\n setError(e instanceof Error ? e.message : String(e))\n void load()\n }\n }\n\n const title = t('cardTitle')\n const description = t('cardDescription')\n\n return (\n <li className={'qc-settings-card' + (open ? ' qc-settings-card-open' : '')}>\n <button\n type=\"button\"\n className=\"qc-settings-header\"\n aria-expanded={open}\n aria-label={`${t('collapseOrExpand')}: ${title}`}\n onClick={() => setOpen((v) => !v)}\n >\n <span className=\"qc-settings-head-text\">\n <span className=\"qc-settings-name\">{title}</span>\n <span className=\"qc-settings-description\">{description}</span>\n </span>\n <IconChevronDownOutline14 className={'qc-settings-chevron' + (open ? ' qc-settings-chevron-open' : '')} />\n </button>\n\n {open && (\n <div className=\"qc-settings-body\">\n <p className=\"qc-settings-intro\">{t('cardIntro')}</p>\n\n <div className=\"qc-settings-anchor\">\n <span className=\"qc-settings-anchor-label\">{t('cardAnchor')}</span>\n <label className=\"qc-settings-radio\">\n <input\n type=\"radio\"\n name=\"qc-anchor\"\n checked={state.anchor === 'corner'}\n onChange={() => void setAnchor('corner')}\n />\n {t('cardAnchorCorner')}\n </label>\n <label className=\"qc-settings-radio\">\n <input\n type=\"radio\"\n name=\"qc-anchor\"\n checked={state.anchor === 'button'}\n onChange={() => void setAnchor('button')}\n />\n {t('cardAnchorButton')}\n </label>\n </div>\n\n {error !== null && <div className=\"qc-settings-error\">{t('cardSaveError')}: {error}</div>}\n\n {state.loaded && state.entries.length === 0 && (\n <p className=\"qc-settings-empty\">{t('cardNoWorkspaces')}</p>\n )}\n\n {state.entries.map((entry) => (\n <section key={entry.workspaceId} className=\"qc-settings-workspace\">\n <header className=\"qc-settings-workspace-head\">\n <span className=\"qc-settings-workspace-title\">{entry.title || entry.path || entry.workspaceId}</span>\n <span className=\"qc-settings-workspace-path\">{entry.path}</span>\n </header>\n {entry.commands.length === 0 && (\n <p className=\"qc-settings-no-commands\">{t('cardNoCommands')}</p>\n )}\n {entry.commands.map((command, index) => (\n <div key={`${entry.workspaceId}-${index}`} className=\"qc-settings-command\">\n <input\n className=\"qc-settings-field qc-settings-field-name\"\n placeholder={t('cardName')}\n value={command.name}\n aria-label={t('cardName')}\n onChange={(e) => patchEntry(entry.workspaceId, index, 'name', e.target.value)}\n />\n <input\n className=\"qc-settings-field qc-settings-field-command\"\n placeholder={t('cardCommand')}\n value={command.command}\n aria-label={t('cardCommand')}\n onChange={(e) => patchEntry(entry.workspaceId, index, 'command', e.target.value)}\n />\n <button\n type=\"button\"\n className=\"qc-settings-icon-btn\"\n aria-label={t('cardRemove')}\n title={t('cardRemove')}\n onClick={() => removeCommand(entry.workspaceId, index)}\n >\n <IconTrashOutline16 size={14} />\n </button>\n </div>\n ))}\n <button type=\"button\" className=\"qc-settings-add-btn\" onClick={() => addCommand(entry.workspaceId)}>\n <IconPlusOutline16 size={14} />\n {t('cardAddCommand')}\n </button>\n </section>\n ))}\n </div>\n )}\n </li>\n )\n}\n","/**\n * Client dictionary namespace for @fonlan/dsh-quick-commands.\n */\nexport const LOCALE_NS = 'quick-commands'\n\nexport const zh = {\n // header button\n buttonTitle: '快速命令',\n buttonTooltip: '快速命令(当前工作区)',\n noWorkspace: '当前会话不在任何已配置的工作区中',\n noCommands: '当前工作区未配置命令',\n openSettings: '去设置配置命令',\n\n // command popup menu\n menuTitle: '快速命令',\n menuEmpty: '(无命令)',\n\n // run popup\n runTitle: '运行中',\n runExited: '已退出',\n runExitCode: '退出码',\n runSignal: '信号',\n runStdout: '输出 (stdout)',\n runStderr: '错误 (stderr)',\n runEmpty: '(无输出)',\n runCommand: '命令',\n runCwd: '工作区',\n runStateRunning: '运行中…',\n runStateExited: '已退出',\n runKill: '终止',\n runClose: '关闭',\n runResize: '拖动调整弹窗大小',\n runKillConfirm: '终止后输出弹窗将关闭,确定终止该命令?',\n runBusy: '该工作区已有命令在运行',\n runStartFailed: '命令启动失败',\n runPollFailed: '输出读取失败',\n runLost: '运行记录已失效',\n runNotFound: '工作区或命令未找到',\n\n // settings card\n cardTitle: '快速命令',\n cardDescription: '为每个工作区配置常用命令,会话头部一键运行',\n collapseOrExpand: '展开 / 收起',\n cardIntro: '为每个工作区配置常用命令,运行于工作区目录。支持占位符:{workspace} 工作区路径,{cwd} 会话目录,{title} 工作区标题。',\n cardWorkspaces: '工作区',\n cardNoWorkspaces: '没有工作区。请在侧边栏先添加工作区。',\n cardCommands: '命令',\n cardNoCommands: '未配置命令',\n cardAddCommand: '添加命令',\n cardName: '名称',\n cardCommand: '命令',\n cardRemove: '删除',\n cardSaveError: '保存失败',\n cardAnchor: '输出弹窗位置',\n cardAnchorCorner: '右下角',\n cardAnchorButton: '锚定按钮下方',\n}\n\nexport const en = {\n buttonTitle: 'Quick commands',\n buttonTooltip: 'Quick commands (current workspace)',\n noWorkspace: 'This session is not in any configured workspace',\n noCommands: 'No commands configured for this workspace',\n openSettings: 'Configure commands in Settings',\n\n menuTitle: 'Quick commands',\n menuEmpty: '(no commands)',\n\n runTitle: 'Running',\n runExited: 'Exited',\n runExitCode: 'Exit code',\n runSignal: 'Signal',\n runStdout: 'Output (stdout)',\n runStderr: 'Errors (stderr)',\n runEmpty: '(no output)',\n runCommand: 'Command',\n runCwd: 'Workspace',\n runStateRunning: 'Running…',\n runStateExited: 'Exited',\n runKill: 'Terminate',\n runClose: 'Close',\n runResize: 'Drag to resize the popup',\n runKillConfirm: 'Terminating closes the output popup. Terminate this command?',\n runBusy: 'A command is already running for this workspace',\n runStartFailed: 'Command failed to start',\n runPollFailed: 'Failed to read output',\n runLost: 'Run record expired',\n runNotFound: 'Workspace or command not found',\n\n cardTitle: 'Quick commands',\n cardDescription: 'Configure common commands per workspace, run them from the session header',\n collapseOrExpand: 'Expand / collapse',\n cardIntro: 'Configure common commands per workspace; they run in the workspace directory. Placeholders: {workspace} workspace path, {cwd} session cwd, {title} workspace title.',\n cardWorkspaces: 'Workspaces',\n cardNoWorkspaces: 'No workspaces. Add one in the sidebar first.',\n cardCommands: 'Commands',\n cardNoCommands: 'No commands configured',\n cardAddCommand: 'Add command',\n cardName: 'Name',\n cardCommand: 'Command',\n cardRemove: 'Remove',\n cardSaveError: 'Save failed',\n cardAnchor: 'Output popup position',\n cardAnchorCorner: 'Bottom right',\n cardAnchorButton: 'Anchored at button',\n}\n","/**\n * @fonlan/dsh-quick-commands client half.\n *\n * Registers:\n * - `settings.plugin.item` card (keyed by the `quick-commands` settings\n * namespace) — per-workspace command CRUD + popup anchor preference.\n * - `conversation.session.header.utilities` entry (id `quick-commands`,\n * order -1 → left of the Session log pill) — the quick-run ▶ button, command\n * menu, and live output popup.\n */\nimport type { Context } from '@deepseek-ai/cordis'\ntype ClientContext = Context\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-settings-plugins/client'\nimport { QuickCommandsHeaderAction } from './header'\nimport { QuickCommandsSettingsCard } from './settings-card'\nimport { LOCALE_NS, zh, en } from './locales'\nimport './quick-commands.css'\n\n/** Slots face (local, erased at build). */\ninterface Slots {\n inject(name: string, callback: () => unknown): unknown\n register(\n def: { name: string; key?: string; id?: string; order?: number; locale?: string },\n component: unknown,\n ): unknown\n}\n\n/** Services required before mounting (provided by the client runtime). */\nexport const inject = ['slots', 'locale']\n\n/** Client plugin body. */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(LOCALE_NS, { zh, en }), 'quick-commands: dictionaries')\n const t = ctx.locale.bind(LOCALE_NS)\n const slots = (ctx as unknown as { slots: Slots }).slots\n\n // The plugin's own Settings Card (设置 → 插件配置) rides the `quick-commands`\n // settings namespace: registering into the keyed `settings.plugin.item` slot\n // with the namespace string makes the configurable-plugins tab dispatch the\n // card next to the built-in ones (bash / agent loop / web search).\n slots.inject('settings.plugin.item', () =>\n slots.register({\n name: 'settings.plugin.item',\n key: LOCALE_NS,\n locale: LOCALE_NS,\n }, QuickCommandsSettingsCard as never),\n )\n\n // Session-header util: order -1 renders LEFT of the Session log pill\n // (session-log-download registers with default order 0).\n slots.inject('conversation.session.header.utilities', () =>\n slots.register({\n name: 'conversation.session.header.utilities',\n id: 'quick-commands',\n order: -1,\n locale: LOCALE_NS,\n }, QuickCommandsHeaderAction as never),\n )\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fonlan/dsh-quick-commands",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "DSH web plugin: per-workspace quick commands. Configure named shell commands per DSH workspace, run one from the session header with live stdout/stderr in a floating popup.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
package/src/client/header.tsx
CHANGED
|
@@ -142,7 +142,7 @@ export function QuickCommandsHeaderAction(props: HeaderProps): JSX.Element {
|
|
|
142
142
|
disabled={workspace === undefined}
|
|
143
143
|
onClick={() => { popover.menuOpen ? closeMenu() : openMenu() }}
|
|
144
144
|
>
|
|
145
|
-
<IconPlayOutline16 size={
|
|
145
|
+
<IconPlayOutline16 size={15} />
|
|
146
146
|
</button>
|
|
147
147
|
|
|
148
148
|
{popover.menuOpen && workspace !== undefined && (
|
|
@@ -11,21 +11,24 @@
|
|
|
11
11
|
display: inline-flex;
|
|
12
12
|
align-items: center;
|
|
13
13
|
}
|
|
14
|
+
/* Sized to DSH built-in header buttons (e.g. the open-in-app split button on
|
|
15
|
+
the left): 26px tall pill, 13px radius, hairline border-l4, hover uses the
|
|
16
|
+
shared --dsw-alias-interactive-bg-hover token. */
|
|
14
17
|
.qc-head-btn {
|
|
15
18
|
display: inline-flex;
|
|
16
19
|
align-items: center;
|
|
17
20
|
justify-content: center;
|
|
18
|
-
width:
|
|
19
|
-
height:
|
|
20
|
-
border:
|
|
21
|
-
border-radius:
|
|
21
|
+
width: 26px;
|
|
22
|
+
height: 26px;
|
|
23
|
+
border: 0.5px solid var(--dsw-alias-border-l4, rgba(128, 128, 128, 0.35));
|
|
24
|
+
border-radius: 13px;
|
|
22
25
|
background: transparent;
|
|
23
26
|
color: var(--dsw-alias-label-primary, #f2f2f2);
|
|
24
27
|
cursor: pointer;
|
|
25
28
|
padding: 0;
|
|
26
29
|
}
|
|
27
30
|
.qc-head-btn:hover:not(:disabled) {
|
|
28
|
-
background: color-mix(in srgb, var(--dsw-alias-label-primary, #f2f2f2) 10%, transparent);
|
|
31
|
+
background: var(--dsw-alias-interactive-bg-hover, color-mix(in srgb, var(--dsw-alias-label-primary, #f2f2f2) 10%, transparent));
|
|
29
32
|
}
|
|
30
33
|
.qc-head-btn:disabled {
|
|
31
34
|
color: var(--dsw-alias-label-dimmed, #666);
|