dsh-plugin-shop 0.4.9 → 0.4.11
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 +9 -5
- package/lib/client.js +104 -85
- package/lib/index.js +88 -10
- package/lib/typert.host.js +16 -13
- package/lib/typert.remote-client.js +14 -11
- package/lib/types/client/locales.d.ts +6 -0
- package/lib/types/client/present.d.ts +5 -0
- package/lib/types/host/catalog.d.ts +3 -2
- package/lib/types/host/index.d.ts +10 -0
- package/lib/types/host/install.d.ts +1 -1
- package/lib/types/host/repo-pins.d.ts +22 -0
- package/lib/types/host/types.d.ts +6 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
dsh plugins, install one with a single confirmation, and manage what you have, from a tab inside Settings.
|
|
7
7
|
|
|
8
8
|
[](https://www.npmjs.com/package/dsh-plugin-shop)
|
|
9
|
+
[](https://LivXue.github.io/dsh-plugin-shop/v1/index.json)
|
|
10
|
+
[](https://LivXue.github.io/dsh-plugin-shop/v1/index.json)
|
|
9
11
|
[](https://github.com/LivXue/dsh-plugin-shop/blob/main/LICENSE)
|
|
10
12
|
[](https://github.com/LivXue/dsh-plugin-shop/actions/workflows/plugin.yml)
|
|
11
13
|
[](https://github.com/LivXue/dsh-plugin-shop/actions/workflows/daily.yml)
|
|
@@ -50,9 +52,9 @@ install them once with `npm install -g @deepseek-ai/dsh pnpm` and verify with
|
|
|
50
52
|
# holds back very recent releases, so a bare `add dsh-plugin-shop` can hand
|
|
51
53
|
# you an older version for a while. Pin the current release — refresh it
|
|
52
54
|
# with `npm view dsh-plugin-shop version`.
|
|
53
|
-
dsh plugin --profile web add dsh-plugin-shop@0.4.
|
|
55
|
+
dsh plugin --profile web add dsh-plugin-shop@0.4.11
|
|
54
56
|
# or straight through npx, nothing installed:
|
|
55
|
-
npx -y @deepseek-ai/dsh plugin --profile web add dsh-plugin-shop@0.4.
|
|
57
|
+
npx -y @deepseek-ai/dsh plugin --profile web add dsh-plugin-shop@0.4.11
|
|
56
58
|
```
|
|
57
59
|
|
|
58
60
|
Replace `web` with your profile if you use another one. Then restart `dsh` once — a
|
|
@@ -82,7 +84,7 @@ ls -1 "${DSH_HOME:-$HOME/.dsh}/profiles" | grep -v '^node_modules$'
|
|
|
82
84
|
cooldown, and deterministic installs are the point of the agent path.
|
|
83
85
|
|
|
84
86
|
```sh
|
|
85
|
-
dsh plugin --profile <profile> add dsh-plugin-shop@0.4.
|
|
87
|
+
dsh plugin --profile <profile> add dsh-plugin-shop@0.4.11
|
|
86
88
|
```
|
|
87
89
|
|
|
88
90
|
**3. Verify — do not skip this.** A zero exit from step 2 means pnpm resolved the
|
|
@@ -144,7 +146,7 @@ Compromising the browser half buys an attacker those nine calls and nothing more
|
|
|
144
146
|
|
|
145
147
|
Built daily from the public npm registry and published as static JSON:
|
|
146
148
|
|
|
147
|
-
- [`/v1/index.json`](https://LivXue.github.io/dsh-plugin-shop/v1/index.json) — the pointer, carrying `schemaVersion`, `builtAt`, and the content hash
|
|
149
|
+
- [`/v1/index.json`](https://LivXue.github.io/dsh-plugin-shop/v1/index.json) — the pointer, carrying `schemaVersion`, `builtAt`, the entry `count` and `rejected` totals (the badges above read them live), and the content hash
|
|
148
150
|
- `/v1/plugins.<sha256>.json` — the data, content-addressed and safe to cache indefinitely
|
|
149
151
|
|
|
150
152
|
The pointer is small enough to poll. The shop verifies the data file's sha256 against
|
|
@@ -163,7 +165,9 @@ actually given — so passing review once cannot buy trust for every future rele
|
|
|
163
165
|
## 🏷️ For plugin authors
|
|
164
166
|
|
|
165
167
|
Add a harvest keyword — `"keywords": ["dsh-plugin"]` or `"keywords": ["deepseek-harness"]` — to
|
|
166
|
-
`package.json` and publish.
|
|
168
|
+
`package.json` and publish. Or, without npm: add the keyword as a GitHub repo *topic* and keep a
|
|
169
|
+
`package.json` at the root with a `name` and `dsh.bundle` — the catalog lists the repo and pins the
|
|
170
|
+
default-branch commit as its version. The daily build finds you; nothing is submitted to this project. Declare a `dsh.catalog` section to control
|
|
167
171
|
your own category, summary and capabilities — or omit it, and the catalog derives a
|
|
168
172
|
listing from your npm `description`.
|
|
169
173
|
|
package/lib/client.js
CHANGED
|
@@ -4279,7 +4279,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4279
4279
|
]),
|
|
4280
4280
|
"metadata": union([literal("declared"), literal("derived")]),
|
|
4281
4281
|
"review": union([_undefined(), object({
|
|
4282
|
-
"reviewedVersion": string(),
|
|
4282
|
+
"reviewedVersion": union([_undefined(), string()]).optional(),
|
|
4283
|
+
"reviewedCommit": union([_undefined(), string()]).optional(),
|
|
4283
4284
|
"reviewer": string(),
|
|
4284
4285
|
"reviewCommit": string(),
|
|
4285
4286
|
"notes": string()
|
|
@@ -4298,7 +4299,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4298
4299
|
"zh": union([_undefined(), string()]).optional()
|
|
4299
4300
|
}),
|
|
4300
4301
|
"capabilities": array(string())
|
|
4301
|
-
})]).optional()
|
|
4302
|
+
})]).optional(),
|
|
4303
|
+
"source": union([literal("npm"), literal("github")]),
|
|
4304
|
+
"repo": union([_undefined(), string()]).optional()
|
|
4302
4305
|
})),
|
|
4303
4306
|
"denied": array(object({
|
|
4304
4307
|
"name": string(),
|
|
@@ -4330,7 +4333,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4330
4333
|
literal("not-in-catalog"),
|
|
4331
4334
|
literal("denied"),
|
|
4332
4335
|
literal("version-mismatch"),
|
|
4333
|
-
literal("needs-acknowledgement")
|
|
4336
|
+
literal("needs-acknowledgement"),
|
|
4337
|
+
literal("git-missing")
|
|
4334
4338
|
]),
|
|
4335
4339
|
"detail": string()
|
|
4336
4340
|
})]);
|
|
@@ -4410,7 +4414,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4410
4414
|
},
|
|
4411
4415
|
sourceLocation: {
|
|
4412
4416
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4413
|
-
"line":
|
|
4417
|
+
"line": 270,
|
|
4414
4418
|
"column": 9
|
|
4415
4419
|
}
|
|
4416
4420
|
},
|
|
@@ -4428,7 +4432,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4428
4432
|
},
|
|
4429
4433
|
sourceLocation: {
|
|
4430
4434
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4431
|
-
"line":
|
|
4435
|
+
"line": 374,
|
|
4432
4436
|
"column": 9
|
|
4433
4437
|
}
|
|
4434
4438
|
},
|
|
@@ -4456,7 +4460,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4456
4460
|
},
|
|
4457
4461
|
sourceLocation: {
|
|
4458
4462
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4459
|
-
"line":
|
|
4463
|
+
"line": 296,
|
|
4460
4464
|
"column": 9
|
|
4461
4465
|
}
|
|
4462
4466
|
},
|
|
@@ -4483,7 +4487,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4483
4487
|
},
|
|
4484
4488
|
sourceLocation: {
|
|
4485
4489
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4486
|
-
"line":
|
|
4490
|
+
"line": 363,
|
|
4487
4491
|
"column": 3
|
|
4488
4492
|
}
|
|
4489
4493
|
},
|
|
@@ -4501,7 +4505,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4501
4505
|
},
|
|
4502
4506
|
sourceLocation: {
|
|
4503
4507
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4504
|
-
"line":
|
|
4508
|
+
"line": 469,
|
|
4505
4509
|
"column": 9
|
|
4506
4510
|
}
|
|
4507
4511
|
},
|
|
@@ -4528,7 +4532,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4528
4532
|
},
|
|
4529
4533
|
sourceLocation: {
|
|
4530
4534
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4531
|
-
"line":
|
|
4535
|
+
"line": 239,
|
|
4532
4536
|
"column": 3
|
|
4533
4537
|
}
|
|
4534
4538
|
},
|
|
@@ -4556,7 +4560,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4556
4560
|
},
|
|
4557
4561
|
sourceLocation: {
|
|
4558
4562
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4559
|
-
"line":
|
|
4563
|
+
"line": 429,
|
|
4560
4564
|
"column": 9
|
|
4561
4565
|
}
|
|
4562
4566
|
},
|
|
@@ -4583,7 +4587,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4583
4587
|
},
|
|
4584
4588
|
sourceLocation: {
|
|
4585
4589
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4586
|
-
"line":
|
|
4590
|
+
"line": 518,
|
|
4587
4591
|
"column": 9
|
|
4588
4592
|
}
|
|
4589
4593
|
},
|
|
@@ -4601,7 +4605,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4601
4605
|
},
|
|
4602
4606
|
sourceLocation: {
|
|
4603
4607
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4604
|
-
"line":
|
|
4608
|
+
"line": 503,
|
|
4605
4609
|
"column": 9
|
|
4606
4610
|
}
|
|
4607
4611
|
}
|
|
@@ -4617,12 +4621,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4617
4621
|
case "community": return "tierCommunity";
|
|
4618
4622
|
}
|
|
4619
4623
|
}
|
|
4620
|
-
/** A derived listing has no author-declared catalog section; the shop
|
|
4621
|
-
* presents it as unclaimed, which is the signal that prompts an author to add
|
|
4622
|
-
* one (§6.1). Never present a derived entry as though the author wrote it. */
|
|
4623
|
-
function isUnclaimed(entry) {
|
|
4624
|
-
return entry.metadata === "derived";
|
|
4625
|
-
}
|
|
4626
4624
|
/** Spec §9.3 verbatim — the community-tier acknowledgement. The zh dictionary
|
|
4627
4625
|
* states the same facts in its own register; never soften this wording. */
|
|
4628
4626
|
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.";
|
|
@@ -4636,8 +4634,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4636
4634
|
case "not-in-catalog": return "notInCatalogCode";
|
|
4637
4635
|
case "version-mismatch": return "versionMismatchCode";
|
|
4638
4636
|
case "needs-acknowledgement": return "needsAcknowledgementCode";
|
|
4637
|
+
case "git-missing": return "gitMissingCode";
|
|
4639
4638
|
}
|
|
4640
4639
|
}
|
|
4640
|
+
/** The version a card displays: full for npm, the short commit for github. */
|
|
4641
|
+
function displayVersion(entry) {
|
|
4642
|
+
return entry.source === "github" ? entry.version.slice(0, 7) : entry.version;
|
|
4643
|
+
}
|
|
4641
4644
|
/** §7.2 once-per-second poll cadence, as a named constant. */
|
|
4642
4645
|
const INSTALL_POLL_MS = 1e3;
|
|
4643
4646
|
/** How long the check-update button reports "up to date" after a re-check
|
|
@@ -4695,10 +4698,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4695
4698
|
"shop",
|
|
4696
4699
|
"marketplace"
|
|
4697
4700
|
];
|
|
4698
|
-
/** Competing marketplaces whose names carry no store/market keyword and so
|
|
4699
|
-
* escape the pattern rules below. Named explicitly rather than guessed:
|
|
4700
|
-
* `dsh-plugin` is the npm package of github.com/dshplugin/dsh-plugin-hub —
|
|
4701
|
-
* a community plugin marketplace for DeepSeek Harness. */
|
|
4702
4701
|
/**
|
|
4703
4702
|
* Competing plugin marketplaces whose names carry no store/market keyword
|
|
4704
4703
|
* and so escape the pattern rules below (spec §7.2). Each is a real npm
|
|
@@ -4804,7 +4803,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4804
4803
|
tierVerified: "已审核",
|
|
4805
4804
|
tierVerifiedStale: "审核版本过期",
|
|
4806
4805
|
tierCommunity: "社区",
|
|
4807
|
-
unclaimed: "作者未认领",
|
|
4808
4806
|
capabilitiesNote: "作者自述,无强制约束",
|
|
4809
4807
|
install: "安装",
|
|
4810
4808
|
confirm: "确认",
|
|
@@ -4827,6 +4825,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4827
4825
|
notInCatalogCode: "不在目录中",
|
|
4828
4826
|
versionMismatchCode: "版本不匹配",
|
|
4829
4827
|
needsAcknowledgementCode: "需要确认",
|
|
4828
|
+
gitMissingCode: "缺少 git",
|
|
4829
|
+
githubSource: "来自 GitHub 仓库安装",
|
|
4830
4830
|
installedSection: "已安装",
|
|
4831
4831
|
installedVersion: "已安装 v{version}",
|
|
4832
4832
|
latestVersion: "最新 v{version}",
|
|
@@ -4875,7 +4875,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4875
4875
|
tierVerified: "Verified",
|
|
4876
4876
|
tierVerifiedStale: "Reviewed version stale",
|
|
4877
4877
|
tierCommunity: "Community",
|
|
4878
|
-
unclaimed: "not claimed by its author",
|
|
4879
4878
|
capabilitiesNote: "self-declared by the author, not enforced",
|
|
4880
4879
|
install: "Install",
|
|
4881
4880
|
confirm: "Confirm",
|
|
@@ -4898,6 +4897,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4898
4897
|
notInCatalogCode: "Not in the catalog",
|
|
4899
4898
|
versionMismatchCode: "Version mismatch",
|
|
4900
4899
|
needsAcknowledgementCode: "Acknowledgement required",
|
|
4900
|
+
gitMissingCode: "git missing",
|
|
4901
|
+
githubSource: "installed from a GitHub repository",
|
|
4901
4902
|
installedSection: "Installed",
|
|
4902
4903
|
installedVersion: "installed v{version}",
|
|
4903
4904
|
latestVersion: "latest v{version}",
|
|
@@ -5074,7 +5075,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5074
5075
|
}
|
|
5075
5076
|
//#endregion
|
|
5076
5077
|
//#region \0dsh-plugin-shop-css:e3675a89
|
|
5077
|
-
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}\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: repeat(auto-fill, minmax(260px, 1fr));\n gap: 12px;\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.s30e662_skeletonCover {\n display: block;\n height: 26px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 10%, transparent);\n}\n\n.s8c7d6f_skeletonName {\n width: 55%;\n height: 14px;\n margin: 12px 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 14px;\n border-radius: 4px;\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.sf657f5_cards {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));\n gap: 12px;\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 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 240px;\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.s9e2bec_cardCover {\n display: block;\n padding: 5px 12px 5px 12px;\n border-bottom: 1px solid var(--dsw-alias-border-l1);\n background: color-mix(in srgb, var(--spine-hue, #8B8E96) 12%, transparent);\n}\n\n.s1a7c5b_coverLabel {\n display: block;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--spine-hue, #8B8E96);\n}\n\n.sa97234_entryHeader {\n display: flex;\n flex-direction: column;\n align-items: stretch;\n gap: 8px;\n padding: 10px 12px 6px;\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 owns the full header width; badges sit on their own line below. */\n.sc3f149_name {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 13px;\n font-weight: 600;\n overflow-wrap: anywhere;\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.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.s23e3e1_unclaimedBadge {\n padding: 1px 7px;\n border-radius: 999px;\n border: 1px dashed var(--dsw-alias-border-l2);\n font-size: 11px;\n line-height: 1.6;\n color: var(--dsw-alias-label-secondary);\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 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";
|
|
5078
|
+
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";
|
|
5078
5079
|
if (typeof document !== "undefined" && !document.querySelector("style[data-plugin-css=\"e3675a89\"]")) {
|
|
5079
5080
|
const tag = document.createElement("style");
|
|
5080
5081
|
tag.dataset.pluginCss = "e3675a89";
|
|
@@ -5091,10 +5092,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5091
5092
|
"srOnly": "s3a0ece_srOnly",
|
|
5092
5093
|
"skeletonGrid": "se0c3b4_skeletonGrid",
|
|
5093
5094
|
"skeletonCard": "s520416_skeletonCard",
|
|
5094
|
-
"skeletonCover": "s30e662_skeletonCover",
|
|
5095
5095
|
"skeletonName": "s8c7d6f_skeletonName",
|
|
5096
5096
|
"skeletonSummary": "sf5179e_skeletonSummary",
|
|
5097
5097
|
"skeletonSummaryShort": "sbb0d25_skeletonSummaryShort",
|
|
5098
|
+
"skeletonActions": "s96e910_skeletonActions",
|
|
5098
5099
|
"actionButton": "s73242a_actionButton",
|
|
5099
5100
|
"toolbar": "s646509_toolbar",
|
|
5100
5101
|
"toolbarRight": "s745e3c_toolbarRight",
|
|
@@ -5118,13 +5119,17 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5118
5119
|
"cardsSentry": "sd7ee01_cardsSentry",
|
|
5119
5120
|
"showingLine": "s705cc6_showingLine",
|
|
5120
5121
|
"cardSpine": "s4ce25d_cardSpine",
|
|
5121
|
-
"
|
|
5122
|
-
"coverLabel": "s1a7c5b_coverLabel",
|
|
5122
|
+
"categoryBadge": "s9c8eaa_categoryBadge",
|
|
5123
5123
|
"entryHeader": "sa97234_entryHeader",
|
|
5124
5124
|
"name": "sc3f149_name",
|
|
5125
5125
|
"badges": "s513026_badges",
|
|
5126
|
+
"cardActions": "s9b5048_cardActions",
|
|
5127
|
+
"gate": "sc4f2ac_gate",
|
|
5128
|
+
"installPanel": "s4d374c_installPanel",
|
|
5129
|
+
"installedActions": "sf53ca0_installedActions",
|
|
5130
|
+
"cardVersion": "sc6ec6e_cardVersion",
|
|
5131
|
+
"sourceBadge": "s71f10f_sourceBadge",
|
|
5126
5132
|
"tierBadge": "s5e82cb_tierBadge",
|
|
5127
|
-
"unclaimedBadge": "s23e3e1_unclaimedBadge",
|
|
5128
5133
|
"starsBadge": "sff43b4_starsBadge",
|
|
5129
5134
|
"chevron": "s73f7fb_chevron",
|
|
5130
5135
|
"chevronOpen": "s61393c_chevronOpen",
|
|
@@ -5140,15 +5145,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5140
5145
|
"detailRows": "se98179_detailRows",
|
|
5141
5146
|
"detailRow": "sfb4d2f_detailRow",
|
|
5142
5147
|
"reviewedLine": "sd58c6d_reviewedLine",
|
|
5143
|
-
"installPanel": "s4d374c_installPanel",
|
|
5144
5148
|
"installButton": "se7aaed_installButton",
|
|
5145
5149
|
"confirmButton": "sb43a49_confirmButton",
|
|
5146
5150
|
"cancelButton": "s6e45d1_cancelButton",
|
|
5147
5151
|
"installedLabel": "sb6fc74_installedLabel",
|
|
5148
|
-
"installedActions": "sf53ca0_installedActions",
|
|
5149
5152
|
"uninstallButton": "s96967f_uninstallButton",
|
|
5150
5153
|
"restartButton": "s6ac237_restartButton",
|
|
5151
|
-
"gate": "sc4f2ac_gate",
|
|
5152
5154
|
"gateTitle": "s559c38_gateTitle",
|
|
5153
5155
|
"gateBody": "s3c0ace_gateBody",
|
|
5154
5156
|
"gateActions": "s4b2373_gateActions",
|
|
@@ -5193,7 +5195,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5193
5195
|
});
|
|
5194
5196
|
}
|
|
5195
5197
|
/** One entry card: the category spine and cover band, the expandable header
|
|
5196
|
-
* (name, tier, category
|
|
5198
|
+
* (name, tier, category), the plain-text summary in both
|
|
5197
5199
|
* languages, the self-declared capabilities, the detail section, and the
|
|
5198
5200
|
* install controls. An installed plugin's card carries its installed row:
|
|
5199
5201
|
* current → the non-interactive installed label, behind → the update button;
|
|
@@ -5212,14 +5214,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5212
5214
|
className: _dsh_plugin_shop_css_e3675a89_default.cardSpine,
|
|
5213
5215
|
"aria-hidden": "true"
|
|
5214
5216
|
}),
|
|
5215
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5216
|
-
className: _dsh_plugin_shop_css_e3675a89_default.cardCover,
|
|
5217
|
-
"aria-hidden": "true",
|
|
5218
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5219
|
-
className: _dsh_plugin_shop_css_e3675a89_default.coverLabel,
|
|
5220
|
-
children: t(categoryKey(entry))
|
|
5221
|
-
})
|
|
5222
|
-
}),
|
|
5223
5217
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5224
5218
|
type: "button",
|
|
5225
5219
|
className: _dsh_plugin_shop_css_e3675a89_default.entryHeader,
|
|
@@ -5232,15 +5226,33 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5232
5226
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5233
5227
|
className: _dsh_plugin_shop_css_e3675a89_default.badges,
|
|
5234
5228
|
children: [
|
|
5229
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5230
|
+
className: _dsh_plugin_shop_css_e3675a89_default.categoryBadge,
|
|
5231
|
+
children: t(categoryKey(entry))
|
|
5232
|
+
}),
|
|
5233
|
+
entry.source === "github" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5234
|
+
className: _dsh_plugin_shop_css_e3675a89_default.sourceBadge,
|
|
5235
|
+
role: "img",
|
|
5236
|
+
"aria-label": t("githubSource"),
|
|
5237
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
5238
|
+
viewBox: "0 0 16 16",
|
|
5239
|
+
width: "12",
|
|
5240
|
+
height: "12",
|
|
5241
|
+
"aria-hidden": "true",
|
|
5242
|
+
fill: "currentColor",
|
|
5243
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z" })
|
|
5244
|
+
})
|
|
5245
|
+
}),
|
|
5246
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5247
|
+
className: _dsh_plugin_shop_css_e3675a89_default.cardVersion,
|
|
5248
|
+
"data-card-version": true,
|
|
5249
|
+
children: ["v", displayVersion(entry)]
|
|
5250
|
+
}),
|
|
5235
5251
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5236
5252
|
className: _dsh_plugin_shop_css_e3675a89_default.tierBadge,
|
|
5237
5253
|
"data-tier": entry.tier,
|
|
5238
5254
|
children: t(tierKey(entry.tier))
|
|
5239
5255
|
}),
|
|
5240
|
-
isUnclaimed(entry) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5241
|
-
className: _dsh_plugin_shop_css_e3675a89_default.unclaimedBadge,
|
|
5242
|
-
children: t("unclaimed")
|
|
5243
|
-
}),
|
|
5244
5256
|
stars !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5245
5257
|
className: _dsh_plugin_shop_css_e3675a89_default.starsBadge,
|
|
5246
5258
|
role: "img",
|
|
@@ -5262,7 +5274,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5262
5274
|
className: open ? `${_dsh_plugin_shop_css_e3675a89_default.summaryZh} ${_dsh_plugin_shop_css_e3675a89_default.summaryZhExpanded}` : _dsh_plugin_shop_css_e3675a89_default.summaryZh,
|
|
5263
5275
|
children: summary.zh
|
|
5264
5276
|
}),
|
|
5265
|
-
entry.catalog !== void 0 && entry.catalog.capabilities.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5277
|
+
open && entry.catalog !== void 0 && entry.catalog.capabilities.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5266
5278
|
className: _dsh_plugin_shop_css_e3675a89_default.capabilitiesBlock,
|
|
5267
5279
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5268
5280
|
className: _dsh_plugin_shop_css_e3675a89_default.capabilitiesNote,
|
|
@@ -5303,39 +5315,39 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5303
5315
|
current: entry.version
|
|
5304
5316
|
})
|
|
5305
5317
|
})]
|
|
5306
|
-
}),
|
|
5307
|
-
installed === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallPanel, {
|
|
5308
|
-
name: entry.name,
|
|
5309
|
-
version: entry.version,
|
|
5310
|
-
tier: entry.tier,
|
|
5311
|
-
t,
|
|
5312
|
-
install,
|
|
5313
|
-
installStatus,
|
|
5314
|
-
restart
|
|
5315
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5316
|
-
className: _dsh_plugin_shop_css_e3675a89_default.installedActions,
|
|
5317
|
-
children: [installed.outdated ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallPanel, {
|
|
5318
|
-
name: entry.name,
|
|
5319
|
-
version: entry.version,
|
|
5320
|
-
tier: entry.tier,
|
|
5321
|
-
variant: "update",
|
|
5322
|
-
t,
|
|
5323
|
-
install,
|
|
5324
|
-
installStatus,
|
|
5325
|
-
restart
|
|
5326
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5327
|
-
className: _dsh_plugin_shop_css_e3675a89_default.installedLabel,
|
|
5328
|
-
"data-shop-installed": true,
|
|
5329
|
-
children: t("installed")
|
|
5330
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UninstallPanel, {
|
|
5331
|
-
name: entry.name,
|
|
5332
|
-
t,
|
|
5333
|
-
uninstall,
|
|
5334
|
-
installStatus,
|
|
5335
|
-
restart
|
|
5336
|
-
})]
|
|
5337
5318
|
})
|
|
5338
5319
|
]
|
|
5320
|
+
}),
|
|
5321
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5322
|
+
className: _dsh_plugin_shop_css_e3675a89_default.cardActions,
|
|
5323
|
+
children: installed === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallPanel, {
|
|
5324
|
+
name: entry.name,
|
|
5325
|
+
version: entry.version,
|
|
5326
|
+
tier: entry.tier,
|
|
5327
|
+
t,
|
|
5328
|
+
install,
|
|
5329
|
+
installStatus,
|
|
5330
|
+
restart
|
|
5331
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [installed.outdated ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallPanel, {
|
|
5332
|
+
name: entry.name,
|
|
5333
|
+
version: entry.version,
|
|
5334
|
+
tier: entry.tier,
|
|
5335
|
+
variant: "update",
|
|
5336
|
+
t,
|
|
5337
|
+
install,
|
|
5338
|
+
installStatus,
|
|
5339
|
+
restart
|
|
5340
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5341
|
+
className: _dsh_plugin_shop_css_e3675a89_default.installedLabel,
|
|
5342
|
+
"data-shop-installed": true,
|
|
5343
|
+
children: t("installed")
|
|
5344
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UninstallPanel, {
|
|
5345
|
+
name: entry.name,
|
|
5346
|
+
t,
|
|
5347
|
+
uninstall,
|
|
5348
|
+
installStatus,
|
|
5349
|
+
restart
|
|
5350
|
+
})] })
|
|
5339
5351
|
})
|
|
5340
5352
|
]
|
|
5341
5353
|
});
|
|
@@ -5616,7 +5628,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5616
5628
|
* installed-state RPC behind it — the tab assumes an installed plugin is on,
|
|
5617
5629
|
* so the first toggle sends the inverted value. After a successful `setEnabled`
|
|
5618
5630
|
* the §8 hot note renders. */
|
|
5619
|
-
function OutdatedRow({ row, tier, t, setEnabled, install, installStatus, restart }) {
|
|
5631
|
+
function OutdatedRow({ row, tier, source, t, setEnabled, install, installStatus, restart }) {
|
|
5620
5632
|
const [enabled, setEnabledState] = (0, react.useState)(true);
|
|
5621
5633
|
const [toggle, setToggle] = (0, react.useState)({ kind: "idle" });
|
|
5622
5634
|
const onToggle = async () => {
|
|
@@ -5655,7 +5667,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5655
5667
|
className: _dsh_plugin_shop_css_e3675a89_default.outdatedVersions,
|
|
5656
5668
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5657
5669
|
className: _dsh_plugin_shop_css_e3675a89_default.outdatedVersion,
|
|
5658
|
-
children: t("installedVersion", { version: row.installed })
|
|
5670
|
+
children: t("installedVersion", { version: source === "github" ? row.installed.slice(0, 7) : row.installed })
|
|
5659
5671
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5660
5672
|
className: _dsh_plugin_shop_css_e3675a89_default.outdatedVersion,
|
|
5661
5673
|
children: t("latestVersion", { version: row.latest })
|
|
@@ -5705,7 +5717,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5705
5717
|
* tier for the update gate is looked up from the catalog by name (community →
|
|
5706
5718
|
* acknowledgement); an entry absent from the catalog defaults to the
|
|
5707
5719
|
* community gate (the safer read). */
|
|
5708
|
-
function OutdatedSection({ state, tiers, t, setEnabled, install, installStatus, restart }) {
|
|
5720
|
+
function OutdatedSection({ state, tiers, sources, t, setEnabled, install, installStatus, restart }) {
|
|
5709
5721
|
if (state.kind === "loading") return null;
|
|
5710
5722
|
if (state.kind === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5711
5723
|
className: _dsh_plugin_shop_css_e3675a89_default.stateLine,
|
|
@@ -5725,6 +5737,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5725
5737
|
children: outdated.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OutdatedRow, {
|
|
5726
5738
|
row,
|
|
5727
5739
|
tier: tiers.get(row.name) ?? "community",
|
|
5740
|
+
source: sources.get(row.name) ?? "npm",
|
|
5728
5741
|
t,
|
|
5729
5742
|
setEnabled,
|
|
5730
5743
|
install,
|
|
@@ -5827,7 +5840,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5827
5840
|
if (catalogState.kind !== "ready") return [];
|
|
5828
5841
|
const q = query.trim().toLowerCase();
|
|
5829
5842
|
return catalogState.result.plugins.filter((entry) => {
|
|
5830
|
-
if (isShopLike(entry.name)) return false;
|
|
5843
|
+
if (isShopLike(entry.name) || entry.repo !== void 0 && isShopLike(entry.repo)) return false;
|
|
5831
5844
|
if (category === "installed") {
|
|
5832
5845
|
if (!installedByName.has(entry.name)) return false;
|
|
5833
5846
|
} else if (category !== null && categoryKey(entry) !== categoryLocaleKey(category)) return false;
|
|
@@ -5863,7 +5876,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5863
5876
|
const categoryCounts = (0, react.useMemo)(() => {
|
|
5864
5877
|
const counts = /* @__PURE__ */ new Map();
|
|
5865
5878
|
if (catalogState.kind === "ready") for (const entry of catalogState.result.plugins) {
|
|
5866
|
-
if (isShopLike(entry.name)) continue;
|
|
5879
|
+
if (isShopLike(entry.name) || entry.repo !== void 0 && isShopLike(entry.repo)) continue;
|
|
5867
5880
|
const key = categoryKey(entry);
|
|
5868
5881
|
const bare = CATEGORY_ORDER.find((c) => categoryLocaleKey(c) === key);
|
|
5869
5882
|
if (bare !== void 0) counts.set(bare, (counts.get(bare) ?? 0) + 1);
|
|
@@ -5879,6 +5892,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5879
5892
|
if (catalogState.kind === "ready") for (const entry of catalogState.result.plugins) map.set(entry.name, entry.tier);
|
|
5880
5893
|
return map;
|
|
5881
5894
|
}, [catalogState]);
|
|
5895
|
+
const sources = (0, react.useMemo)(() => {
|
|
5896
|
+
const map = /* @__PURE__ */ new Map();
|
|
5897
|
+
if (catalogState.kind === "ready") for (const entry of catalogState.result.plugins) map.set(entry.name, entry.source);
|
|
5898
|
+
return map;
|
|
5899
|
+
}, [catalogState]);
|
|
5882
5900
|
if (catalogState.kind === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5883
5901
|
className: _dsh_plugin_shop_css_e3675a89_default.panel,
|
|
5884
5902
|
"data-shop-tab": true,
|
|
@@ -5893,10 +5911,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5893
5911
|
children: Array.from({ length: 8 }, (_, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5894
5912
|
className: _dsh_plugin_shop_css_e3675a89_default.skeletonCard,
|
|
5895
5913
|
children: [
|
|
5896
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_e3675a89_default.skeletonCover }),
|
|
5897
5914
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_e3675a89_default.skeletonName }),
|
|
5898
5915
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_e3675a89_default.skeletonSummary }),
|
|
5899
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_e3675a89_default.skeletonSummaryShort })
|
|
5916
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_e3675a89_default.skeletonSummaryShort }),
|
|
5917
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_e3675a89_default.skeletonActions })
|
|
5900
5918
|
]
|
|
5901
5919
|
}, i))
|
|
5902
5920
|
})]
|
|
@@ -6107,7 +6125,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6107
6125
|
className: _dsh_plugin_shop_css_e3675a89_default.cards,
|
|
6108
6126
|
children: [visible.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EntryCard, {
|
|
6109
6127
|
entry,
|
|
6110
|
-
stars: stars[entry.name],
|
|
6128
|
+
stars: stars[entry.repo ?? entry.name],
|
|
6111
6129
|
installed: installedByName.get(entry.name),
|
|
6112
6130
|
t,
|
|
6113
6131
|
install,
|
|
@@ -6131,6 +6149,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6131
6149
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(OutdatedSection, {
|
|
6132
6150
|
state: installedState,
|
|
6133
6151
|
tiers,
|
|
6152
|
+
sources,
|
|
6134
6153
|
t,
|
|
6135
6154
|
setEnabled,
|
|
6136
6155
|
install,
|
package/lib/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
2
2
|
import { loadOptionalPatches, readProfileManifest, resolveProfileDir } from "@deepseek-ai/dsh-app-boot";
|
|
3
3
|
import { lt, minVersion, valid } from "semver";
|
|
4
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, realpathSync, renameSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
4
6
|
import { fileURLToPath } from "node:url";
|
|
5
7
|
import { basename, dirname, join } from "node:path";
|
|
6
|
-
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, realpathSync, renameSync, writeFileSync } from "node:fs";
|
|
7
8
|
import { createHash, randomUUID } from "node:crypto";
|
|
8
9
|
import { z } from "zod";
|
|
9
|
-
import { spawn } from "node:child_process";
|
|
10
10
|
import { dump } from "js-yaml";
|
|
11
11
|
//#region src/own-version.ts
|
|
12
12
|
/** The shop's own published version, read from the package.json that ships
|
|
@@ -37,7 +37,8 @@ const entrySchema = z.object({
|
|
|
37
37
|
]),
|
|
38
38
|
metadata: z.enum(["declared", "derived"]),
|
|
39
39
|
review: z.object({
|
|
40
|
-
reviewedVersion: z.string(),
|
|
40
|
+
reviewedVersion: z.string().optional(),
|
|
41
|
+
reviewedCommit: z.string().optional(),
|
|
41
42
|
reviewer: z.string(),
|
|
42
43
|
reviewCommit: z.string(),
|
|
43
44
|
notes: z.string()
|
|
@@ -56,7 +57,9 @@ const entrySchema = z.object({
|
|
|
56
57
|
zh: z.string().optional()
|
|
57
58
|
}),
|
|
58
59
|
capabilities: z.array(z.string())
|
|
59
|
-
}).optional()
|
|
60
|
+
}).optional(),
|
|
61
|
+
source: z.enum(["npm", "github"]).default("npm"),
|
|
62
|
+
repo: z.string().optional()
|
|
60
63
|
});
|
|
61
64
|
const dataSchema = z.object({
|
|
62
65
|
schemaVersion: z.number(),
|
|
@@ -140,13 +143,13 @@ async function loadCatalog(options) {
|
|
|
140
143
|
const readCached = () => {
|
|
141
144
|
try {
|
|
142
145
|
const pointer = pointerSchema.parse(JSON.parse(fsImpl.read(indexPath)));
|
|
143
|
-
if (pointer.schemaVersion >
|
|
146
|
+
if (pointer.schemaVersion > 3) throw new Error(`catalog schemaVersion ${pointer.schemaVersion} is newer than this build supports (3)`);
|
|
144
147
|
const dataPath = join(cacheDir, basename(pointer.plugins.url));
|
|
145
148
|
const dataText = fsImpl.read(dataPath);
|
|
146
149
|
const actual = createHash("sha256").update(dataText).digest("hex");
|
|
147
150
|
if (actual !== pointer.plugins.sha256) throw new Error(`cached catalog data failed integrity check: expected ${pointer.plugins.sha256}, got ${actual}`);
|
|
148
151
|
const data = dataSchema.parse(JSON.parse(dataText));
|
|
149
|
-
if (data.schemaVersion >
|
|
152
|
+
if (data.schemaVersion > 3) throw new Error(`catalog schemaVersion ${data.schemaVersion} is newer than this build supports (3)`);
|
|
150
153
|
let stars = {};
|
|
151
154
|
if (pointer.stars !== void 0) try {
|
|
152
155
|
const starsText = fsImpl.read(join(cacheDir, basename(pointer.stars.url)));
|
|
@@ -187,7 +190,7 @@ async function loadCatalog(options) {
|
|
|
187
190
|
throw error;
|
|
188
191
|
}
|
|
189
192
|
const pointer = pointerSchema.parse(JSON.parse(pointerText));
|
|
190
|
-
if (pointer.schemaVersion >
|
|
193
|
+
if (pointer.schemaVersion > 3) throw new Error(`catalog schemaVersion ${pointer.schemaVersion} is newer than this build supports (3)`);
|
|
191
194
|
const dataUrl = resolveDataUrl(baseUrl, pointer.plugins.url);
|
|
192
195
|
let dataText;
|
|
193
196
|
try {
|
|
@@ -205,7 +208,7 @@ async function loadCatalog(options) {
|
|
|
205
208
|
const actual = createHash("sha256").update(dataText).digest("hex");
|
|
206
209
|
if (actual !== pointer.plugins.sha256) throw new Error(`catalog data failed integrity check: expected ${pointer.plugins.sha256}, got ${actual}`);
|
|
207
210
|
const data = dataSchema.parse(JSON.parse(dataText));
|
|
208
|
-
if (data.schemaVersion >
|
|
211
|
+
if (data.schemaVersion > 3) throw new Error(`catalog schemaVersion ${data.schemaVersion} is newer than this build supports (3)`);
|
|
209
212
|
let stars = {};
|
|
210
213
|
if (pointer.stars !== void 0) try {
|
|
211
214
|
const starsResponse = await fetchImpl(resolveDataUrl(baseUrl, pointer.stars.url));
|
|
@@ -497,6 +500,25 @@ async function fetchLatestVersion(fetchFn = fetch) {
|
|
|
497
500
|
}
|
|
498
501
|
}
|
|
499
502
|
//#endregion
|
|
503
|
+
//#region src/host/repo-pins.ts
|
|
504
|
+
/** Read the pins file; any irregularity degrades to an empty record. */
|
|
505
|
+
function readRepoPins(fs, path) {
|
|
506
|
+
if (!fs.exists(path)) return {};
|
|
507
|
+
try {
|
|
508
|
+
const parsed = JSON.parse(fs.read(path));
|
|
509
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
|
|
510
|
+
const out = {};
|
|
511
|
+
for (const [name, commit] of Object.entries(parsed)) if (typeof commit === "string" && /^[0-9a-f]{40}$/.test(commit)) out[name] = commit;
|
|
512
|
+
return out;
|
|
513
|
+
} catch {
|
|
514
|
+
return {};
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
/** Write the pins file atomically enough for its purpose (a cache). */
|
|
518
|
+
function writeRepoPins(fs, path, pins) {
|
|
519
|
+
fs.write(path, `${JSON.stringify(pins, null, 2)}\n`);
|
|
520
|
+
}
|
|
521
|
+
//#endregion
|
|
500
522
|
//#region src/host/profile.ts
|
|
501
523
|
/** Profile directory discovery and user-layer writes (§8: hot enable/disable). */
|
|
502
524
|
/**
|
|
@@ -759,6 +781,8 @@ let ShopGateway = (() => {
|
|
|
759
781
|
restartExitDelayMs;
|
|
760
782
|
restartParentPid;
|
|
761
783
|
latestVersion;
|
|
784
|
+
hasGit;
|
|
785
|
+
pinFs;
|
|
762
786
|
/** The install gate runs against the last loaded snapshot, never a fresh
|
|
763
787
|
* fetch per request (§7.2: the Host's cached snapshot is the truth). */
|
|
764
788
|
/** Finished install records retained, so a poll sees the true terminal
|
|
@@ -786,6 +810,19 @@ let ShopGateway = (() => {
|
|
|
786
810
|
this.restartExitDelayMs = options.restartExitDelayMs ?? ShopGateway.RESTART_EXIT_DELAY_MS;
|
|
787
811
|
this.restartParentPid = options.restartParentPid ?? process.pid;
|
|
788
812
|
this.latestVersion = options.fetchLatestVersion ?? (() => fetchLatestVersion());
|
|
813
|
+
this.hasGit = options.hasGit ?? (() => spawnSync("git", ["--version"], { stdio: "ignore" }).status === 0);
|
|
814
|
+
this.pinFs = options.pinFs ?? {
|
|
815
|
+
exists: (path) => existsSync(path),
|
|
816
|
+
read: (path) => readFileSync(path, "utf8"),
|
|
817
|
+
write: (path, data) => {
|
|
818
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
819
|
+
writeFileSync(path, data);
|
|
820
|
+
}
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
/** The pins file lives in the shop's own cache, next to the catalog cache. */
|
|
824
|
+
pinsPath() {
|
|
825
|
+
return join(this.rowConfig().cacheDir, "github-pins.json");
|
|
789
826
|
}
|
|
790
827
|
/** The boot's Loader root directory (the active profile's `cordis.yml`
|
|
791
828
|
* directory, carried on `ctx.baseUrl`), when present. A `link:` install
|
|
@@ -882,12 +919,39 @@ let ShopGateway = (() => {
|
|
|
882
919
|
code: verdict.code,
|
|
883
920
|
detail: verdict.detail
|
|
884
921
|
};
|
|
922
|
+
const entry = this.lastSnapshot.entries.find((e) => e.name === args.name);
|
|
923
|
+
if (entry === void 0) return {
|
|
924
|
+
ok: false,
|
|
925
|
+
code: "not-in-catalog",
|
|
926
|
+
detail: `dsh-plugin-shop: ${args.name} is not in the catalog`
|
|
927
|
+
};
|
|
928
|
+
let spec;
|
|
929
|
+
if (entry.source === "github") {
|
|
930
|
+
if (entry.repo === void 0 || !/^[0-9a-f]{40}$/.test(args.version)) return {
|
|
931
|
+
ok: false,
|
|
932
|
+
code: "version-mismatch",
|
|
933
|
+
detail: `dsh-plugin-shop: ${args.name} has no installable commit`
|
|
934
|
+
};
|
|
935
|
+
if (!this.hasGit()) return {
|
|
936
|
+
ok: false,
|
|
937
|
+
code: "git-missing",
|
|
938
|
+
detail: "dsh-plugin-shop: git is not on PATH, which github installs require; install git and retry"
|
|
939
|
+
};
|
|
940
|
+
spec = `github:${entry.repo}#${args.version}`;
|
|
941
|
+
} else spec = `${args.name}@${args.version}`;
|
|
885
942
|
const running = startInstall({
|
|
886
943
|
profile: this.profile,
|
|
887
|
-
spec
|
|
944
|
+
spec,
|
|
888
945
|
dshBin: this.dshBin,
|
|
889
946
|
expectedName: args.name
|
|
890
947
|
});
|
|
948
|
+
if (entry.source === "github") {
|
|
949
|
+
const pins = readRepoPins(this.pinFs, this.pinsPath());
|
|
950
|
+
writeRepoPins(this.pinFs, this.pinsPath(), {
|
|
951
|
+
...pins,
|
|
952
|
+
[args.name]: args.version
|
|
953
|
+
});
|
|
954
|
+
}
|
|
891
955
|
this.installs.set(running.installId, running);
|
|
892
956
|
this.installOrder.push(running.installId);
|
|
893
957
|
this.evictFinishedInstalls();
|
|
@@ -936,11 +1000,20 @@ let ShopGateway = (() => {
|
|
|
936
1000
|
this.lastSnapshot = snapshot;
|
|
937
1001
|
}
|
|
938
1002
|
const dependencies = readProfileManifest("dsh-plugin-shop", this.profileDirResolved()).dependencies ?? {};
|
|
1003
|
+
const pins = readRepoPins(this.pinFs, this.pinsPath());
|
|
939
1004
|
const installed = [];
|
|
940
1005
|
for (const entry of this.lastSnapshot.entries) {
|
|
941
1006
|
const spec = dependencies[entry.name];
|
|
942
1007
|
if (spec === void 0) continue;
|
|
943
|
-
|
|
1008
|
+
if (entry.source === "github") {
|
|
1009
|
+
const pin = pins[entry.name];
|
|
1010
|
+
installed.push({
|
|
1011
|
+
name: entry.name,
|
|
1012
|
+
installed: pin ?? spec,
|
|
1013
|
+
latest: entry.version,
|
|
1014
|
+
outdated: pin !== void 0 && pin !== entry.version
|
|
1015
|
+
});
|
|
1016
|
+
} else installed.push({
|
|
944
1017
|
name: entry.name,
|
|
945
1018
|
installed: spec,
|
|
946
1019
|
latest: entry.version,
|
|
@@ -992,6 +1065,11 @@ let ShopGateway = (() => {
|
|
|
992
1065
|
dshBin: this.dshBin,
|
|
993
1066
|
expectedName: args.name
|
|
994
1067
|
});
|
|
1068
|
+
const pins = readRepoPins(this.pinFs, this.pinsPath());
|
|
1069
|
+
if (pins[args.name] !== void 0) {
|
|
1070
|
+
delete pins[args.name];
|
|
1071
|
+
writeRepoPins(this.pinFs, this.pinsPath(), pins);
|
|
1072
|
+
}
|
|
995
1073
|
this.installs.set(running.installId, running);
|
|
996
1074
|
this.installOrder.push(running.installId);
|
|
997
1075
|
this.evictFinishedInstalls();
|
package/lib/typert.host.js
CHANGED
|
@@ -18,7 +18,8 @@ const dsh_plugin_shop_shop_catalog_result$schema = z.object({
|
|
|
18
18
|
'tier': z.union([z.literal("verified"), z.literal("verified-stale"), z.literal("community")]),
|
|
19
19
|
'metadata': z.union([z.literal("declared"), z.literal("derived")]),
|
|
20
20
|
'review': z.union([z.undefined(), z.object({
|
|
21
|
-
'reviewedVersion': z.string(),
|
|
21
|
+
'reviewedVersion': z.union([z.undefined(), z.string()]).optional(),
|
|
22
|
+
'reviewedCommit': z.union([z.undefined(), z.string()]).optional(),
|
|
22
23
|
'reviewer': z.string(),
|
|
23
24
|
'reviewCommit': z.string(),
|
|
24
25
|
'notes': z.string(),
|
|
@@ -31,6 +32,8 @@ const dsh_plugin_shop_shop_catalog_result$schema = z.object({
|
|
|
31
32
|
}),
|
|
32
33
|
'capabilities': z.array(z.string()),
|
|
33
34
|
})]).optional(),
|
|
35
|
+
'source': z.union([z.literal("npm"), z.literal("github")]),
|
|
36
|
+
'repo': z.union([z.undefined(), z.string()]).optional(),
|
|
34
37
|
})),
|
|
35
38
|
'denied': z.array(z.object({
|
|
36
39
|
'name': z.string(),
|
|
@@ -54,7 +57,7 @@ const dsh_plugin_shop_shop_installStart_result$schema = z.union([z.object({
|
|
|
54
57
|
'installId': z.string(),
|
|
55
58
|
}), z.object({
|
|
56
59
|
'ok': z.literal(false),
|
|
57
|
-
'code': z.union([z.literal("not-in-catalog"), z.literal("denied"), z.literal("version-mismatch"), z.literal("needs-acknowledgement")]),
|
|
60
|
+
'code': z.union([z.literal("not-in-catalog"), z.literal("denied"), z.literal("version-mismatch"), z.literal("needs-acknowledgement"), z.literal("git-missing")]),
|
|
58
61
|
'detail': z.string(),
|
|
59
62
|
})])
|
|
60
63
|
const dsh_plugin_shop_shop_installStatus_parameter_0$schema = z.object({
|
|
@@ -137,7 +140,7 @@ export const TYPERT = {
|
|
|
137
140
|
typeSymbol: 'dsh-plugin-shop/types#ShopCatalogResult',
|
|
138
141
|
schema: dsh_plugin_shop_shop_catalog_result$schema,
|
|
139
142
|
},
|
|
140
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
143
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":270,"column":9},
|
|
141
144
|
},
|
|
142
145
|
{
|
|
143
146
|
id: 'dsh-plugin-shop#shop/installed',
|
|
@@ -152,7 +155,7 @@ export const TYPERT = {
|
|
|
152
155
|
typeSymbol: 'dsh-plugin-shop#shop/installed:result',
|
|
153
156
|
schema: dsh_plugin_shop_shop_installed_result$schema,
|
|
154
157
|
},
|
|
155
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
158
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":374,"column":9},
|
|
156
159
|
},
|
|
157
160
|
{
|
|
158
161
|
id: 'dsh-plugin-shop#shop/installStart',
|
|
@@ -178,7 +181,7 @@ export const TYPERT = {
|
|
|
178
181
|
typeSymbol: 'dsh-plugin-shop/types#ShopInstallResult',
|
|
179
182
|
schema: dsh_plugin_shop_shop_installStart_result$schema,
|
|
180
183
|
},
|
|
181
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
184
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":296,"column":9},
|
|
182
185
|
},
|
|
183
186
|
{
|
|
184
187
|
id: 'dsh-plugin-shop#shop/installStatus',
|
|
@@ -203,7 +206,7 @@ export const TYPERT = {
|
|
|
203
206
|
typeSymbol: 'dsh-plugin-shop/types#ShopInstallStatusResult',
|
|
204
207
|
schema: dsh_plugin_shop_shop_installStatus_result$schema,
|
|
205
208
|
},
|
|
206
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
209
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":363,"column":3},
|
|
207
210
|
},
|
|
208
211
|
{
|
|
209
212
|
id: 'dsh-plugin-shop#shop/restart',
|
|
@@ -218,7 +221,7 @@ export const TYPERT = {
|
|
|
218
221
|
typeSymbol: 'dsh-plugin-shop/types#ShopRestartResult',
|
|
219
222
|
schema: dsh_plugin_shop_shop_restart_result$schema,
|
|
220
223
|
},
|
|
221
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
224
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":469,"column":9},
|
|
222
225
|
},
|
|
223
226
|
{
|
|
224
227
|
id: 'dsh-plugin-shop#shop/setEnabled',
|
|
@@ -243,7 +246,7 @@ export const TYPERT = {
|
|
|
243
246
|
typeSymbol: 'dsh-plugin-shop/types#ShopSetEnabledResult',
|
|
244
247
|
schema: dsh_plugin_shop_shop_setEnabled_result$schema,
|
|
245
248
|
},
|
|
246
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
249
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":239,"column":3},
|
|
247
250
|
},
|
|
248
251
|
{
|
|
249
252
|
id: 'dsh-plugin-shop#shop/uninstallStart',
|
|
@@ -269,7 +272,7 @@ export const TYPERT = {
|
|
|
269
272
|
typeSymbol: 'dsh-plugin-shop/types#ShopUninstallResult',
|
|
270
273
|
schema: dsh_plugin_shop_shop_uninstallStart_result$schema,
|
|
271
274
|
},
|
|
272
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
275
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":429,"column":9},
|
|
273
276
|
},
|
|
274
277
|
{
|
|
275
278
|
id: 'dsh-plugin-shop#shop/updateStart',
|
|
@@ -294,7 +297,7 @@ export const TYPERT = {
|
|
|
294
297
|
typeSymbol: 'dsh-plugin-shop/types#ShopUpdateResult',
|
|
295
298
|
schema: dsh_plugin_shop_shop_updateStart_result$schema,
|
|
296
299
|
},
|
|
297
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
300
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":518,"column":9},
|
|
298
301
|
},
|
|
299
302
|
{
|
|
300
303
|
id: 'dsh-plugin-shop#shop/version',
|
|
@@ -309,7 +312,7 @@ export const TYPERT = {
|
|
|
309
312
|
typeSymbol: 'dsh-plugin-shop/types#ShopVersionResult',
|
|
310
313
|
schema: dsh_plugin_shop_shop_version_result$schema,
|
|
311
314
|
},
|
|
312
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
315
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":503,"column":9},
|
|
313
316
|
},
|
|
314
317
|
],
|
|
315
318
|
model: {
|
|
@@ -395,7 +398,7 @@ export const TYPERT = {
|
|
|
395
398
|
"types": [
|
|
396
399
|
{
|
|
397
400
|
"name": "CatalogEntry",
|
|
398
|
-
"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
|
|
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}"
|
|
399
402
|
},
|
|
400
403
|
{
|
|
401
404
|
"name": "CatalogSection",
|
|
@@ -415,7 +418,7 @@ export const TYPERT = {
|
|
|
415
418
|
},
|
|
416
419
|
{
|
|
417
420
|
"name": "InstallRejectionCode",
|
|
418
|
-
"declaration": "export type InstallRejectionCode = 'not-in-catalog' | 'denied' | 'version-mismatch' | 'needs-acknowledgement';"
|
|
421
|
+
"declaration": "export type InstallRejectionCode = 'not-in-catalog' | 'denied' | 'version-mismatch' | 'needs-acknowledgement' | 'git-missing';"
|
|
419
422
|
},
|
|
420
423
|
{
|
|
421
424
|
"name": "InstallState",
|
|
@@ -18,7 +18,8 @@ const dsh_plugin_shop_shop_catalog_result$schema = z.object({
|
|
|
18
18
|
'tier': z.union([z.literal("verified"), z.literal("verified-stale"), z.literal("community")]),
|
|
19
19
|
'metadata': z.union([z.literal("declared"), z.literal("derived")]),
|
|
20
20
|
'review': z.union([z.undefined(), z.object({
|
|
21
|
-
'reviewedVersion': z.string(),
|
|
21
|
+
'reviewedVersion': z.union([z.undefined(), z.string()]).optional(),
|
|
22
|
+
'reviewedCommit': z.union([z.undefined(), z.string()]).optional(),
|
|
22
23
|
'reviewer': z.string(),
|
|
23
24
|
'reviewCommit': z.string(),
|
|
24
25
|
'notes': z.string(),
|
|
@@ -31,6 +32,8 @@ const dsh_plugin_shop_shop_catalog_result$schema = z.object({
|
|
|
31
32
|
}),
|
|
32
33
|
'capabilities': z.array(z.string()),
|
|
33
34
|
})]).optional(),
|
|
35
|
+
'source': z.union([z.literal("npm"), z.literal("github")]),
|
|
36
|
+
'repo': z.union([z.undefined(), z.string()]).optional(),
|
|
34
37
|
})),
|
|
35
38
|
'denied': z.array(z.object({
|
|
36
39
|
'name': z.string(),
|
|
@@ -54,7 +57,7 @@ const dsh_plugin_shop_shop_installStart_result$schema = z.union([z.object({
|
|
|
54
57
|
'installId': z.string(),
|
|
55
58
|
}), z.object({
|
|
56
59
|
'ok': z.literal(false),
|
|
57
|
-
'code': z.union([z.literal("not-in-catalog"), z.literal("denied"), z.literal("version-mismatch"), z.literal("needs-acknowledgement")]),
|
|
60
|
+
'code': z.union([z.literal("not-in-catalog"), z.literal("denied"), z.literal("version-mismatch"), z.literal("needs-acknowledgement"), z.literal("git-missing")]),
|
|
58
61
|
'detail': z.string(),
|
|
59
62
|
})])
|
|
60
63
|
const dsh_plugin_shop_shop_installStatus_parameter_0$schema = z.object({
|
|
@@ -134,7 +137,7 @@ export const TYPERT_REMOTE = {
|
|
|
134
137
|
typeSymbol: 'dsh-plugin-shop/types#ShopCatalogResult',
|
|
135
138
|
schema: dsh_plugin_shop_shop_catalog_result$schema,
|
|
136
139
|
},
|
|
137
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
140
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":270,"column":9},
|
|
138
141
|
},
|
|
139
142
|
{
|
|
140
143
|
id: 'dsh-plugin-shop#shop/installed',
|
|
@@ -149,7 +152,7 @@ export const TYPERT_REMOTE = {
|
|
|
149
152
|
typeSymbol: 'dsh-plugin-shop#shop/installed:result',
|
|
150
153
|
schema: dsh_plugin_shop_shop_installed_result$schema,
|
|
151
154
|
},
|
|
152
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
155
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":374,"column":9},
|
|
153
156
|
},
|
|
154
157
|
{
|
|
155
158
|
id: 'dsh-plugin-shop#shop/installStart',
|
|
@@ -175,7 +178,7 @@ export const TYPERT_REMOTE = {
|
|
|
175
178
|
typeSymbol: 'dsh-plugin-shop/types#ShopInstallResult',
|
|
176
179
|
schema: dsh_plugin_shop_shop_installStart_result$schema,
|
|
177
180
|
},
|
|
178
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
181
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":296,"column":9},
|
|
179
182
|
},
|
|
180
183
|
{
|
|
181
184
|
id: 'dsh-plugin-shop#shop/installStatus',
|
|
@@ -200,7 +203,7 @@ export const TYPERT_REMOTE = {
|
|
|
200
203
|
typeSymbol: 'dsh-plugin-shop/types#ShopInstallStatusResult',
|
|
201
204
|
schema: dsh_plugin_shop_shop_installStatus_result$schema,
|
|
202
205
|
},
|
|
203
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
206
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":363,"column":3},
|
|
204
207
|
},
|
|
205
208
|
{
|
|
206
209
|
id: 'dsh-plugin-shop#shop/restart',
|
|
@@ -215,7 +218,7 @@ export const TYPERT_REMOTE = {
|
|
|
215
218
|
typeSymbol: 'dsh-plugin-shop/types#ShopRestartResult',
|
|
216
219
|
schema: dsh_plugin_shop_shop_restart_result$schema,
|
|
217
220
|
},
|
|
218
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
221
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":469,"column":9},
|
|
219
222
|
},
|
|
220
223
|
{
|
|
221
224
|
id: 'dsh-plugin-shop#shop/setEnabled',
|
|
@@ -240,7 +243,7 @@ export const TYPERT_REMOTE = {
|
|
|
240
243
|
typeSymbol: 'dsh-plugin-shop/types#ShopSetEnabledResult',
|
|
241
244
|
schema: dsh_plugin_shop_shop_setEnabled_result$schema,
|
|
242
245
|
},
|
|
243
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
246
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":239,"column":3},
|
|
244
247
|
},
|
|
245
248
|
{
|
|
246
249
|
id: 'dsh-plugin-shop#shop/uninstallStart',
|
|
@@ -266,7 +269,7 @@ export const TYPERT_REMOTE = {
|
|
|
266
269
|
typeSymbol: 'dsh-plugin-shop/types#ShopUninstallResult',
|
|
267
270
|
schema: dsh_plugin_shop_shop_uninstallStart_result$schema,
|
|
268
271
|
},
|
|
269
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
272
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":429,"column":9},
|
|
270
273
|
},
|
|
271
274
|
{
|
|
272
275
|
id: 'dsh-plugin-shop#shop/updateStart',
|
|
@@ -291,7 +294,7 @@ export const TYPERT_REMOTE = {
|
|
|
291
294
|
typeSymbol: 'dsh-plugin-shop/types#ShopUpdateResult',
|
|
292
295
|
schema: dsh_plugin_shop_shop_updateStart_result$schema,
|
|
293
296
|
},
|
|
294
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
297
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":518,"column":9},
|
|
295
298
|
},
|
|
296
299
|
{
|
|
297
300
|
id: 'dsh-plugin-shop#shop/version',
|
|
@@ -306,7 +309,7 @@ export const TYPERT_REMOTE = {
|
|
|
306
309
|
typeSymbol: 'dsh-plugin-shop/types#ShopVersionResult',
|
|
307
310
|
schema: dsh_plugin_shop_shop_version_result$schema,
|
|
308
311
|
},
|
|
309
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
312
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":503,"column":9},
|
|
310
313
|
},
|
|
311
314
|
],
|
|
312
315
|
}
|
|
@@ -44,6 +44,8 @@ export declare const zh: {
|
|
|
44
44
|
notInCatalogCode: string;
|
|
45
45
|
versionMismatchCode: string;
|
|
46
46
|
needsAcknowledgementCode: string;
|
|
47
|
+
gitMissingCode: string;
|
|
48
|
+
githubSource: string;
|
|
47
49
|
installedSection: string;
|
|
48
50
|
installedVersion: string;
|
|
49
51
|
latestVersion: string;
|
|
@@ -65,6 +67,7 @@ export declare const zh: {
|
|
|
65
67
|
updateTransportFailed: string;
|
|
66
68
|
checkUpdate: string;
|
|
67
69
|
upToDate: string;
|
|
70
|
+
github: string;
|
|
68
71
|
enabledSwitch: string;
|
|
69
72
|
toggleFailed: string;
|
|
70
73
|
hotApplyNote: string;
|
|
@@ -116,6 +119,8 @@ export declare const en: {
|
|
|
116
119
|
notInCatalogCode: string;
|
|
117
120
|
versionMismatchCode: string;
|
|
118
121
|
needsAcknowledgementCode: string;
|
|
122
|
+
gitMissingCode: string;
|
|
123
|
+
githubSource: string;
|
|
119
124
|
installedSection: string;
|
|
120
125
|
installedVersion: string;
|
|
121
126
|
latestVersion: string;
|
|
@@ -137,6 +142,7 @@ export declare const en: {
|
|
|
137
142
|
updateTransportFailed: string;
|
|
138
143
|
checkUpdate: string;
|
|
139
144
|
upToDate: string;
|
|
145
|
+
github: string;
|
|
140
146
|
enabledSwitch: string;
|
|
141
147
|
toggleFailed: string;
|
|
142
148
|
hotApplyNote: string;
|
|
@@ -17,6 +17,11 @@ export declare const ACKNOWLEDGEMENT_ZH = "\u5B89\u88C5\u540E\uFF0C\u6B64\u63D2\
|
|
|
17
17
|
/** Install rejection code → locale key, for the rejected-state code label
|
|
18
18
|
* (§7.2): every rejection the host can return has a human label here. */
|
|
19
19
|
export declare function rejectionCodeKey(code: InstallRejectionCode): ShopLocaleKey;
|
|
20
|
+
/** The version a card displays: full for npm, the short commit for github. */
|
|
21
|
+
export declare function displayVersion(entry: {
|
|
22
|
+
source: 'npm' | 'github';
|
|
23
|
+
version: string;
|
|
24
|
+
}): string;
|
|
20
25
|
/** One polled install status (§7.3 wire data), structural. */
|
|
21
26
|
export interface InstallStatusShape {
|
|
22
27
|
found: boolean;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/** Catalog fetch, verification, and disk cache — the Host's only network path. */
|
|
2
2
|
import type { CatalogEntry, DeniedEntry } from './types.ts';
|
|
3
|
-
/** Highest schemaVersion this build understands; a higher one is refused (§10).
|
|
4
|
-
|
|
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;
|
|
5
6
|
export interface CatalogSnapshot {
|
|
6
7
|
schemaVersion: number;
|
|
7
8
|
builtAt: string;
|
|
@@ -6,6 +6,7 @@ import type { CatalogEntry, DeniedEntry } from './types.ts';
|
|
|
6
6
|
import { type InstallArgs, type InstallRejectionCode } from './install.ts';
|
|
7
7
|
import { type InstallStatus } from './executor.ts';
|
|
8
8
|
import { type RestartOutcome } from './restart.ts';
|
|
9
|
+
import { type RepoPinFs } from './repo-pins.ts';
|
|
9
10
|
export type { InstallArgs, InstallRejectionCode } from './install.ts';
|
|
10
11
|
export type { CatalogEntry } from './types.ts';
|
|
11
12
|
/** One Loader inventory entry, structurally — the shop never depends on
|
|
@@ -46,6 +47,11 @@ export interface ShopGatewayOptions {
|
|
|
46
47
|
/** How long the gateway waits after a successful restart response before
|
|
47
48
|
* exiting the old process; test-only shortening, production uses 2s. */
|
|
48
49
|
restartExitDelayMs?: number;
|
|
50
|
+
/** Whether `git` is on PATH — a github entry cannot install without it;
|
|
51
|
+
* test-only injection, production probes the real binary. */
|
|
52
|
+
hasGit?: () => boolean;
|
|
53
|
+
/** Test-only injection: the pins file's filesystem; production uses node:fs. */
|
|
54
|
+
pinFs?: RepoPinFs;
|
|
49
55
|
}
|
|
50
56
|
/** `shop/installStart` result (§7.3): rejections are typed wire values with an
|
|
51
57
|
* author-readable `detail`, not thrown RPC errors. */
|
|
@@ -138,6 +144,8 @@ export declare class ShopGateway extends TypertRemoteService {
|
|
|
138
144
|
private readonly restartExitDelayMs;
|
|
139
145
|
private readonly restartParentPid;
|
|
140
146
|
private readonly latestVersion;
|
|
147
|
+
private readonly hasGit;
|
|
148
|
+
private readonly pinFs;
|
|
141
149
|
/** The install gate runs against the last loaded snapshot, never a fresh
|
|
142
150
|
* fetch per request (§7.2: the Host's cached snapshot is the truth). */
|
|
143
151
|
/** Finished install records retained, so a poll sees the true terminal
|
|
@@ -154,6 +162,8 @@ export declare class ShopGateway extends TypertRemoteService {
|
|
|
154
162
|
/** Every install id in insertion order, oldest first; finished-record eviction walks this from the front. */
|
|
155
163
|
private readonly installOrder;
|
|
156
164
|
constructor(ctx: Context, options?: ShopGatewayOptions);
|
|
165
|
+
/** The pins file lives in the shop's own cache, next to the catalog cache. */
|
|
166
|
+
private pinsPath;
|
|
157
167
|
/** The boot's Loader root directory (the active profile's `cordis.yml`
|
|
158
168
|
* directory, carried on `ctx.baseUrl`), when present. A `link:` install
|
|
159
169
|
* keeps this package at its source location, so the walk-up from
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Install gate: the four rejection paths of §7.2, as a pure function. */
|
|
2
2
|
import type { CatalogSnapshot } from './catalog.ts';
|
|
3
|
-
export type InstallRejectionCode = 'not-in-catalog' | 'denied' | 'version-mismatch' | 'needs-acknowledgement';
|
|
3
|
+
export type InstallRejectionCode = 'not-in-catalog' | 'denied' | 'version-mismatch' | 'needs-acknowledgement' | 'git-missing';
|
|
4
4
|
export interface InstallArgs {
|
|
5
5
|
name: string;
|
|
6
6
|
version: string;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Host's memory of which commit it installed for each github entry.
|
|
3
|
+
*
|
|
4
|
+
* pnpm writes the manifest dependency as `github:owner/slug` — no commit —
|
|
5
|
+
* and the resolved commit lives only in the lockfile. The shop records the
|
|
6
|
+
* commit it pinned at install time in its own cache so `installed()` can
|
|
7
|
+
* report `outdated` honestly without parsing pnpm's lockfile. The profile
|
|
8
|
+
* manifest remains the source of truth for *presence*: a pin whose bundle
|
|
9
|
+
* is not a manifest dependency is a leftover from a failed install and is
|
|
10
|
+
* never reported.
|
|
11
|
+
*/
|
|
12
|
+
export interface RepoPinFs {
|
|
13
|
+
exists: (path: string) => boolean;
|
|
14
|
+
read: (path: string) => string;
|
|
15
|
+
write: (path: string, data: string) => void;
|
|
16
|
+
}
|
|
17
|
+
/** Bundle name to the pinned commit. */
|
|
18
|
+
export type RepoPins = Record<string, string>;
|
|
19
|
+
/** Read the pins file; any irregularity degrades to an empty record. */
|
|
20
|
+
export declare function readRepoPins(fs: RepoPinFs, path: string): RepoPins;
|
|
21
|
+
/** Write the pins file atomically enough for its purpose (a cache). */
|
|
22
|
+
export declare function writeRepoPins(fs: RepoPinFs, path: string, pins: RepoPins): void;
|
|
@@ -18,12 +18,17 @@ export interface CatalogEntry {
|
|
|
18
18
|
tier: 'verified' | 'verified-stale' | 'community';
|
|
19
19
|
metadata: 'declared' | 'derived';
|
|
20
20
|
review?: {
|
|
21
|
-
reviewedVersion
|
|
21
|
+
reviewedVersion?: string;
|
|
22
|
+
reviewedCommit?: string;
|
|
22
23
|
reviewer: string;
|
|
23
24
|
reviewCommit: string;
|
|
24
25
|
notes: string;
|
|
25
26
|
};
|
|
26
27
|
catalog?: CatalogSection;
|
|
28
|
+
/** Where the entry installs from. */
|
|
29
|
+
source: 'npm' | 'github';
|
|
30
|
+
/** `owner/slug`; present exactly when `source` is github. */
|
|
31
|
+
repo?: string;
|
|
27
32
|
}
|
|
28
33
|
export interface DeniedEntry {
|
|
29
34
|
name: string;
|
package/package.json
CHANGED