dsh-plugin-shop 0.4.12 → 0.4.14

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/README.md CHANGED
@@ -52,9 +52,9 @@ install them once with `npm install -g @deepseek-ai/dsh pnpm` and verify with
52
52
  # holds back very recent releases, so a bare `add dsh-plugin-shop` can hand
53
53
  # you an older version for a while. Pin the current release — refresh it
54
54
  # with `npm view dsh-plugin-shop version`.
55
- dsh plugin --profile web add dsh-plugin-shop@0.4.12
55
+ dsh plugin --profile web add dsh-plugin-shop@0.4.14
56
56
  # or straight through npx, nothing installed:
57
- npx -y @deepseek-ai/dsh plugin --profile web add dsh-plugin-shop@0.4.12
57
+ npx -y @deepseek-ai/dsh plugin --profile web add dsh-plugin-shop@0.4.14
58
58
  ```
59
59
 
60
60
  Replace `web` with your profile if you use another one. Then restart `dsh` once — a
@@ -84,7 +84,7 @@ ls -1 "${DSH_HOME:-$HOME/.dsh}/profiles" | grep -v '^node_modules$'
84
84
  cooldown, and deterministic installs are the point of the agent path.
85
85
 
86
86
  ```sh
87
- dsh plugin --profile <profile> add dsh-plugin-shop@0.4.12
87
+ dsh plugin --profile <profile> add dsh-plugin-shop@0.4.14
88
88
  ```
89
89
 
90
90
  **3. Verify — do not skip this.** A zero exit from step 2 means pnpm resolved the
package/lib/client.js CHANGED
@@ -4301,7 +4301,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4301
4301
  "capabilities": array(string())
4302
4302
  })]).optional(),
4303
4303
  "source": union([literal("npm"), literal("github")]),
4304
- "repo": union([_undefined(), string()]).optional()
4304
+ "repo": union([_undefined(), string()]).optional(),
4305
+ "subdir": union([_undefined(), string()]).optional()
4305
4306
  })),
4306
4307
  "denied": array(object({
4307
4308
  "name": string(),
@@ -4313,7 +4314,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4313
4314
  "name": string(),
4314
4315
  "installed": string(),
4315
4316
  "latest": string(),
4316
- "outdated": boolean()
4317
+ "outdated": boolean(),
4318
+ "enabled": boolean()
4317
4319
  }));
4318
4320
  const dsh_plugin_shop_shop_installStart_parameter_0$schema = object({
4319
4321
  "name": string(),
@@ -4414,7 +4416,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4414
4416
  },
4415
4417
  sourceLocation: {
4416
4418
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4417
- "line": 270,
4419
+ "line": 275,
4418
4420
  "column": 9
4419
4421
  }
4420
4422
  },
@@ -4432,7 +4434,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4432
4434
  },
4433
4435
  sourceLocation: {
4434
4436
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4435
- "line": 374,
4437
+ "line": 380,
4436
4438
  "column": 9
4437
4439
  }
4438
4440
  },
@@ -4460,7 +4462,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4460
4462
  },
4461
4463
  sourceLocation: {
4462
4464
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4463
- "line": 296,
4465
+ "line": 301,
4464
4466
  "column": 9
4465
4467
  }
4466
4468
  },
@@ -4487,7 +4489,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4487
4489
  },
4488
4490
  sourceLocation: {
4489
4491
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4490
- "line": 363,
4492
+ "line": 369,
4491
4493
  "column": 3
4492
4494
  }
4493
4495
  },
@@ -4505,7 +4507,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4505
4507
  },
4506
4508
  sourceLocation: {
4507
4509
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4508
- "line": 469,
4510
+ "line": 491,
4509
4511
  "column": 9
4510
4512
  }
4511
4513
  },
@@ -4532,7 +4534,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4532
4534
  },
4533
4535
  sourceLocation: {
4534
4536
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4535
- "line": 239,
4537
+ "line": 241,
4536
4538
  "column": 3
4537
4539
  }
4538
4540
  },
@@ -4560,7 +4562,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4560
4562
  },
4561
4563
  sourceLocation: {
4562
4564
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4563
- "line": 429,
4565
+ "line": 451,
4564
4566
  "column": 9
4565
4567
  }
4566
4568
  },
@@ -4587,7 +4589,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4587
4589
  },
4588
4590
  sourceLocation: {
4589
4591
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4590
- "line": 518,
4592
+ "line": 540,
4591
4593
  "column": 9
4592
4594
  }
4593
4595
  },
@@ -4605,7 +4607,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4605
4607
  },
4606
4608
  sourceLocation: {
4607
4609
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4608
- "line": 503,
4610
+ "line": 525,
4609
4611
  "column": 9
4610
4612
  }
4611
4613
  }
@@ -4641,6 +4643,15 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4641
4643
  function displayVersion(entry) {
4642
4644
  return entry.source === "github" ? entry.version.slice(0, 7) : entry.version;
4643
4645
  }
4646
+ /**
4647
+ * Whether the license value is the npm idiom `SEE LICENSE IN <file>` — a
4648
+ * valid SPDX form meaning "custom license, text ships in that file". The
4649
+ * catalog keeps the author's verbatim value; the shelf renders it as a
4650
+ * localized "Custom license" label. Everything else renders verbatim.
4651
+ */
4652
+ function isCustomLicense(license) {
4653
+ return license !== null && /^SEE LICENSE IN /i.test(license);
4654
+ }
4644
4655
  /** §7.2 once-per-second poll cadence, as a named constant. */
4645
4656
  const INSTALL_POLL_MS = 1e3;
4646
4657
  /** How long the check-update button reports "up to date" after a re-check
@@ -4819,6 +4830,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4819
4830
  showing: "显示前 {shown} 个(共 {total})· 继续滚动加载",
4820
4831
  repository: "仓库",
4821
4832
  license: "许可",
4833
+ customLicense: "自定义许可",
4822
4834
  reviewedVersionLine: "已审核 v{reviewed} / 当前 v{current} 未审核",
4823
4835
  deniedCode: "已拒绝",
4824
4836
  notInCatalogCode: "不在目录中",
@@ -4890,6 +4902,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4890
4902
  showing: "showing first {shown} of {total} — scroll to load more",
4891
4903
  repository: "Repository",
4892
4904
  license: "License",
4905
+ customLicense: "Custom license",
4893
4906
  reviewedVersionLine: "reviewed v{reviewed} / current v{current} unreviewed",
4894
4907
  deniedCode: "Denied",
4895
4908
  notInCatalogCode: "Not in the catalog",
@@ -5073,7 +5086,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5073
5086
  }
5074
5087
  //#endregion
5075
5088
  //#region \0dsh-plugin-shop-css:e3675a89
5076
- const css = "/* Shop tab styles — a market shelf inside the settings surface. The visual\n * language is the dsh theme's alias tokens ONLY (the four tokens the previous\n * styles referenced that do not exist — bg-layer-3, interactive-bg-hover,\n * state-error, state-success — are corrected to bg-layer-2 and the\n * state-*-primary set). The signature is the category spine: one HUE per\n * category (six fixed hues, not the brand token — the brand resolves to\n * near-black in the light theme, so six brand opacities read as six shades\n * of gray), with the category name as the cover label in the same hue. Class names are mapped to content-derived names at bundle time\n * (tsdown.s8583e3_client.s942a79_config.s5f67aa_ts) and stubbed in tests; only the keys are relied\n * on.\n *\n * One token rule the theme forces: the background LAYERS do not carry\n * contrast. In the light theme bg-base, bg-layer-1, bg-layer-2 and\n * bg-layer-3 all resolve to the same white; only the dark theme spreads them\n * (950/875/850/800). So a layer-2 fill on a layer-1 ground is invisible for\n * half of all users, and anything whose fill is its ONLY affordance — the\n * loading skeleton, the borderless capability chips — derives its fill from\n * label-primary instead, which inverts with the theme by construction.\n * Elements that also carry a border or text may keep a layer fill.\n * `css-tokens.s8583e3_client.sfd9d8a_spec.s5f67aa_ts` pins this. */\n\n.s255e5a_panel {\n display: flex;\n flex-direction: column;\n gap: 14px;\n /* The shelf never scrolls sideways: a card too wide for its track wraps\n * its badges rather than widening the panel. */\n overflow-x: hidden;\n}\n\n.s44a3ec_stateLine {\n margin: 0;\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n/* Visually hidden but screen-reader readable (the loading copy while the\n * skeleton stands in visually). */\n.s3a0ece_srOnly {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n border: 0;\n clip: rect(0 0 0 0);\n clip-path: inset(50%);\n overflow: hidden;\n white-space: nowrap;\n}\n\n/* The loading shelf: ghost cards with the same geometry as the real grid, so\n * the swap to content is a same-shape handoff instead of a jump. */\n.se0c3b4_skeletonGrid {\n display: grid;\n grid-template-columns: 1fr;\n gap: 8px;\n}\n\n.s520416_skeletonCard {\n position: relative;\n display: flex;\n flex-direction: column;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n overflow: hidden;\n}\n\n/* The shimmer sweep: one gradient band travels across each ghost card while\n * the bars breathe, so the loading state reads as \"the shelf is scanning\". */\n.s520416_skeletonCard::after {\n content: '';\n position: absolute;\n inset: 0;\n background: linear-gradient(\n 100deg,\n transparent 25%,\n color-mix(in srgb, var(--dsw-alias-label-primary) 14%, transparent) 50%,\n transparent 75%\n );\n transform: translateX(-100%);\n animation: dshShopSkeletonSweep 1.4s ease-in-out infinite;\n pointer-events: none;\n}\n\n@keyframes dshShopSkeletonSweep {\n to { transform: translateX(100%); }\n}\n\n.s8c7d6f_skeletonName {\n width: 55%;\n height: 14px;\n margin: 10px 12px 0;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n.sf5179e_skeletonSummary {\n width: 92%;\n height: 12px;\n margin: 10px 12px 0;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n.sbb0d25_skeletonSummaryShort {\n width: 68%;\n height: 12px;\n margin: 6px 12px 10px;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n/* The ghost of the action line, right where the real buttons land. */\n.s96e910_skeletonActions {\n display: block;\n width: 84px;\n height: 24px;\n margin: 0 12px 10px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n.s520416_skeletonCard span {\n animation: dshShopSkeletonPulse 1.6s ease-in-out infinite;\n}\n\n@keyframes dshShopSkeletonPulse {\n 0%, 100% { opacity: 0.5; }\n 50% { opacity: 1; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .s520416_skeletonCard span { animation: none; opacity: 0.75; }\n .s520416_skeletonCard::after { content: none; }\n}\n\n.s73242a_actionButton {\n align-self: flex-start;\n padding: 5px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n cursor: pointer;\n}\n\n.s73242a_actionButton:hover {\n border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, var(--dsw-alias-border-l2));\n color: var(--dsw-alias-brand-primary);\n}\n\n.s73242a_actionButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s646509_toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.s745e3c_toolbarRight {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n/* The shop's own version, right of the search box: `v0.4.4`, plus the\n * update button when the version check found a newer release. */\n.s4fe61f_versionBlock {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.sb9b924_versionText {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sa04455_updateSelfButton {\n padding: 2px 10px;\n border: 1px solid var(--dsw-alias-brand-primary);\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);\n color: var(--dsw-alias-brand-primary);\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n}\n\n.sa04455_updateSelfButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 20%, transparent);\n}\n\n.sa04455_updateSelfButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The on-demand re-check next to the version number: a quiet pill; the\n * border and text carry it in the light theme where bg layers are white. */\n.s614a38_checkUpdateButton {\n padding: 2px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n}\n\n.s614a38_checkUpdateButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n color: var(--dsw-alias-label-primary);\n}\n\n.s614a38_checkUpdateButton:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n.s614a38_checkUpdateButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The project's GitHub mark, right of the version row: a quiet icon link.\n * The octocat carries itself — color shift on hover, no text, no border. */\n.sa987f7_githubLink {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 3px;\n border-radius: 6px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sa987f7_githubLink:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-layer-2);\n}\n\n.sa987f7_githubLink:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The self-update's expanded states (running log / done + restart /\n * failure detail) sit under the toolbar, above the category bar. */\n.sb81ba5_selfUpdatePanel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 8px;\n}\n\n.s232ec4_searchInput {\n width: min(280px, 100%);\n padding: 6px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n.s232ec4_searchInput:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s1555aa_staleBlock {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.s3946f1_staleBadge {\n padding: 2px 8px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 40%, transparent);\n color: var(--dsw-alias-state-warn-primary);\n font-size: 12px;\n}\n\n.sb2c859_categoryBar {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-top: 6px;\n}\n\n.s26d58e_categoryButton {\n padding: 3px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n}\n\n.s26d58e_categoryButton:hover {\n border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, var(--dsw-alias-border-l2));\n color: var(--dsw-alias-brand-primary);\n}\n\n.s5f68c4_categoryButtonOn {\n border-color: var(--dsw-alias-brand-primary);\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);\n color: var(--dsw-alias-brand-primary);\n font-weight: 600;\n}\n\n.s26d58e_categoryButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s668774_catalogHeading {\n margin: 2px 0 0;\n font-size: 16px;\n font-weight: 600;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-primary);\n}\n\n.sccc4d1_catalogStats {\n margin: 6px 0 0;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s6de1ab_emptyLine {\n margin: 0;\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n/* The shelf: a responsive grid of cards. */\n/* Single-line rows: one full-width column, one plugin per line. */\n.sf657f5_cards {\n display: grid;\n grid-template-columns: 1fr;\n max-width: 100%;\n gap: 8px;\n list-style: none;\n margin: 0;\n padding: 0;\n animation: dshShopCardsIn 220ms ease;\n}\n\n@keyframes dshShopCardsIn {\n from { opacity: 0; transform: translateY(3px); }\n to { opacity: 1; transform: none; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .sf657f5_cards { animation: none; }\n}\n\n.s65f383_card {\n position: relative;\n display: flex;\n flex-direction: column;\n /* A grid item's min-width is auto by default: its content would widen the\n * 1fr track past the panel and overflow horizontally. */\n min-width: 0;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n overflow: hidden;\n transition: border-color 120ms ease, transform 120ms ease, box-shadow 120ms ease;\n /* Off-screen cards skip layout and paint; the placeholder keeps scrollbar\n * estimation sane before the browser has measured the real height. */\n content-visibility: auto;\n contain-intrinsic-size: auto 140px;\n}\n\n/* The sentinel that triggers the next shelf batch: zero visual footprint. */\n.sd7ee01_cardsSentry {\n height: 1px;\n margin: 0;\n padding: 0;\n}\n\n.s705cc6_showingLine {\n margin: 0;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s65f383_card:hover {\n border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, var(--dsw-alias-border-l1));\n transform: translateY(-1px);\n box-shadow: 0 2px 8px color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .s65f383_card { transition: none; }\n .s65f383_card:hover { transform: none; }\n}\n\n/* The signature: a spine and cover in the category's own hue. The hues are\n * fixed mid-bright colors that read on both themes; `other` deliberately\n * stays a neutral gray so the fallback category does not compete. */\n.s4ce25d_cardSpine {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: 3px;\n background: var(--spine-hue, #8B8E96);\n}\n\n.s65f383_card[data-category='tool'] { --spine-hue: #4C8DFF; }\n.s65f383_card[data-category='provider'] { --spine-hue: #A78BFA; }\n.s65f383_card[data-category='ui'] { --spine-hue: #2DD4BF; }\n.s65f383_card[data-category='workflow'] { --spine-hue: #F59E0B; }\n.s65f383_card[data-category='integration'] { --spine-hue: #34D399; }\n.s65f383_card[data-category='other'] { --spine-hue: #8B8E96; }\n\n/* The category sits in the badge row as a quiet chip in the category's own\n * hue — the border carries it where the light theme's white layers cannot\n * (the same lesson as the check button). */\n.s9c8eaa_categoryBadge {\n padding: 1px 7px;\n border: 1px solid color-mix(in srgb, var(--spine-hue, #8B8E96) 45%, transparent);\n border-radius: 999px;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--spine-hue, #8B8E96);\n white-space: nowrap;\n}\n\n.sa97234_entryHeader {\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 12px;\n min-width: 0;\n padding: 8px 12px 2px;\n border: none;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n\n.sa97234_entryHeader:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n\n/* The name fills the row and truncates rather than wraps; the badges stay\n * intact beside it. */\n.sc3f149_name {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 13px;\n font-weight: 600;\n flex: 1 1 auto;\n min-width: 0;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: var(--dsw-alias-label-primary);\n}\n\n.s513026_badges {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n/* The action line: buttons side by side; an active install/uninstall flow\n * (gate, log, notices) takes the full width below them. */\n.s9b5048_cardActions {\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n padding: 8px 12px 10px;\n}\n\n.s9b5048_cardActions > .sc4f2ac_gate,\n.s9b5048_cardActions > .s4d374c_installPanel,\n.s9b5048_cardActions > .sf53ca0_installedActions {\n flex-basis: 100%;\n}\n\n/* The entry's version, always visible on the card (the expanded detail\n * repeats it in the version row) — the same quiet mono as the toolbar. */\n.sc6ec6e_cardVersion {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* The octocat marking a github-sourced entry, beside its short commit. */\n.s71f10f_sourceBadge {\n display: inline-flex;\n align-items: center;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s5e82cb_tierBadge {\n padding: 1px 7px;\n border-radius: 999px;\n font-size: 11px;\n line-height: 1.6;\n}\n\n.s5e82cb_tierBadge[data-tier='verified'] {\n color: var(--dsw-alias-state-success-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-success-primary) 40%, transparent);\n}\n\n.s5e82cb_tierBadge[data-tier='verified-stale'] {\n color: var(--dsw-alias-state-warn-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 40%, transparent);\n}\n\n.s5e82cb_tierBadge[data-tier='community'] {\n color: var(--dsw-alias-label-secondary);\n background: var(--dsw-alias-bg-layer-2);\n border: 1px solid var(--dsw-alias-border-l2);\n}\n\n.sff43b4_starsBadge {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s73f7fb_chevron, .s61393c_chevronOpen {\n color: var(--dsw-alias-label-secondary);\n transition: transform 120ms ease;\n}\n\n.s61393c_chevronOpen { transform: rotate(180deg); }\n\n@media (prefers-reduced-motion: reduce) {\n .s73f7fb_chevron, .s61393c_chevronOpen { transition: none; }\n}\n\n.s820ebf_body {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 0 12px;\n}\n\n.sb8fbc8_summary {\n margin: 0;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n.se507db_summaryZh {\n margin: -4px 0 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-secondary);\n display: -webkit-box;\n -webkit-line-clamp: 1;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n/* Expanded cards show the full summary: the clamp is lifted, never raised —\n * a higher number would still truncate the author's text, just later. */\n.s10baf0_summaryExpanded {\n -webkit-line-clamp: unset;\n display: block;\n}\n\n.sb9dba9_summaryZhExpanded {\n -webkit-line-clamp: unset;\n display: block;\n}\n\n.s9d2029_capabilitiesBlock {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.s3bdbd5_capabilitiesNote {\n margin: 0;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s441702_capabilities {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.s441702_capabilities li {\n padding: 1px 6px;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 6%, transparent);\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sde3f0c_detail {\n border-top: 1px solid var(--dsw-alias-border-l1);\n padding-top: 8px;\n}\n\n.se98179_detailRows {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin: 0;\n}\n\n.sfb4d2f_detailRow {\n display: flex;\n justify-content: space-between;\n gap: 12px;\n font-size: 12px;\n}\n\n.sfb4d2f_detailRow dt {\n color: var(--dsw-alias-label-secondary);\n}\n\n.sfb4d2f_detailRow dd {\n margin: 0;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n overflow-wrap: anywhere;\n text-align: right;\n color: var(--dsw-alias-label-primary);\n}\n\n.sfb4d2f_detailRow a {\n color: var(--dsw-alias-brand-primary);\n text-decoration: none;\n}\n\n.sfb4d2f_detailRow a:hover {\n text-decoration: underline;\n}\n\n.sfb4d2f_detailRow a:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.sd58c6d_reviewedLine {\n margin: 8px 0 0;\n padding: 6px 8px;\n border-radius: 6px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 12%, transparent);\n font-size: 12px;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n/* Install flow. */\n.s4d374c_installPanel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 2px;\n}\n\n.se7aaed_installButton, .sb43a49_confirmButton, .s6e45d1_cancelButton {\n align-self: flex-start;\n padding: 5px 14px;\n border-radius: 6px;\n font-size: 13px;\n cursor: pointer;\n}\n\n.se7aaed_installButton {\n border: 1px solid var(--dsw-alias-brand-primary);\n background: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-bg-base);\n}\n\n.se7aaed_installButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.sb43a49_confirmButton {\n border: 1px solid var(--dsw-alias-brand-primary);\n background: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-bg-base);\n}\n\n.sb43a49_confirmButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.s6e45d1_cancelButton {\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n}\n\n.s6e45d1_cancelButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n}\n\n.se7aaed_installButton:focus-visible, .sb43a49_confirmButton:focus-visible, .s6e45d1_cancelButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The shelf card of a plugin that is installed and current carries this\n * non-interactive label instead of an install button. Border + text do the\n * work in the light theme, where every bg-layer token resolves to white. */\n.sb6fc74_installedLabel {\n align-self: flex-start;\n margin: 0;\n padding: 5px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n font-size: 13px;\n color: var(--dsw-alias-label-secondary);\n background: var(--dsw-alias-bg-layer-2);\n}\n\n/* An installed card's controls: the installed label or the update button,\n * plus the uninstall button. The install panel's running/failed states are\n * column layouts and sit as one item of this row. */\n.sf53ca0_installedActions {\n display: flex;\n align-items: flex-start;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n/* Uninstall revokes privilege; the quiet outline keeps it from competing\n * with the primary action, and the error tint says \"removes\". */\n.s96967f_uninstallButton {\n align-self: flex-start;\n padding: 5px 14px;\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 55%, transparent);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-state-error-primary);\n font-size: 13px;\n cursor: pointer;\n}\n\n.s96967f_uninstallButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);\n}\n\n.s96967f_uninstallButton:focus-visible {\n outline: 2px solid var(--dsw-alias-state-error-primary);\n outline-offset: 1px;\n}\n\n/* The restart dsh button on a done install: quiet outline, same rank as\n * the other secondary actions. */\n.s6ac237_restartButton {\n align-self: flex-start;\n padding: 5px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n cursor: pointer;\n}\n\n.s6ac237_restartButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n}\n\n.s6ac237_restartButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.sc4f2ac_gate {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 10px;\n border: 1px solid var(--dsw-alias-state-warn-primary);\n border-radius: 8px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 8%, transparent);\n}\n\n.s559c38_gateTitle {\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.s3c0ace_gateBody {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.s4b2373_gateActions {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n.s52b5a5_installing {\n margin: 0;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s64ab71_log {\n max-height: 160px;\n overflow: auto;\n margin: 0;\n padding: 8px;\n border-radius: 6px;\n background: var(--dsw-alias-bg-base);\n border: 1px solid var(--dsw-alias-border-l1);\n list-style: none;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11px;\n line-height: 1.5;\n}\n\n.s1531a8_logLine {\n white-space: pre-wrap;\n word-break: break-all;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sf20dda_notice {\n margin: 0;\n font-size: 12px;\n color: var(--dsw-alias-state-success-primary);\n}\n\n.seb9b08_failedHeading {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.sfd3021_failedDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.s50ba35_rejectedCode {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.se75489_rejectedDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n/* Installed section. */\n.sa276cf_outdatedSection {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 6px;\n}\n\n.sceae6e_outdatedList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.s8d2072_outdatedRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n flex-wrap: wrap;\n padding: 10px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1);\n}\n\n.s5c2545_outdatedInfo {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n.s5c2545_outdatedInfo strong {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 13px;\n word-break: break-all;\n color: var(--dsw-alias-label-primary);\n}\n\n.s877c68_outdatedVersions {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sa1f556_outdatedActions {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n/* The enable/disable switch. */\n.s11c019_switch {\n position: relative;\n width: 32px;\n height: 18px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-2);\n cursor: pointer;\n transition: background 120ms ease, border-color 120ms ease;\n}\n\n.s7580e8_switchOn {\n border-color: var(--dsw-alias-state-success-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 55%, transparent);\n}\n\n.saab2b3_switchKnob {\n position: absolute;\n top: 2px;\n left: 2px;\n width: 12px;\n height: 12px;\n border-radius: 999px;\n background: var(--dsw-alias-label-primary);\n transition: transform 120ms ease, background 120ms ease;\n}\n\n.s7580e8_switchOn .saab2b3_switchKnob {\n transform: translateX(14px);\n background: var(--dsw-alias-bg-base);\n}\n\n.s11c019_switch:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .s11c019_switch, .saab2b3_switchKnob { transition: none; }\n}\n";
5089
+ const css = "/* Shop tab styles — a market shelf inside the settings surface. The visual\n * language is the dsh theme's alias tokens ONLY (the four tokens the previous\n * styles referenced that do not exist — bg-layer-3, interactive-bg-hover,\n * state-error, state-success — are corrected to bg-layer-2 and the\n * state-*-primary set). The signature is the category spine: one HUE per\n * category (six fixed hues, not the brand token — the brand resolves to\n * near-black in the light theme, so six brand opacities read as six shades\n * of gray), with the category name as the cover label in the same hue. Class names are mapped to content-derived names at bundle time\n * (tsdown.s8583e3_client.s942a79_config.s5f67aa_ts) and stubbed in tests; only the keys are relied\n * on.\n *\n * One token rule the theme forces: the background LAYERS do not carry\n * contrast. In the light theme bg-base, bg-layer-1, bg-layer-2 and\n * bg-layer-3 all resolve to the same white; only the dark theme spreads them\n * (950/875/850/800). So a layer-2 fill on a layer-1 ground is invisible for\n * half of all users, and anything whose fill is its ONLY affordance — the\n * loading skeleton, the borderless capability chips — derives its fill from\n * label-primary instead, which inverts with the theme by construction.\n * Elements that also carry a border or text may keep a layer fill.\n * `css-tokens.s8583e3_client.sfd9d8a_spec.s5f67aa_ts` pins this. */\n\n.s255e5a_panel {\n display: flex;\n flex-direction: column;\n gap: 14px;\n /* The shelf never scrolls sideways: a card too wide for its track wraps\n * its badges rather than widening the panel. */\n overflow-x: hidden;\n}\n\n.s44a3ec_stateLine {\n margin: 0;\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n/* Visually hidden but screen-reader readable (the loading copy while the\n * skeleton stands in visually). */\n.s3a0ece_srOnly {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n border: 0;\n clip: rect(0 0 0 0);\n clip-path: inset(50%);\n overflow: hidden;\n white-space: nowrap;\n}\n\n/* The loading shelf: ghost cards with the same geometry as the real grid, so\n * the swap to content is a same-shape handoff instead of a jump. */\n.se0c3b4_skeletonGrid {\n display: grid;\n grid-template-columns: 1fr;\n gap: 8px;\n}\n\n.s520416_skeletonCard {\n position: relative;\n display: flex;\n flex-direction: column;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n overflow: hidden;\n}\n\n/* The shimmer sweep: one gradient band travels across each ghost card while\n * the bars breathe, so the loading state reads as \"the shelf is scanning\". */\n.s520416_skeletonCard::after {\n content: '';\n position: absolute;\n inset: 0;\n background: linear-gradient(\n 100deg,\n transparent 25%,\n color-mix(in srgb, var(--dsw-alias-label-primary) 14%, transparent) 50%,\n transparent 75%\n );\n transform: translateX(-100%);\n animation: dshShopSkeletonSweep 1.4s ease-in-out infinite;\n pointer-events: none;\n}\n\n@keyframes dshShopSkeletonSweep {\n to { transform: translateX(100%); }\n}\n\n.s8c7d6f_skeletonName {\n width: 55%;\n height: 14px;\n margin: 10px 12px 0;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n.sf5179e_skeletonSummary {\n width: 92%;\n height: 12px;\n margin: 10px 12px 0;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n.sbb0d25_skeletonSummaryShort {\n width: 68%;\n height: 12px;\n margin: 6px 12px 10px;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n/* The ghost of the action line, right where the real buttons land. */\n.s96e910_skeletonActions {\n display: block;\n width: 84px;\n height: 24px;\n margin: 0 12px 10px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n.s520416_skeletonCard span {\n animation: dshShopSkeletonPulse 1.6s ease-in-out infinite;\n}\n\n@keyframes dshShopSkeletonPulse {\n 0%, 100% { opacity: 0.5; }\n 50% { opacity: 1; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .s520416_skeletonCard span { animation: none; opacity: 0.75; }\n .s520416_skeletonCard::after { content: none; }\n}\n\n.s73242a_actionButton {\n align-self: flex-start;\n padding: 5px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n cursor: pointer;\n}\n\n.s73242a_actionButton:hover {\n border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, var(--dsw-alias-border-l2));\n color: var(--dsw-alias-brand-primary);\n}\n\n.s73242a_actionButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s646509_toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.s745e3c_toolbarRight {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n/* The shop's own version, right of the search box: `v0.4.4`, plus the\n * update button when the version check found a newer release. */\n.s4fe61f_versionBlock {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.sb9b924_versionText {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sa04455_updateSelfButton {\n padding: 2px 10px;\n border: 1px solid var(--dsw-alias-brand-primary);\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);\n color: var(--dsw-alias-brand-primary);\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n}\n\n.sa04455_updateSelfButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 20%, transparent);\n}\n\n.sa04455_updateSelfButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The on-demand re-check next to the version number: a quiet pill; the\n * border and text carry it in the light theme where bg layers are white. */\n.s614a38_checkUpdateButton {\n padding: 2px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n}\n\n.s614a38_checkUpdateButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n color: var(--dsw-alias-label-primary);\n}\n\n.s614a38_checkUpdateButton:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n.s614a38_checkUpdateButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The project's GitHub mark, right of the version row: a quiet icon link.\n * The octocat carries itself — color shift on hover, no text, no border. */\n.sa987f7_githubLink {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 3px;\n border-radius: 6px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sa987f7_githubLink:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-layer-2);\n}\n\n.sa987f7_githubLink:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The self-update's expanded states (running log / done + restart /\n * failure detail) sit under the toolbar, above the category bar. */\n.sb81ba5_selfUpdatePanel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 8px;\n}\n\n.s232ec4_searchInput {\n width: min(280px, 100%);\n padding: 6px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n.s232ec4_searchInput:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s1555aa_staleBlock {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.s3946f1_staleBadge {\n padding: 2px 8px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 40%, transparent);\n color: var(--dsw-alias-state-warn-primary);\n font-size: 12px;\n}\n\n.sb2c859_categoryBar {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-top: 6px;\n}\n\n.s26d58e_categoryButton {\n padding: 3px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n}\n\n.s26d58e_categoryButton:hover {\n border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, var(--dsw-alias-border-l2));\n color: var(--dsw-alias-brand-primary);\n}\n\n.s5f68c4_categoryButtonOn {\n border-color: var(--dsw-alias-brand-primary);\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);\n color: var(--dsw-alias-brand-primary);\n font-weight: 600;\n}\n\n.s26d58e_categoryButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s668774_catalogHeading {\n margin: 2px 0 0;\n font-size: 16px;\n font-weight: 600;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-primary);\n}\n\n.sccc4d1_catalogStats {\n margin: 6px 0 0;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s6de1ab_emptyLine {\n margin: 0;\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n/* The shelf: a responsive grid of cards. */\n/* Single-line rows: one full-width column, one plugin per line. */\n.sf657f5_cards {\n display: grid;\n grid-template-columns: 1fr;\n max-width: 100%;\n gap: 8px;\n list-style: none;\n margin: 0;\n padding: 0;\n animation: dshShopCardsIn 220ms ease;\n}\n\n@keyframes dshShopCardsIn {\n from { opacity: 0; transform: translateY(3px); }\n to { opacity: 1; transform: none; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .sf657f5_cards { animation: none; }\n}\n\n.s65f383_card {\n position: relative;\n display: flex;\n flex-direction: column;\n /* A grid item's min-width is auto by default: its content would widen the\n * 1fr track past the panel and overflow horizontally. */\n min-width: 0;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n overflow: hidden;\n transition: border-color 120ms ease, transform 120ms ease, box-shadow 120ms ease;\n /* Off-screen cards skip layout and paint; the placeholder keeps scrollbar\n * estimation sane before the browser has measured the real height. */\n content-visibility: auto;\n contain-intrinsic-size: auto 140px;\n}\n\n/* The sentinel that triggers the next shelf batch: zero visual footprint. */\n.sd7ee01_cardsSentry {\n height: 1px;\n margin: 0;\n padding: 0;\n}\n\n.s705cc6_showingLine {\n margin: 0;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s65f383_card:hover {\n border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, var(--dsw-alias-border-l1));\n transform: translateY(-1px);\n box-shadow: 0 2px 8px color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .s65f383_card { transition: none; }\n .s65f383_card:hover { transform: none; }\n}\n\n/* The signature: a spine and cover in the category's own hue. The hues are\n * fixed mid-bright colors that read on both themes; `other` deliberately\n * stays a neutral gray so the fallback category does not compete. */\n.s4ce25d_cardSpine {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: 3px;\n background: var(--spine-hue, #8B8E96);\n}\n\n.s65f383_card[data-category='tool'] { --spine-hue: #4C8DFF; }\n.s65f383_card[data-category='provider'] { --spine-hue: #A78BFA; }\n.s65f383_card[data-category='ui'] { --spine-hue: #2DD4BF; }\n.s65f383_card[data-category='workflow'] { --spine-hue: #F59E0B; }\n.s65f383_card[data-category='integration'] { --spine-hue: #34D399; }\n.s65f383_card[data-category='other'] { --spine-hue: #8B8E96; }\n\n/* The category sits in the badge row as a quiet chip in the category's own\n * hue — the border carries it where the light theme's white layers cannot\n * (the same lesson as the check button). */\n.s9c8eaa_categoryBadge {\n padding: 1px 7px;\n border: 1px solid color-mix(in srgb, var(--spine-hue, #8B8E96) 45%, transparent);\n border-radius: 999px;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--spine-hue, #8B8E96);\n white-space: nowrap;\n}\n\n.sa97234_entryHeader {\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 12px;\n min-width: 0;\n padding: 8px 12px 2px;\n border: none;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n\n.sa97234_entryHeader:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n\n/* The name fills the row and truncates rather than wraps; the badges stay\n * intact beside it. */\n.sc3f149_name {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 13px;\n font-weight: 600;\n flex: 1 1 auto;\n min-width: 0;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: var(--dsw-alias-label-primary);\n}\n\n.s513026_badges {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n/* The action line: buttons side by side; an active install/uninstall flow\n * (gate, log, notices) takes the full width below them. */\n.s9b5048_cardActions {\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n padding: 8px 12px 10px;\n}\n\n.s9b5048_cardActions > .sc4f2ac_gate,\n.s9b5048_cardActions > .s4d374c_installPanel,\n.s9b5048_cardActions > .sf53ca0_installedActions {\n flex-basis: 100%;\n}\n\n/* The entry's version, always visible on the card (the expanded detail\n * repeats it in the version row) — the same quiet mono as the toolbar. */\n.sc6ec6e_cardVersion {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* The octocat marking a github-sourced entry, beside its short commit. */\n.s71f10f_sourceBadge {\n display: inline-flex;\n align-items: center;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s5e82cb_tierBadge {\n padding: 1px 7px;\n border-radius: 999px;\n font-size: 11px;\n line-height: 1.6;\n}\n\n.s5e82cb_tierBadge[data-tier='verified'] {\n color: var(--dsw-alias-state-success-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-success-primary) 40%, transparent);\n}\n\n.s5e82cb_tierBadge[data-tier='verified-stale'] {\n color: var(--dsw-alias-state-warn-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 40%, transparent);\n}\n\n.s5e82cb_tierBadge[data-tier='community'] {\n color: var(--dsw-alias-label-secondary);\n background: var(--dsw-alias-bg-layer-2);\n border: 1px solid var(--dsw-alias-border-l2);\n}\n\n.sff43b4_starsBadge {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s73f7fb_chevron, .s61393c_chevronOpen {\n color: var(--dsw-alias-label-secondary);\n transition: transform 120ms ease;\n}\n\n.s61393c_chevronOpen { transform: rotate(180deg); }\n\n@media (prefers-reduced-motion: reduce) {\n .s73f7fb_chevron, .s61393c_chevronOpen { transition: none; }\n}\n\n.s820ebf_body {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 0 12px;\n}\n\n.sb8fbc8_summary {\n margin: 0;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n.se507db_summaryZh {\n margin: -4px 0 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-secondary);\n display: -webkit-box;\n -webkit-line-clamp: 1;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n/* Expanded cards show the full summary: the clamp is lifted, never raised —\n * a higher number would still truncate the author's text, just later. */\n.s10baf0_summaryExpanded {\n -webkit-line-clamp: unset;\n display: block;\n}\n\n.sb9dba9_summaryZhExpanded {\n -webkit-line-clamp: unset;\n display: block;\n}\n\n.s9d2029_capabilitiesBlock {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.s3bdbd5_capabilitiesNote {\n margin: 0;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s441702_capabilities {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.s441702_capabilities li {\n padding: 1px 6px;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 6%, transparent);\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sde3f0c_detail {\n border-top: 1px solid var(--dsw-alias-border-l1);\n padding-top: 8px;\n}\n\n.se98179_detailRows {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin: 0;\n}\n\n.sfb4d2f_detailRow {\n display: flex;\n justify-content: space-between;\n gap: 12px;\n font-size: 12px;\n}\n\n.sfb4d2f_detailRow dt {\n color: var(--dsw-alias-label-secondary);\n}\n\n.sfb4d2f_detailRow dd {\n margin: 0;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n overflow-wrap: anywhere;\n text-align: right;\n color: var(--dsw-alias-label-primary);\n}\n\n.sfb4d2f_detailRow a {\n color: var(--dsw-alias-brand-primary);\n text-decoration: none;\n}\n\n.sfb4d2f_detailRow a:hover {\n text-decoration: underline;\n}\n\n.sfb4d2f_detailRow a:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.sd58c6d_reviewedLine {\n margin: 8px 0 0;\n padding: 6px 8px;\n border-radius: 6px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 12%, transparent);\n font-size: 12px;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n/* Install flow. */\n.s4d374c_installPanel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 2px;\n}\n\n.se7aaed_installButton, .sb43a49_confirmButton, .s6e45d1_cancelButton {\n align-self: flex-start;\n padding: 5px 14px;\n border-radius: 6px;\n font-size: 13px;\n cursor: pointer;\n}\n\n.se7aaed_installButton {\n border: 1px solid var(--dsw-alias-brand-primary);\n background: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-bg-base);\n}\n\n.se7aaed_installButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.sb43a49_confirmButton {\n border: 1px solid var(--dsw-alias-brand-primary);\n background: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-bg-base);\n}\n\n.sb43a49_confirmButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.s6e45d1_cancelButton {\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n}\n\n.s6e45d1_cancelButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n}\n\n.se7aaed_installButton:focus-visible, .sb43a49_confirmButton:focus-visible, .s6e45d1_cancelButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The shelf card of a plugin that is installed and current carries this\n * non-interactive label instead of an install button. Border + text do the\n * work in the light theme, where every bg-layer token resolves to white. */\n.sb6fc74_installedLabel {\n align-self: flex-start;\n margin: 0;\n padding: 5px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n font-size: 13px;\n color: var(--dsw-alias-label-secondary);\n background: var(--dsw-alias-bg-layer-2);\n}\n\n/* An installed card's controls: the installed label or the update button,\n * plus the uninstall button. The install panel's running/failed states are\n * column layouts and sit as one item of this row. */\n.sf53ca0_installedActions {\n display: flex;\n align-items: flex-start;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n/* Uninstall revokes privilege; the quiet outline keeps it from competing\n * with the primary action, and the error tint says \"removes\". */\n.s96967f_uninstallButton {\n align-self: flex-start;\n padding: 5px 14px;\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 55%, transparent);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-state-error-primary);\n font-size: 13px;\n cursor: pointer;\n}\n\n.s96967f_uninstallButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);\n}\n\n.s96967f_uninstallButton:focus-visible {\n outline: 2px solid var(--dsw-alias-state-error-primary);\n outline-offset: 1px;\n}\n\n/* The restart dsh button on a done install: quiet outline, same rank as\n * the other secondary actions. */\n.s6ac237_restartButton {\n align-self: flex-start;\n padding: 5px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n cursor: pointer;\n}\n\n.s6ac237_restartButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n}\n\n.s6ac237_restartButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.sc4f2ac_gate {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 10px;\n border: 1px solid var(--dsw-alias-state-warn-primary);\n border-radius: 8px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 8%, transparent);\n}\n\n.s559c38_gateTitle {\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.s3c0ace_gateBody {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.s4b2373_gateActions {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n.s52b5a5_installing {\n margin: 0;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s64ab71_log {\n max-height: 160px;\n overflow: auto;\n margin: 0;\n padding: 8px;\n border-radius: 6px;\n background: var(--dsw-alias-bg-base);\n border: 1px solid var(--dsw-alias-border-l1);\n list-style: none;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11px;\n line-height: 1.5;\n}\n\n.s1531a8_logLine {\n white-space: pre-wrap;\n word-break: break-all;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sf20dda_notice {\n margin: 0;\n font-size: 12px;\n color: var(--dsw-alias-state-success-primary);\n}\n\n.seb9b08_failedHeading {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.sfd3021_failedDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.s50ba35_rejectedCode {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.se75489_rejectedDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n/* Installed section. */\n.sa276cf_outdatedSection {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 6px;\n}\n\n.sceae6e_outdatedList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.s8d2072_outdatedRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n flex-wrap: wrap;\n padding: 10px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1);\n}\n\n.s5c2545_outdatedInfo {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n.s5c2545_outdatedInfo strong {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 13px;\n word-break: break-all;\n color: var(--dsw-alias-label-primary);\n}\n\n.s877c68_outdatedVersions {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sa1f556_outdatedActions {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n/* The enable/disable switch. */\n.s11c019_switch {\n position: relative;\n width: 32px;\n height: 18px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-2);\n cursor: pointer;\n transition: background 120ms ease, border-color 120ms ease;\n}\n\n.s7580e8_switchOn {\n border-color: var(--dsw-alias-state-success-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 55%, transparent);\n}\n\n.saab2b3_switchKnob {\n position: absolute;\n top: 2px;\n left: 2px;\n width: 12px;\n height: 12px;\n border-radius: 999px;\n background: var(--dsw-alias-label-primary);\n transition: transform 120ms ease, background 120ms ease;\n}\n\n.s7580e8_switchOn .saab2b3_switchKnob {\n transform: translateX(14px);\n background: var(--dsw-alias-bg-base);\n}\n\n.s11c019_switch:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .s11c019_switch, .saab2b3_switchKnob { transition: none; }\n}\n\n/* The hot enable/disable switch on installed rows: the knob button plus the\n * §8 hot-apply / failure notices rendered beside it. */\n.sd9f9e4_switchWrap {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n}\n";
5077
5090
  if (typeof document !== "undefined" && !document.querySelector("style[data-plugin-css=\"e3675a89\"]")) {
5078
5091
  const tag = document.createElement("style");
5079
5092
  tag.dataset.pluginCss = "e3675a89";
@@ -5168,7 +5181,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5168
5181
  "outdatedActions": "sa1f556_outdatedActions",
5169
5182
  "switch": "s11c019_switch",
5170
5183
  "switchOn": "s7580e8_switchOn",
5171
- "switchKnob": "saab2b3_switchKnob"
5184
+ "switchKnob": "saab2b3_switchKnob",
5185
+ "switchWrap": "sd9f9e4_switchWrap"
5172
5186
  };
5173
5187
  //#endregion
5174
5188
  //#region src/client/ShopTab.tsx
@@ -5198,7 +5212,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5198
5212
  * install controls. An installed plugin's card carries its installed row:
5199
5213
  * current → the non-interactive installed label, behind → the update button;
5200
5214
  * uninstalled → the install button. */
5201
- const EntryCard = (0, react.memo)(function EntryCard({ entry, stars, installed, t, install, installStatus, uninstall, restart }) {
5215
+ const EntryCard = (0, react.memo)(function EntryCard({ entry, stars, installed, t, install, installStatus, uninstall, restart, setEnabled }) {
5202
5216
  const [open, setOpen] = (0, react.useState)(false);
5203
5217
  const detailId = (0, react.useId)();
5204
5218
  const summary = entry.catalog?.summary;
@@ -5297,7 +5311,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5297
5311
  }) : entry.repository })]
5298
5312
  }), entry.license !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5299
5313
  className: _dsh_plugin_shop_css_e3675a89_default.detailRow,
5300
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("license") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: entry.license })]
5314
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("license") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: isCustomLicense(entry.license) ? t("customLicense") : entry.license })]
5301
5315
  })]
5302
5316
  }), entry.tier === "verified-stale" && entry.review !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5303
5317
  className: _dsh_plugin_shop_css_e3675a89_default.reviewedLine,
@@ -5319,26 +5333,34 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5319
5333
  install,
5320
5334
  installStatus,
5321
5335
  restart
5322
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [installed.outdated ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallPanel, {
5323
- name: entry.name,
5324
- version: entry.version,
5325
- tier: entry.tier,
5326
- variant: "update",
5327
- t,
5328
- install,
5329
- installStatus,
5330
- restart
5331
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5332
- className: _dsh_plugin_shop_css_e3675a89_default.installedLabel,
5333
- "data-shop-installed": true,
5334
- children: t("installed")
5335
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UninstallPanel, {
5336
- name: entry.name,
5337
- t,
5338
- uninstall,
5339
- installStatus,
5340
- restart
5341
- })] })
5336
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
5337
+ installed.outdated ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallPanel, {
5338
+ name: entry.name,
5339
+ version: entry.version,
5340
+ tier: entry.tier,
5341
+ variant: "update",
5342
+ t,
5343
+ install,
5344
+ installStatus,
5345
+ restart
5346
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5347
+ className: _dsh_plugin_shop_css_e3675a89_default.installedLabel,
5348
+ "data-shop-installed": true,
5349
+ children: t("installed")
5350
+ }),
5351
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EnabledSwitch, {
5352
+ row: installed,
5353
+ t,
5354
+ setEnabled
5355
+ }),
5356
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UninstallPanel, {
5357
+ name: entry.name,
5358
+ t,
5359
+ uninstall,
5360
+ installStatus,
5361
+ restart
5362
+ })
5363
+ ] })
5342
5364
  })
5343
5365
  ]
5344
5366
  });
@@ -5613,14 +5635,15 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5613
5635
  children: t("restart")
5614
5636
  });
5615
5637
  }
5616
- /** One outdated install row (§7.3): the name, the installed and latest
5617
- * versions, an optimistic enable/disable switch, and the update button (the
5618
- * install flow for `name@latest`, reusing `InstallPanel`). The switch has no
5619
- * installed-state RPC behind it the tab assumes an installed plugin is on,
5620
- * so the first toggle sends the inverted value. After a successful `setEnabled`
5621
- * the §8 hot note renders. */
5622
- function OutdatedRow({ row, tier, source, t, setEnabled, install, installStatus, restart }) {
5623
- const [enabled, setEnabledState] = (0, react.useState)(true);
5638
+ /**
5639
+ * The hot enable/disable switch (§8). The initial state is the Host's
5640
+ * inventory verdict carried on the installed row; the click is optimistic
5641
+ * (the value flips on success) and a success renders the §8 hot note. A
5642
+ * transport throw renders the localized failure line its private detail
5643
+ * (which can name hosts and ports) never reaches the UI.
5644
+ */
5645
+ function EnabledSwitch({ row, t, setEnabled }) {
5646
+ const [enabled, setEnabledState] = (0, react.useState)(row.enabled);
5624
5647
  const [toggle, setToggle] = (0, react.useState)({ kind: "idle" });
5625
5648
  const onToggle = async () => {
5626
5649
  if (toggle.kind === "saving") return;
@@ -5646,47 +5669,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5646
5669
  }
5647
5670
  };
5648
5671
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5649
- className: _dsh_plugin_shop_css_e3675a89_default.outdatedRow,
5650
- "data-shop-outdated-entry": row.name,
5672
+ className: _dsh_plugin_shop_css_e3675a89_default.switchWrap,
5673
+ "data-shop-enabled-switch": row.name,
5651
5674
  children: [
5652
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5653
- className: _dsh_plugin_shop_css_e3675a89_default.outdatedInfo,
5654
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5655
- className: _dsh_plugin_shop_css_e3675a89_default.name,
5656
- children: row.name
5657
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5658
- className: _dsh_plugin_shop_css_e3675a89_default.outdatedVersions,
5659
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5660
- className: _dsh_plugin_shop_css_e3675a89_default.outdatedVersion,
5661
- children: t("installedVersion", { version: source === "github" ? row.installed.slice(0, 7) : row.installed })
5662
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5663
- className: _dsh_plugin_shop_css_e3675a89_default.outdatedVersion,
5664
- children: t("latestVersion", { version: row.latest })
5665
- })]
5666
- })]
5667
- }),
5668
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5669
- className: _dsh_plugin_shop_css_e3675a89_default.outdatedActions,
5670
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5671
- type: "button",
5672
- role: "switch",
5673
- "aria-checked": enabled,
5674
- "aria-label": t("enabledSwitch"),
5675
- "data-shop-toggle": true,
5676
- className: `${_dsh_plugin_shop_css_e3675a89_default.switch} ${enabled ? _dsh_plugin_shop_css_e3675a89_default.switchOn : ""}`,
5677
- onClick: () => void onToggle(),
5678
- disabled: toggle.kind === "saving",
5679
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_e3675a89_default.switchKnob })
5680
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallPanel, {
5681
- name: row.name,
5682
- version: row.latest,
5683
- tier,
5684
- variant: "update",
5685
- t,
5686
- install,
5687
- installStatus,
5688
- restart
5689
- })]
5675
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5676
+ type: "button",
5677
+ role: "switch",
5678
+ "aria-checked": enabled,
5679
+ "aria-label": t("enabledSwitch"),
5680
+ "data-shop-toggle": true,
5681
+ className: `${_dsh_plugin_shop_css_e3675a89_default.switch} ${enabled ? _dsh_plugin_shop_css_e3675a89_default.switchOn : ""}`,
5682
+ onClick: () => void onToggle(),
5683
+ disabled: toggle.kind === "saving",
5684
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_e3675a89_default.switchKnob })
5690
5685
  }),
5691
5686
  toggle.kind === "saved" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5692
5687
  className: _dsh_plugin_shop_css_e3675a89_default.notice,
@@ -5701,6 +5696,47 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5701
5696
  ]
5702
5697
  });
5703
5698
  }
5699
+ /** One outdated install row (§7.3): the name, the installed and latest
5700
+ * versions, the hot enable/disable switch, and the update button (the
5701
+ * install flow for `name@latest`, reusing `InstallPanel`). */
5702
+ function OutdatedRow({ row, tier, source, t, setEnabled, install, installStatus, restart }) {
5703
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5704
+ className: _dsh_plugin_shop_css_e3675a89_default.outdatedRow,
5705
+ "data-shop-outdated-entry": row.name,
5706
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5707
+ className: _dsh_plugin_shop_css_e3675a89_default.outdatedInfo,
5708
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5709
+ className: _dsh_plugin_shop_css_e3675a89_default.name,
5710
+ children: row.name
5711
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5712
+ className: _dsh_plugin_shop_css_e3675a89_default.outdatedVersions,
5713
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5714
+ className: _dsh_plugin_shop_css_e3675a89_default.outdatedVersion,
5715
+ children: t("installedVersion", { version: source === "github" ? row.installed.slice(0, 7) : row.installed })
5716
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5717
+ className: _dsh_plugin_shop_css_e3675a89_default.outdatedVersion,
5718
+ children: t("latestVersion", { version: row.latest })
5719
+ })]
5720
+ })]
5721
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5722
+ className: _dsh_plugin_shop_css_e3675a89_default.outdatedActions,
5723
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(EnabledSwitch, {
5724
+ row,
5725
+ t,
5726
+ setEnabled
5727
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallPanel, {
5728
+ name: row.name,
5729
+ version: row.latest,
5730
+ tier,
5731
+ variant: "update",
5732
+ t,
5733
+ install,
5734
+ installStatus,
5735
+ restart
5736
+ })]
5737
+ })]
5738
+ });
5739
+ }
5704
5740
  /** The §7.3 installed list, rendered as the "installed" section: each row
5705
5741
  * shows both versions, a switch, and an update button. The rows are the
5706
5742
  * installed entries filtered to `outdated` — a current install is already
@@ -6122,7 +6158,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6122
6158
  install,
6123
6159
  installStatus,
6124
6160
  uninstall,
6125
- restart
6161
+ restart,
6162
+ setEnabled
6126
6163
  }) }, entry.name)), incremental && visibleCount < filtered.length && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
6127
6164
  ref: sentinelRef,
6128
6165
  className: _dsh_plugin_shop_css_e3675a89_default.cardsSentry,
package/lib/index.js CHANGED
@@ -59,7 +59,8 @@ const entrySchema = z.object({
59
59
  capabilities: z.array(z.string())
60
60
  }).optional(),
61
61
  source: z.enum(["npm", "github"]).default("npm"),
62
- repo: z.string().optional()
62
+ repo: z.string().optional(),
63
+ subdir: z.string().regex(/^(?!.*(^|\/)\.\.?(\/|$))[A-Za-z0-9._-]+(\/[A-Za-z0-9._-]+)*$/).optional()
63
64
  });
64
65
  const dataSchema = z.object({
65
66
  schemaVersion: z.number(),
@@ -143,13 +144,13 @@ async function loadCatalog(options) {
143
144
  const readCached = () => {
144
145
  try {
145
146
  const pointer = pointerSchema.parse(JSON.parse(fsImpl.read(indexPath)));
146
- if (pointer.schemaVersion > 3) throw new Error(`catalog schemaVersion ${pointer.schemaVersion} is newer than this build supports (3)`);
147
+ if (pointer.schemaVersion > 4) throw new Error(`catalog schemaVersion ${pointer.schemaVersion} is newer than this build supports (4)`);
147
148
  const dataPath = join(cacheDir, basename(pointer.plugins.url));
148
149
  const dataText = fsImpl.read(dataPath);
149
150
  const actual = createHash("sha256").update(dataText).digest("hex");
150
151
  if (actual !== pointer.plugins.sha256) throw new Error(`cached catalog data failed integrity check: expected ${pointer.plugins.sha256}, got ${actual}`);
151
152
  const data = dataSchema.parse(JSON.parse(dataText));
152
- if (data.schemaVersion > 3) throw new Error(`catalog schemaVersion ${data.schemaVersion} is newer than this build supports (3)`);
153
+ if (data.schemaVersion > 4) throw new Error(`catalog schemaVersion ${data.schemaVersion} is newer than this build supports (4)`);
153
154
  let stars = {};
154
155
  if (pointer.stars !== void 0) try {
155
156
  const starsText = fsImpl.read(join(cacheDir, basename(pointer.stars.url)));
@@ -190,7 +191,7 @@ async function loadCatalog(options) {
190
191
  throw error;
191
192
  }
192
193
  const pointer = pointerSchema.parse(JSON.parse(pointerText));
193
- if (pointer.schemaVersion > 3) throw new Error(`catalog schemaVersion ${pointer.schemaVersion} is newer than this build supports (3)`);
194
+ if (pointer.schemaVersion > 4) throw new Error(`catalog schemaVersion ${pointer.schemaVersion} is newer than this build supports (4)`);
194
195
  const dataUrl = resolveDataUrl(baseUrl, pointer.plugins.url);
195
196
  let dataText;
196
197
  try {
@@ -208,7 +209,7 @@ async function loadCatalog(options) {
208
209
  const actual = createHash("sha256").update(dataText).digest("hex");
209
210
  if (actual !== pointer.plugins.sha256) throw new Error(`catalog data failed integrity check: expected ${pointer.plugins.sha256}, got ${actual}`);
210
211
  const data = dataSchema.parse(JSON.parse(dataText));
211
- if (data.schemaVersion > 3) throw new Error(`catalog schemaVersion ${data.schemaVersion} is newer than this build supports (3)`);
212
+ if (data.schemaVersion > 4) throw new Error(`catalog schemaVersion ${data.schemaVersion} is newer than this build supports (4)`);
212
213
  let stars = {};
213
214
  if (pointer.stars !== void 0) try {
214
215
  const starsResponse = await fetchImpl(resolveDataUrl(baseUrl, pointer.stars.url));
@@ -851,8 +852,14 @@ let ShopGateway = (() => {
851
852
  }
852
853
  /** Enable or disable one installed plugin, hot (§8): a disable writes the
853
854
  * row to the user layer, an enable drops it again so the bundle default
854
- * rules — the CLI's watchUserPatches applies either through HMR. */
855
+ * rules — the CLI's watchUserPatches applies either through HMR. The shop's
856
+ * own row and the framework's bundles are never toggleable: disabling the
857
+ * host chain would break HMR itself. */
855
858
  setEnabled(args) {
859
+ if (args.name === "dsh-plugin-shop" || args.name.startsWith("@deepseek-ai/")) return {
860
+ ok: false,
861
+ detail: `dsh-plugin-shop: ${args.name} is part of the harness chain and cannot be toggled from the shop`
862
+ };
856
863
  const entry = this.listInventory().find((entry) => entry.moduleName === args.name);
857
864
  if (entry === void 0) return {
858
865
  ok: false,
@@ -937,7 +944,7 @@ let ShopGateway = (() => {
937
944
  code: "git-missing",
938
945
  detail: "dsh-plugin-shop: git is not on PATH, which github installs require; install git and retry"
939
946
  };
940
- spec = `github:${entry.repo}#${args.version}`;
947
+ spec = `github:${entry.repo}#${args.version}${entry.subdir !== void 0 ? `&path:${entry.subdir}` : ""}`;
941
948
  } else spec = `${args.name}@${args.version}`;
942
949
  const running = startInstall({
943
950
  profile: this.profile,
@@ -1001,6 +1008,10 @@ let ShopGateway = (() => {
1001
1008
  }
1002
1009
  const dependencies = readProfileManifest("dsh-plugin-shop", this.profileDirResolved()).dependencies ?? {};
1003
1010
  const pins = readRepoPins(this.pinFs, this.pinsPath());
1011
+ let byName = /* @__PURE__ */ new Map();
1012
+ try {
1013
+ byName = new Map(this.listInventory().map((entry) => [entry.moduleName, entry.enabled]));
1014
+ } catch {}
1004
1015
  const installed = [];
1005
1016
  for (const entry of this.lastSnapshot.entries) {
1006
1017
  const spec = dependencies[entry.name];
@@ -1011,13 +1022,15 @@ let ShopGateway = (() => {
1011
1022
  name: entry.name,
1012
1023
  installed: pin ?? spec,
1013
1024
  latest: entry.version,
1014
- outdated: pin !== void 0 && pin !== entry.version
1025
+ outdated: pin !== void 0 && pin !== entry.version,
1026
+ enabled: byName.get(entry.name) ?? true
1015
1027
  });
1016
1028
  } else installed.push({
1017
1029
  name: entry.name,
1018
1030
  installed: spec,
1019
1031
  latest: entry.version,
1020
- outdated: this.isBehind(spec, entry.version)
1032
+ outdated: this.isBehind(spec, entry.version),
1033
+ enabled: byName.get(entry.name) ?? true
1021
1034
  });
1022
1035
  }
1023
1036
  return installed;
@@ -34,6 +34,7 @@ const dsh_plugin_shop_shop_catalog_result$schema = z.object({
34
34
  })]).optional(),
35
35
  'source': z.union([z.literal("npm"), z.literal("github")]),
36
36
  'repo': z.union([z.undefined(), z.string()]).optional(),
37
+ 'subdir': z.union([z.undefined(), z.string()]).optional(),
37
38
  })),
38
39
  'denied': z.array(z.object({
39
40
  'name': z.string(),
@@ -46,6 +47,7 @@ const dsh_plugin_shop_shop_installed_result$schema = z.array(z.object({
46
47
  'installed': z.string(),
47
48
  'latest': z.string(),
48
49
  'outdated': z.boolean(),
50
+ 'enabled': z.boolean(),
49
51
  }))
50
52
  const dsh_plugin_shop_shop_installStart_parameter_0$schema = z.object({
51
53
  'name': z.string(),
@@ -140,7 +142,7 @@ export const TYPERT = {
140
142
  typeSymbol: 'dsh-plugin-shop/types#ShopCatalogResult',
141
143
  schema: dsh_plugin_shop_shop_catalog_result$schema,
142
144
  },
143
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":270,"column":9},
145
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":275,"column":9},
144
146
  },
145
147
  {
146
148
  id: 'dsh-plugin-shop#shop/installed',
@@ -155,7 +157,7 @@ export const TYPERT = {
155
157
  typeSymbol: 'dsh-plugin-shop#shop/installed:result',
156
158
  schema: dsh_plugin_shop_shop_installed_result$schema,
157
159
  },
158
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":374,"column":9},
160
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":380,"column":9},
159
161
  },
160
162
  {
161
163
  id: 'dsh-plugin-shop#shop/installStart',
@@ -181,7 +183,7 @@ export const TYPERT = {
181
183
  typeSymbol: 'dsh-plugin-shop/types#ShopInstallResult',
182
184
  schema: dsh_plugin_shop_shop_installStart_result$schema,
183
185
  },
184
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":296,"column":9},
186
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":301,"column":9},
185
187
  },
186
188
  {
187
189
  id: 'dsh-plugin-shop#shop/installStatus',
@@ -206,7 +208,7 @@ export const TYPERT = {
206
208
  typeSymbol: 'dsh-plugin-shop/types#ShopInstallStatusResult',
207
209
  schema: dsh_plugin_shop_shop_installStatus_result$schema,
208
210
  },
209
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":363,"column":3},
211
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":369,"column":3},
210
212
  },
211
213
  {
212
214
  id: 'dsh-plugin-shop#shop/restart',
@@ -221,7 +223,7 @@ export const TYPERT = {
221
223
  typeSymbol: 'dsh-plugin-shop/types#ShopRestartResult',
222
224
  schema: dsh_plugin_shop_shop_restart_result$schema,
223
225
  },
224
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":469,"column":9},
226
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":491,"column":9},
225
227
  },
226
228
  {
227
229
  id: 'dsh-plugin-shop#shop/setEnabled',
@@ -246,7 +248,7 @@ export const TYPERT = {
246
248
  typeSymbol: 'dsh-plugin-shop/types#ShopSetEnabledResult',
247
249
  schema: dsh_plugin_shop_shop_setEnabled_result$schema,
248
250
  },
249
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":239,"column":3},
251
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":241,"column":3},
250
252
  },
251
253
  {
252
254
  id: 'dsh-plugin-shop#shop/uninstallStart',
@@ -272,7 +274,7 @@ export const TYPERT = {
272
274
  typeSymbol: 'dsh-plugin-shop/types#ShopUninstallResult',
273
275
  schema: dsh_plugin_shop_shop_uninstallStart_result$schema,
274
276
  },
275
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":429,"column":9},
277
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":451,"column":9},
276
278
  },
277
279
  {
278
280
  id: 'dsh-plugin-shop#shop/updateStart',
@@ -297,7 +299,7 @@ export const TYPERT = {
297
299
  typeSymbol: 'dsh-plugin-shop/types#ShopUpdateResult',
298
300
  schema: dsh_plugin_shop_shop_updateStart_result$schema,
299
301
  },
300
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":518,"column":9},
302
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":540,"column":9},
301
303
  },
302
304
  {
303
305
  id: 'dsh-plugin-shop#shop/version',
@@ -312,7 +314,7 @@ export const TYPERT = {
312
314
  typeSymbol: 'dsh-plugin-shop/types#ShopVersionResult',
313
315
  schema: dsh_plugin_shop_shop_version_result$schema,
314
316
  },
315
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":503,"column":9},
317
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":525,"column":9},
316
318
  },
317
319
  ],
318
320
  model: {
@@ -336,7 +338,7 @@ export const TYPERT = {
336
338
  "name": "setEnabled",
337
339
  "signature": "@Remote('setEnabled') setEnabled(args: { name: string; enabled: boolean }): ShopSetEnabledResult",
338
340
  "summary": "Enable or disable one installed plugin, hot (§8): a disable writes the row to the user layer, an enable drops it again so the bundle default rules — the CLI's watchUserPatches applies either through HMR.",
339
- "jsDoc": "/** Enable or disable one installed plugin, hot (§8): a disable writes the\n * row to the user layer, an enable drops it again so the bundle default\n * rules — the CLI's watchUserPatches applies either through HMR. */"
341
+ "jsDoc": "/** Enable or disable one installed plugin, hot (§8): a disable writes the\n * row to the user layer, an enable drops it again so the bundle default\n * rules — the CLI's watchUserPatches applies either through HMR. The shop's\n * own row and the framework's bundles are never toggleable: disabling the\n * host chain would break HMR itself. */"
340
342
  },
341
343
  {
342
344
  "kind": "method",
@@ -398,7 +400,7 @@ export const TYPERT = {
398
400
  "types": [
399
401
  {
400
402
  "name": "CatalogEntry",
401
- "declaration": "export interface CatalogEntry {\n name: string;\n version: string;\n integrity: string | null;\n publishedAt: string | null;\n repository: string | null;\n license: string | null;\n tier: 'verified' | 'verified-stale' | 'community';\n metadata: 'declared' | 'derived';\n review?: { reviewedVersion?: string; reviewedCommit?: string; reviewer: string; reviewCommit: string; notes: string; };\n catalog?: CatalogSection;\n source: 'npm' | 'github';\n repo?: string;\n}"
403
+ "declaration": "export interface CatalogEntry {\n name: string;\n version: string;\n integrity: string | null;\n publishedAt: string | null;\n repository: string | null;\n license: string | null;\n tier: 'verified' | 'verified-stale' | 'community';\n metadata: 'declared' | 'derived';\n review?: { reviewedVersion?: string; reviewedCommit?: string; reviewer: string; reviewCommit: string; notes: string; };\n catalog?: CatalogSection;\n source: 'npm' | 'github';\n repo?: string;\n subdir?: string;\n}"
402
404
  },
403
405
  {
404
406
  "name": "CatalogSection",
@@ -438,7 +440,7 @@ export const TYPERT = {
438
440
  },
439
441
  {
440
442
  "name": "ShopInstalledEntry",
441
- "declaration": "export interface ShopInstalledEntry {\n name: string;\n installed: string;\n latest: string;\n outdated: boolean;\n}"
443
+ "declaration": "export interface ShopInstalledEntry {\n name: string;\n installed: string;\n latest: string;\n outdated: boolean;\n enabled: boolean;\n}"
442
444
  },
443
445
  {
444
446
  "name": "ShopInstallResult",
@@ -34,6 +34,7 @@ const dsh_plugin_shop_shop_catalog_result$schema = z.object({
34
34
  })]).optional(),
35
35
  'source': z.union([z.literal("npm"), z.literal("github")]),
36
36
  'repo': z.union([z.undefined(), z.string()]).optional(),
37
+ 'subdir': z.union([z.undefined(), z.string()]).optional(),
37
38
  })),
38
39
  'denied': z.array(z.object({
39
40
  'name': z.string(),
@@ -46,6 +47,7 @@ const dsh_plugin_shop_shop_installed_result$schema = z.array(z.object({
46
47
  'installed': z.string(),
47
48
  'latest': z.string(),
48
49
  'outdated': z.boolean(),
50
+ 'enabled': z.boolean(),
49
51
  }))
50
52
  const dsh_plugin_shop_shop_installStart_parameter_0$schema = z.object({
51
53
  'name': z.string(),
@@ -137,7 +139,7 @@ export const TYPERT_REMOTE = {
137
139
  typeSymbol: 'dsh-plugin-shop/types#ShopCatalogResult',
138
140
  schema: dsh_plugin_shop_shop_catalog_result$schema,
139
141
  },
140
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":270,"column":9},
142
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":275,"column":9},
141
143
  },
142
144
  {
143
145
  id: 'dsh-plugin-shop#shop/installed',
@@ -152,7 +154,7 @@ export const TYPERT_REMOTE = {
152
154
  typeSymbol: 'dsh-plugin-shop#shop/installed:result',
153
155
  schema: dsh_plugin_shop_shop_installed_result$schema,
154
156
  },
155
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":374,"column":9},
157
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":380,"column":9},
156
158
  },
157
159
  {
158
160
  id: 'dsh-plugin-shop#shop/installStart',
@@ -178,7 +180,7 @@ export const TYPERT_REMOTE = {
178
180
  typeSymbol: 'dsh-plugin-shop/types#ShopInstallResult',
179
181
  schema: dsh_plugin_shop_shop_installStart_result$schema,
180
182
  },
181
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":296,"column":9},
183
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":301,"column":9},
182
184
  },
183
185
  {
184
186
  id: 'dsh-plugin-shop#shop/installStatus',
@@ -203,7 +205,7 @@ export const TYPERT_REMOTE = {
203
205
  typeSymbol: 'dsh-plugin-shop/types#ShopInstallStatusResult',
204
206
  schema: dsh_plugin_shop_shop_installStatus_result$schema,
205
207
  },
206
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":363,"column":3},
208
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":369,"column":3},
207
209
  },
208
210
  {
209
211
  id: 'dsh-plugin-shop#shop/restart',
@@ -218,7 +220,7 @@ export const TYPERT_REMOTE = {
218
220
  typeSymbol: 'dsh-plugin-shop/types#ShopRestartResult',
219
221
  schema: dsh_plugin_shop_shop_restart_result$schema,
220
222
  },
221
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":469,"column":9},
223
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":491,"column":9},
222
224
  },
223
225
  {
224
226
  id: 'dsh-plugin-shop#shop/setEnabled',
@@ -243,7 +245,7 @@ export const TYPERT_REMOTE = {
243
245
  typeSymbol: 'dsh-plugin-shop/types#ShopSetEnabledResult',
244
246
  schema: dsh_plugin_shop_shop_setEnabled_result$schema,
245
247
  },
246
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":239,"column":3},
248
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":241,"column":3},
247
249
  },
248
250
  {
249
251
  id: 'dsh-plugin-shop#shop/uninstallStart',
@@ -269,7 +271,7 @@ export const TYPERT_REMOTE = {
269
271
  typeSymbol: 'dsh-plugin-shop/types#ShopUninstallResult',
270
272
  schema: dsh_plugin_shop_shop_uninstallStart_result$schema,
271
273
  },
272
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":429,"column":9},
274
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":451,"column":9},
273
275
  },
274
276
  {
275
277
  id: 'dsh-plugin-shop#shop/updateStart',
@@ -294,7 +296,7 @@ export const TYPERT_REMOTE = {
294
296
  typeSymbol: 'dsh-plugin-shop/types#ShopUpdateResult',
295
297
  schema: dsh_plugin_shop_shop_updateStart_result$schema,
296
298
  },
297
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":518,"column":9},
299
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":540,"column":9},
298
300
  },
299
301
  {
300
302
  id: 'dsh-plugin-shop#shop/version',
@@ -309,7 +311,7 @@ export const TYPERT_REMOTE = {
309
311
  typeSymbol: 'dsh-plugin-shop/types#ShopVersionResult',
310
312
  schema: dsh_plugin_shop_shop_version_result$schema,
311
313
  },
312
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":503,"column":9},
314
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":525,"column":9},
313
315
  },
314
316
  ],
315
317
  }
@@ -21,7 +21,6 @@ export declare const zh: {
21
21
  tierVerified: string;
22
22
  tierVerifiedStale: string;
23
23
  tierCommunity: string;
24
- unclaimed: string;
25
24
  capabilitiesNote: string;
26
25
  install: string;
27
26
  confirm: string;
@@ -36,9 +35,9 @@ export declare const zh: {
36
35
  refresh: string;
37
36
  staleLabel: string;
38
37
  showing: string;
39
- version: string;
40
38
  repository: string;
41
39
  license: string;
40
+ customLicense: string;
42
41
  reviewedVersionLine: string;
43
42
  deniedCode: string;
44
43
  notInCatalogCode: string;
@@ -96,7 +95,6 @@ export declare const en: {
96
95
  tierVerified: string;
97
96
  tierVerifiedStale: string;
98
97
  tierCommunity: string;
99
- unclaimed: string;
100
98
  capabilitiesNote: string;
101
99
  install: string;
102
100
  confirm: string;
@@ -111,9 +109,9 @@ export declare const en: {
111
109
  refresh: string;
112
110
  staleLabel: string;
113
111
  showing: string;
114
- version: string;
115
112
  repository: string;
116
113
  license: string;
114
+ customLicense: string;
117
115
  reviewedVersionLine: string;
118
116
  deniedCode: string;
119
117
  notInCatalogCode: string;
@@ -5,10 +5,6 @@ import type { ShopLocaleKey } from './locales.ts';
5
5
  import type { CatalogEntry, InstallRejectionCode } from '../host/index.ts';
6
6
  /** Tier → locale key, for the entry-card tier badge (§6.2). */
7
7
  export declare function tierKey(tier: CatalogEntry['tier']): ShopLocaleKey;
8
- /** A derived listing has no author-declared catalog section; the shop
9
- * presents it as unclaimed, which is the signal that prompts an author to add
10
- * one (§6.1). Never present a derived entry as though the author wrote it. */
11
- export declare function isUnclaimed(entry: CatalogEntry): boolean;
12
8
  /** Spec §9.3 verbatim — the community-tier acknowledgement. The zh dictionary
13
9
  * states the same facts in its own register; never soften this wording. */
14
10
  export declare const ACKNOWLEDGEMENT_EN = "Once installed, this plugin holds the same privileges as a built-in one: reading and writing your files, running shell commands, and reading and modifying the requests sent to the model. It has not been reviewed.";
@@ -22,6 +18,13 @@ export declare function displayVersion(entry: {
22
18
  source: 'npm' | 'github';
23
19
  version: string;
24
20
  }): string;
21
+ /**
22
+ * Whether the license value is the npm idiom `SEE LICENSE IN <file>` — a
23
+ * valid SPDX form meaning "custom license, text ships in that file". The
24
+ * catalog keeps the author's verbatim value; the shelf renders it as a
25
+ * localized "Custom license" label. Everything else renders verbatim.
26
+ */
27
+ export declare function isCustomLicense(license: string | null): boolean;
25
28
  /** One polled install status (§7.3 wire data), structural. */
26
29
  export interface InstallStatusShape {
27
30
  found: boolean;
@@ -1,8 +1,9 @@
1
1
  /** Catalog fetch, verification, and disk cache — the Host's only network path. */
2
2
  import type { CatalogEntry, DeniedEntry } from './types.ts';
3
3
  /** Highest schemaVersion this build understands; a higher one is refused (§10).
4
- * 3 adds `source` and repo entries (github install channel). */
5
- export declare const SUPPORTED_SCHEMA_VERSION = 3;
4
+ * 3 adds `source` and repo entries (github install channel); 4 adds `subdir`
5
+ * for monorepo-subpackage entries (2026-08-31 hub-borrowings A). */
6
+ export declare const SUPPORTED_SCHEMA_VERSION = 4;
6
7
  export interface CatalogSnapshot {
7
8
  schemaVersion: number;
8
9
  builtAt: string;
@@ -105,13 +105,16 @@ export type ShopUpdateResult = {
105
105
  };
106
106
  /** `shop/installed` entry (§7.3): one installed catalog plugin. `installed`
107
107
  * is the profile manifest's dependency spec verbatim (a range, a tag, or
108
- * `workspace:*`); `outdated` is the Host's verdict that the installed version
109
- * sits behind the catalog's the client never does version math. */
108
+ * `workspace:*`) for a github entry it is the pinned commit the shop
109
+ * installed, falling back to the `github:owner/slug` spec when the pin is
110
+ * unknown; `outdated` is the Host's verdict that the installed version sits
111
+ * behind the catalog's — the client never does version math. */
110
112
  export interface ShopInstalledEntry {
111
113
  name: string;
112
114
  installed: string;
113
115
  latest: string;
114
116
  outdated: boolean;
117
+ enabled: boolean;
115
118
  }
116
119
  /** `shop/catalog` result (§7.3), plus the denied list for the install gate's UI. */
117
120
  export interface ShopCatalogResult {
@@ -177,7 +180,9 @@ export declare class ShopGateway extends TypertRemoteService {
177
180
  private listInventory;
178
181
  /** Enable or disable one installed plugin, hot (§8): a disable writes the
179
182
  * row to the user layer, an enable drops it again so the bundle default
180
- * rules — the CLI's watchUserPatches applies either through HMR. */
183
+ * rules — the CLI's watchUserPatches applies either through HMR. The shop's
184
+ * own row and the framework's bundles are never toggleable: disabling the
185
+ * host chain would break HMR itself. */
181
186
  setEnabled(args: {
182
187
  name: string;
183
188
  enabled: boolean;
@@ -29,6 +29,9 @@ export interface CatalogEntry {
29
29
  source: 'npm' | 'github';
30
30
  /** `owner/slug`; present exactly when `source` is github. */
31
31
  repo?: string;
32
+ /** Subpackage directory inside the repo; present exactly when the entry is
33
+ * a monorepo subpackage rather than the repo root. */
34
+ subdir?: string;
32
35
  }
33
36
  export interface DeniedEntry {
34
37
  name: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-shop",
3
- "version": "0.4.12",
3
+ "version": "0.4.14",
4
4
  "description": "The DeepSeek Harness plugin shop: browse, install, enable, and update dsh plugins from a git-auditable catalog.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -61,6 +61,12 @@
61
61
  "platform": "web"
62
62
  }
63
63
  },
64
+ "scripts": {
65
+ "prepublishOnly": "pnpm build",
66
+ "build": "node -e \"require('node:fs').rmSync('lib',{recursive:true,force:true})\" && tsdown && tsc && tsdown -c tsdown.client.config.ts",
67
+ "test": "tsdown && tsdown -c tsdown.client.config.ts && vitest run",
68
+ "typecheck": "tsdown && tsc --noEmit && tsc -p tsconfig.test.json"
69
+ },
64
70
  "dependencies": {
65
71
  "js-yaml": "^4.2.0",
66
72
  "semver": "^7.6.3",
@@ -97,7 +103,7 @@
97
103
  "@deepseek-ai/dsh-client-ui-slots": "^0.1.1-rc.2",
98
104
  "@deepseek-ai/dsh-home-paths": "^0.1.1-rc.2",
99
105
  "@deepseek-ai/dsh-typert-generator": "^0.1.1-rc.2",
100
- "@deepseek-ai/dsh-typert-protocol": "^0.1.1-rc.2",
106
+ "@deepseek-ai/dsh-typert-protocol": "workspace:^0.1.1-rc.2",
101
107
  "@testing-library/react": "^16.1.0",
102
108
  "@types/js-yaml": "^4.0.9",
103
109
  "@types/node": "^22.9.0",
@@ -111,10 +117,5 @@
111
117
  "tsdown": "^0.22.2",
112
118
  "typescript": "^5.6.3",
113
119
  "vitest": "^2.1.4"
114
- },
115
- "scripts": {
116
- "build": "node -e \"require('node:fs').rmSync('lib',{recursive:true,force:true})\" && tsdown && tsc && tsdown -c tsdown.client.config.ts",
117
- "test": "tsdown && tsdown -c tsdown.client.config.ts && vitest run",
118
- "typecheck": "tsdown && tsc --noEmit && tsc -p tsconfig.test.json"
119
120
  }
120
- }
121
+ }