feat(artifacts): storefront layout and Uninstall for installed skills/plugins

- Artifacts page laid out like an app store: hero search, category chips,
  shelves by kind, app-icon cards with one primary pill and a detail sheet
- POST /api/artifacts/:id/uninstall: a skill's copies in ~/.claude/skills and
  ~/.agents/skills are moved to ~/.claude/agenthub-replaced-skills/
  (-uninstalled-<time>), never deleted; a plugin runs
  `claude plugin uninstall --scope user --keep-data` as the caller;
  ENOTINSTALLED when nothing is there; allowed while installs are off
- Uninstall on the card, the ⋯ menu and the detail sheet, after a confirm
This commit is contained in:
2026-10-21 16:10:30 +07:00
parent 383b9ae231
commit c042519bc9
11 changed files with 1066 additions and 235 deletions
+8 -1
View File
@@ -7,7 +7,7 @@
* rename/share/unshare, delete, read a snapshot file (preview/download, under
* a `sandbox` CSP with `nosniff`, so nothing shared runs on the dashboard's
* origin), and install a shared skill or plugin into the caller's own
* account. Every write is same-origin. A path in a request only ever names a
* account (or uninstall it from there again). Every write is same-origin. A path in a request only ever names a
* file inside a workspace (pin, under the read path's guards) or one of a
* snapshot's own recorded files.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
@@ -30,6 +30,7 @@ const STATUS = {
EFOREIGN: 403,
EINSTALLSOFF: 403,
EEXISTS: 409,
ENOTINSTALLED: 409,
ENOACCOUNT: 409,
EUPGRADE: 409,
ENOTSHARED: 409,
@@ -361,4 +362,10 @@ router.post(
})
);
router.post(
"/:id/uninstall",
sameOriginGuard,
wrap(async (req, res) => res.json(await artifacts.uninstall(req, req.params.id)))
);
module.exports = router;