dsh-plugin-shop 0.4.9 → 0.4.10
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 -72
- 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.10
|
|
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.10
|
|
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.10
|
|
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
|
}
|
|
@@ -4636,8 +4640,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4636
4640
|
case "not-in-catalog": return "notInCatalogCode";
|
|
4637
4641
|
case "version-mismatch": return "versionMismatchCode";
|
|
4638
4642
|
case "needs-acknowledgement": return "needsAcknowledgementCode";
|
|
4643
|
+
case "git-missing": return "gitMissingCode";
|
|
4639
4644
|
}
|
|
4640
4645
|
}
|
|
4646
|
+
/** The version a card displays: full for npm, the short commit for github. */
|
|
4647
|
+
function displayVersion(entry) {
|
|
4648
|
+
return entry.source === "github" ? entry.version.slice(0, 7) : entry.version;
|
|
4649
|
+
}
|
|
4641
4650
|
/** §7.2 once-per-second poll cadence, as a named constant. */
|
|
4642
4651
|
const INSTALL_POLL_MS = 1e3;
|
|
4643
4652
|
/** How long the check-update button reports "up to date" after a re-check
|
|
@@ -4695,10 +4704,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4695
4704
|
"shop",
|
|
4696
4705
|
"marketplace"
|
|
4697
4706
|
];
|
|
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
4707
|
/**
|
|
4703
4708
|
* Competing plugin marketplaces whose names carry no store/market keyword
|
|
4704
4709
|
* and so escape the pattern rules below (spec §7.2). Each is a real npm
|
|
@@ -4827,6 +4832,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4827
4832
|
notInCatalogCode: "不在目录中",
|
|
4828
4833
|
versionMismatchCode: "版本不匹配",
|
|
4829
4834
|
needsAcknowledgementCode: "需要确认",
|
|
4835
|
+
gitMissingCode: "缺少 git",
|
|
4836
|
+
githubSource: "来自 GitHub 仓库安装",
|
|
4830
4837
|
installedSection: "已安装",
|
|
4831
4838
|
installedVersion: "已安装 v{version}",
|
|
4832
4839
|
latestVersion: "最新 v{version}",
|
|
@@ -4898,6 +4905,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4898
4905
|
notInCatalogCode: "Not in the catalog",
|
|
4899
4906
|
versionMismatchCode: "Version mismatch",
|
|
4900
4907
|
needsAcknowledgementCode: "Acknowledgement required",
|
|
4908
|
+
gitMissingCode: "git missing",
|
|
4909
|
+
githubSource: "installed from a GitHub repository",
|
|
4901
4910
|
installedSection: "Installed",
|
|
4902
4911
|
installedVersion: "installed v{version}",
|
|
4903
4912
|
latestVersion: "latest v{version}",
|
|
@@ -5074,7 +5083,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5074
5083
|
}
|
|
5075
5084
|
//#endregion
|
|
5076
5085
|
//#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";
|
|
5086
|
+
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/* Single-line rows: one full-width column, one plugin per line. */\n.sf657f5_cards {\n display: grid;\n grid-template-columns: 1fr;\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: row;\n align-items: center;\n flex-wrap: wrap;\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 52px;\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 /* Basis 0: the header takes only the space the action buttons leave,\n * so long names truncate instead of wrapping the buttons off the row. */\n flex: 1 1 0;\n min-width: 0;\n padding: 8px 12px;\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: nowrap;\n gap: 6px;\n}\n\n/* The action area is virtual: its children lay out as direct card rows. */\n.s9b5048_cardActions {\n display: contents;\n}\n\n/* The first action owns the right edge; the rest follow it. */\n.s9b5048_cardActions > :first-child {\n margin-left: auto;\n}\n\n/* A flow that outgrows the row — install/uninstall gate, log, notices, the\n * expanded body — breaks onto its own full-width line below it. Nested\n * blocks inside those panels ignore the basis. */\n.s65f383_card > .sc4f2ac_gate,\n.s65f383_card > .s4d374c_installPanel,\n.s65f383_card > .sf53ca0_installedActions,\n.s65f383_card > .s820ebf_body {\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.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 /* A wrapping pill would stretch the single-line card to two lines. */\n white-space: nowrap;\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 margin-top: 2px;\n padding: 10px 12px 12px;\n border-top: 1px solid var(--dsw-alias-border-l1);\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
5087
|
if (typeof document !== "undefined" && !document.querySelector("style[data-plugin-css=\"e3675a89\"]")) {
|
|
5079
5088
|
const tag = document.createElement("style");
|
|
5080
5089
|
tag.dataset.pluginCss = "e3675a89";
|
|
@@ -5118,17 +5127,22 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5118
5127
|
"cardsSentry": "sd7ee01_cardsSentry",
|
|
5119
5128
|
"showingLine": "s705cc6_showingLine",
|
|
5120
5129
|
"cardSpine": "s4ce25d_cardSpine",
|
|
5121
|
-
"
|
|
5122
|
-
"coverLabel": "s1a7c5b_coverLabel",
|
|
5130
|
+
"categoryBadge": "s9c8eaa_categoryBadge",
|
|
5123
5131
|
"entryHeader": "sa97234_entryHeader",
|
|
5124
5132
|
"name": "sc3f149_name",
|
|
5125
5133
|
"badges": "s513026_badges",
|
|
5134
|
+
"cardActions": "s9b5048_cardActions",
|
|
5135
|
+
"gate": "sc4f2ac_gate",
|
|
5136
|
+
"installPanel": "s4d374c_installPanel",
|
|
5137
|
+
"installedActions": "sf53ca0_installedActions",
|
|
5138
|
+
"body": "s820ebf_body",
|
|
5139
|
+
"cardVersion": "sc6ec6e_cardVersion",
|
|
5140
|
+
"sourceBadge": "s71f10f_sourceBadge",
|
|
5126
5141
|
"tierBadge": "s5e82cb_tierBadge",
|
|
5127
5142
|
"unclaimedBadge": "s23e3e1_unclaimedBadge",
|
|
5128
5143
|
"starsBadge": "sff43b4_starsBadge",
|
|
5129
5144
|
"chevron": "s73f7fb_chevron",
|
|
5130
5145
|
"chevronOpen": "s61393c_chevronOpen",
|
|
5131
|
-
"body": "s820ebf_body",
|
|
5132
5146
|
"summary": "sb8fbc8_summary",
|
|
5133
5147
|
"summaryZh": "se507db_summaryZh",
|
|
5134
5148
|
"summaryExpanded": "s10baf0_summaryExpanded",
|
|
@@ -5140,15 +5154,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5140
5154
|
"detailRows": "se98179_detailRows",
|
|
5141
5155
|
"detailRow": "sfb4d2f_detailRow",
|
|
5142
5156
|
"reviewedLine": "sd58c6d_reviewedLine",
|
|
5143
|
-
"installPanel": "s4d374c_installPanel",
|
|
5144
5157
|
"installButton": "se7aaed_installButton",
|
|
5145
5158
|
"confirmButton": "sb43a49_confirmButton",
|
|
5146
5159
|
"cancelButton": "s6e45d1_cancelButton",
|
|
5147
5160
|
"installedLabel": "sb6fc74_installedLabel",
|
|
5148
|
-
"installedActions": "sf53ca0_installedActions",
|
|
5149
5161
|
"uninstallButton": "s96967f_uninstallButton",
|
|
5150
5162
|
"restartButton": "s6ac237_restartButton",
|
|
5151
|
-
"gate": "sc4f2ac_gate",
|
|
5152
5163
|
"gateTitle": "s559c38_gateTitle",
|
|
5153
5164
|
"gateBody": "s3c0ace_gateBody",
|
|
5154
5165
|
"gateActions": "s4b2373_gateActions",
|
|
@@ -5212,14 +5223,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5212
5223
|
className: _dsh_plugin_shop_css_e3675a89_default.cardSpine,
|
|
5213
5224
|
"aria-hidden": "true"
|
|
5214
5225
|
}),
|
|
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
5226
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5224
5227
|
type: "button",
|
|
5225
5228
|
className: _dsh_plugin_shop_css_e3675a89_default.entryHeader,
|
|
@@ -5232,6 +5235,28 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5232
5235
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5233
5236
|
className: _dsh_plugin_shop_css_e3675a89_default.badges,
|
|
5234
5237
|
children: [
|
|
5238
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5239
|
+
className: _dsh_plugin_shop_css_e3675a89_default.categoryBadge,
|
|
5240
|
+
children: t(categoryKey(entry))
|
|
5241
|
+
}),
|
|
5242
|
+
entry.source === "github" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5243
|
+
className: _dsh_plugin_shop_css_e3675a89_default.sourceBadge,
|
|
5244
|
+
role: "img",
|
|
5245
|
+
"aria-label": t("githubSource"),
|
|
5246
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
5247
|
+
viewBox: "0 0 16 16",
|
|
5248
|
+
width: "12",
|
|
5249
|
+
height: "12",
|
|
5250
|
+
"aria-hidden": "true",
|
|
5251
|
+
fill: "currentColor",
|
|
5252
|
+
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" })
|
|
5253
|
+
})
|
|
5254
|
+
}),
|
|
5255
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5256
|
+
className: _dsh_plugin_shop_css_e3675a89_default.cardVersion,
|
|
5257
|
+
"data-card-version": true,
|
|
5258
|
+
children: ["v", displayVersion(entry)]
|
|
5259
|
+
}),
|
|
5235
5260
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5236
5261
|
className: _dsh_plugin_shop_css_e3675a89_default.tierBadge,
|
|
5237
5262
|
"data-tier": entry.tier,
|
|
@@ -5251,15 +5276,46 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5251
5276
|
]
|
|
5252
5277
|
})]
|
|
5253
5278
|
}),
|
|
5254
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
5279
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5280
|
+
className: _dsh_plugin_shop_css_e3675a89_default.cardActions,
|
|
5281
|
+
children: installed === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallPanel, {
|
|
5282
|
+
name: entry.name,
|
|
5283
|
+
version: entry.version,
|
|
5284
|
+
tier: entry.tier,
|
|
5285
|
+
t,
|
|
5286
|
+
install,
|
|
5287
|
+
installStatus,
|
|
5288
|
+
restart
|
|
5289
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [installed.outdated ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallPanel, {
|
|
5290
|
+
name: entry.name,
|
|
5291
|
+
version: entry.version,
|
|
5292
|
+
tier: entry.tier,
|
|
5293
|
+
variant: "update",
|
|
5294
|
+
t,
|
|
5295
|
+
install,
|
|
5296
|
+
installStatus,
|
|
5297
|
+
restart
|
|
5298
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5299
|
+
className: _dsh_plugin_shop_css_e3675a89_default.installedLabel,
|
|
5300
|
+
"data-shop-installed": true,
|
|
5301
|
+
children: t("installed")
|
|
5302
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UninstallPanel, {
|
|
5303
|
+
name: entry.name,
|
|
5304
|
+
t,
|
|
5305
|
+
uninstall,
|
|
5306
|
+
installStatus,
|
|
5307
|
+
restart
|
|
5308
|
+
})] })
|
|
5309
|
+
}),
|
|
5310
|
+
open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5255
5311
|
className: _dsh_plugin_shop_css_e3675a89_default.body,
|
|
5256
5312
|
children: [
|
|
5257
5313
|
summary !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5258
|
-
className:
|
|
5314
|
+
className: `${_dsh_plugin_shop_css_e3675a89_default.summary} ${_dsh_plugin_shop_css_e3675a89_default.summaryExpanded}`,
|
|
5259
5315
|
children: summary.en
|
|
5260
5316
|
}),
|
|
5261
5317
|
summary?.zh !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5262
|
-
className:
|
|
5318
|
+
className: `${_dsh_plugin_shop_css_e3675a89_default.summaryZh} ${_dsh_plugin_shop_css_e3675a89_default.summaryZhExpanded}`,
|
|
5263
5319
|
children: summary.zh
|
|
5264
5320
|
}),
|
|
5265
5321
|
entry.catalog !== void 0 && entry.catalog.capabilities.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -5272,7 +5328,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5272
5328
|
children: entry.catalog.capabilities.map((capability) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: capability }, capability))
|
|
5273
5329
|
})]
|
|
5274
5330
|
}),
|
|
5275
|
-
|
|
5331
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
5276
5332
|
id: detailId,
|
|
5277
5333
|
className: _dsh_plugin_shop_css_e3675a89_default.detail,
|
|
5278
5334
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", {
|
|
@@ -5303,37 +5359,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5303
5359
|
current: entry.version
|
|
5304
5360
|
})
|
|
5305
5361
|
})]
|
|
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
5362
|
})
|
|
5338
5363
|
]
|
|
5339
5364
|
})
|
|
@@ -5616,7 +5641,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5616
5641
|
* installed-state RPC behind it — the tab assumes an installed plugin is on,
|
|
5617
5642
|
* so the first toggle sends the inverted value. After a successful `setEnabled`
|
|
5618
5643
|
* the §8 hot note renders. */
|
|
5619
|
-
function OutdatedRow({ row, tier, t, setEnabled, install, installStatus, restart }) {
|
|
5644
|
+
function OutdatedRow({ row, tier, source, t, setEnabled, install, installStatus, restart }) {
|
|
5620
5645
|
const [enabled, setEnabledState] = (0, react.useState)(true);
|
|
5621
5646
|
const [toggle, setToggle] = (0, react.useState)({ kind: "idle" });
|
|
5622
5647
|
const onToggle = async () => {
|
|
@@ -5655,7 +5680,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5655
5680
|
className: _dsh_plugin_shop_css_e3675a89_default.outdatedVersions,
|
|
5656
5681
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5657
5682
|
className: _dsh_plugin_shop_css_e3675a89_default.outdatedVersion,
|
|
5658
|
-
children: t("installedVersion", { version: row.installed })
|
|
5683
|
+
children: t("installedVersion", { version: source === "github" ? row.installed.slice(0, 7) : row.installed })
|
|
5659
5684
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5660
5685
|
className: _dsh_plugin_shop_css_e3675a89_default.outdatedVersion,
|
|
5661
5686
|
children: t("latestVersion", { version: row.latest })
|
|
@@ -5705,7 +5730,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5705
5730
|
* tier for the update gate is looked up from the catalog by name (community →
|
|
5706
5731
|
* acknowledgement); an entry absent from the catalog defaults to the
|
|
5707
5732
|
* community gate (the safer read). */
|
|
5708
|
-
function OutdatedSection({ state, tiers, t, setEnabled, install, installStatus, restart }) {
|
|
5733
|
+
function OutdatedSection({ state, tiers, sources, t, setEnabled, install, installStatus, restart }) {
|
|
5709
5734
|
if (state.kind === "loading") return null;
|
|
5710
5735
|
if (state.kind === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5711
5736
|
className: _dsh_plugin_shop_css_e3675a89_default.stateLine,
|
|
@@ -5725,6 +5750,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5725
5750
|
children: outdated.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OutdatedRow, {
|
|
5726
5751
|
row,
|
|
5727
5752
|
tier: tiers.get(row.name) ?? "community",
|
|
5753
|
+
source: sources.get(row.name) ?? "npm",
|
|
5728
5754
|
t,
|
|
5729
5755
|
setEnabled,
|
|
5730
5756
|
install,
|
|
@@ -5827,7 +5853,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5827
5853
|
if (catalogState.kind !== "ready") return [];
|
|
5828
5854
|
const q = query.trim().toLowerCase();
|
|
5829
5855
|
return catalogState.result.plugins.filter((entry) => {
|
|
5830
|
-
if (isShopLike(entry.name)) return false;
|
|
5856
|
+
if (isShopLike(entry.name) || entry.repo !== void 0 && isShopLike(entry.repo)) return false;
|
|
5831
5857
|
if (category === "installed") {
|
|
5832
5858
|
if (!installedByName.has(entry.name)) return false;
|
|
5833
5859
|
} else if (category !== null && categoryKey(entry) !== categoryLocaleKey(category)) return false;
|
|
@@ -5863,7 +5889,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5863
5889
|
const categoryCounts = (0, react.useMemo)(() => {
|
|
5864
5890
|
const counts = /* @__PURE__ */ new Map();
|
|
5865
5891
|
if (catalogState.kind === "ready") for (const entry of catalogState.result.plugins) {
|
|
5866
|
-
if (isShopLike(entry.name)) continue;
|
|
5892
|
+
if (isShopLike(entry.name) || entry.repo !== void 0 && isShopLike(entry.repo)) continue;
|
|
5867
5893
|
const key = categoryKey(entry);
|
|
5868
5894
|
const bare = CATEGORY_ORDER.find((c) => categoryLocaleKey(c) === key);
|
|
5869
5895
|
if (bare !== void 0) counts.set(bare, (counts.get(bare) ?? 0) + 1);
|
|
@@ -5879,6 +5905,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5879
5905
|
if (catalogState.kind === "ready") for (const entry of catalogState.result.plugins) map.set(entry.name, entry.tier);
|
|
5880
5906
|
return map;
|
|
5881
5907
|
}, [catalogState]);
|
|
5908
|
+
const sources = (0, react.useMemo)(() => {
|
|
5909
|
+
const map = /* @__PURE__ */ new Map();
|
|
5910
|
+
if (catalogState.kind === "ready") for (const entry of catalogState.result.plugins) map.set(entry.name, entry.source);
|
|
5911
|
+
return map;
|
|
5912
|
+
}, [catalogState]);
|
|
5882
5913
|
if (catalogState.kind === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5883
5914
|
className: _dsh_plugin_shop_css_e3675a89_default.panel,
|
|
5884
5915
|
"data-shop-tab": true,
|
|
@@ -6107,7 +6138,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6107
6138
|
className: _dsh_plugin_shop_css_e3675a89_default.cards,
|
|
6108
6139
|
children: [visible.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EntryCard, {
|
|
6109
6140
|
entry,
|
|
6110
|
-
stars: stars[entry.name],
|
|
6141
|
+
stars: stars[entry.repo ?? entry.name],
|
|
6111
6142
|
installed: installedByName.get(entry.name),
|
|
6112
6143
|
t,
|
|
6113
6144
|
install,
|
|
@@ -6131,6 +6162,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6131
6162
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(OutdatedSection, {
|
|
6132
6163
|
state: installedState,
|
|
6133
6164
|
tiers,
|
|
6165
|
+
sources,
|
|
6134
6166
|
t,
|
|
6135
6167
|
setEnabled,
|
|
6136
6168
|
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