@termix-ssh/plugin-sdk 1.0.2 → 1.0.5

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/LICENSE +202 -0
  3. package/README.md +4 -2
  4. package/cli/commands/build.mjs +44 -19
  5. package/cli/commands/dev.mjs +1 -1
  6. package/cli/commands/migrations.mjs +2 -2
  7. package/cli/commands/openapi.mjs +39 -0
  8. package/cli/commands/validate.mjs +72 -0
  9. package/cli/index.mjs +3 -0
  10. package/cli/lib/css-classes.mjs +37 -0
  11. package/dist/changelog.d.ts +3 -1
  12. package/dist/changelog.js +12 -2
  13. package/dist/db.d.ts +1 -1
  14. package/dist/db.js +1 -1
  15. package/dist/ddl.js +4 -1
  16. package/dist/docs.d.ts +28 -0
  17. package/dist/docs.js +39 -0
  18. package/dist/frontend.d.ts +17 -0
  19. package/dist/frontend.js +8 -0
  20. package/dist/host/{chunk-XKMRKQFN.js → chunk-KOOUKYW3.js} +1 -1
  21. package/dist/host/{chunk-LKYAVM4T.js → chunk-OEOKIKXC.js} +18 -9
  22. package/dist/host/{chunk-SS43S6VI.js → chunk-VETPF5WI.js} +478 -431
  23. package/dist/host/core-classes.json +1 -0
  24. package/dist/host/{linked-server-DW5P3NLZ.js → linked-server-RWBK427T.js} +1 -1
  25. package/dist/host/{rbac-api-JKISXZEP.js → rbac-api-UJGGTQVA.js} +1 -1
  26. package/dist/host/testing-host.js +3 -3
  27. package/dist/host/types/src/types/credential-sidebar-preferences.d.ts +1 -1
  28. package/dist/host/types/src/ui/api/plugins-api.d.ts +6 -0
  29. package/dist/host/types/src/ui/components/docs-link.d.ts +25 -0
  30. package/dist/host/types/src/ui/components/panel-layout.d.ts +3 -1
  31. package/dist/host/types/src/ui/lib/docs.d.ts +10 -0
  32. package/dist/host/types/src/ui/lib/error-message.d.ts +1 -1
  33. package/dist/host/types/src/ui/main-axios.d.ts +1 -2
  34. package/dist/host/types/src/ui/plugin-host/sdk-ui.d.ts +1 -0
  35. package/dist/host/ui.js +18 -8
  36. package/dist/host/{use-permissions-5CZEA3P4.js → use-permissions-P3DC7ZDR.js} +1 -1
  37. package/dist/host-commands.js +1 -1
  38. package/dist/manifest.d.ts +7 -1
  39. package/dist/manifest.js +48 -1
  40. package/dist/openapi.d.ts +43 -0
  41. package/dist/openapi.js +60 -0
  42. package/dist/testing/setup.js +1 -0
  43. package/package.json +22 -1
  44. package/schema/manifest.schema.json +21 -0
@@ -0,0 +1 @@
1
+ ["!max-h-[60px]","!mt-0","!opacity-100","!running","**:data-[slot=kbd]:isolate","**:data-[slot=kbd]:relative","**:data-[slot=kbd]:rounded-none","**:data-[slot=kbd]:z-50","*:[a]:hover:text-foreground","*:[a]:underline","*:[a]:underline-offset-3","*:[img:first-child]:rounded-none","*:[img:last-child]:rounded-none","*:[span]:last:flex","*:[span]:last:gap-2","*:[span]:last:items-center","*:data-[slot=alert-description]:text-destructive/90","*:data-[slot=select-value]:flex","*:data-[slot=select-value]:gap-2","*:data-[slot=select-value]:items-center","*:data-[slot=select-value]:line-clamp-1","-bottom-1.5","-left-1.5","-left-px","-mt-0.5","-mt-1","-mt-[3.5px]","-mx-1","-mx-3","-my-1","-right-1.5","-right-2","-right-2.5","-right-px","-top-1.5","-top-[3px]","-translate-x-1/2","-translate-x-px","-translate-y-1/2","2xl:flex","2xl:grid-cols-4","3","@container","@container/card-header","@md:grid-cols-2","[&::-webkit-inner-spin-button]:appearance-none","[&::-webkit-outer-spin-button]:appearance-none","[&::-webkit-scrollbar]:hidden","[&>*:last-child]:border-b-0","[&>*]:h-full","[&>*]:shrink-0","[&>button]:hidden","[&>span:last-child]:truncate","[&>svg]:pointer-events-none","[&>svg]:shrink-0","[&>svg]:size-3","[&>svg]:size-4","[&>svg]:text-current","[&>svg]:text-sidebar-accent-foreground","[&>svg]:translate-y-0.5","[&_[cmdk-group-heading]]:font-medium","[&_[cmdk-group-heading]]:px-2","[&_[cmdk-group-heading]]:py-1.5","[&_[cmdk-group-heading]]:text-muted-foreground","[&_[cmdk-group-heading]]:text-xs","[&_[cmdk-list-sizer]]:flex","[&_[cmdk-list-sizer]]:flex-col","[&_p]:leading-relaxed","[&_svg:not([class*='size-'])]:size-3","[&_svg:not([class*='size-'])]:size-3.5","[&_svg:not([class*='size-'])]:size-4","[&_svg:not([class*='text-'])]:text-muted-foreground","[&_svg]:pointer-events-none","[&_svg]:shrink-0","[&_svg]:size-2.5","[&_svg]:size-3","[&_svg]:size-3.5","[&_svg]:size-4","[.border-b]:pb-4","[.gap-4>&]:border-0","[.gap-4>&]:py-0","[@media(hover:none)]:flex","[[data-side=left][data-collapsible=offcanvas]_&]:-right-2","[[data-side=left][data-state=collapsed]_&]:cursor-e-resize","[[data-side=right][data-collapsible=offcanvas]_&]:-left-2","[[data-side=right][data-state=collapsed]_&]:cursor-w-resize","[a&]:hover:bg-accent","[a&]:hover:bg-destructive/90","[a&]:hover:bg-primary/90","[a&]:hover:bg-secondary/90","[a&]:hover:text-accent-foreground","[a]:hover:bg-primary/80","[animation-duration:900ms]","[appearance:textfield]","[backend:stderr]","[clip-path:inset(0px_-4px_-4px_-4px)]","[contain:content]","[op:http_server_port_conflict]","[scrollbar-width:none]","absolute","accent-[var(--accent-brand)]","active:bg-muted","active:bg-muted/60","active:bg-sidebar-accent","active:cursor-grabbing","active:not-aria-[haspopup]:translate-y-px","active:text-sidebar-accent-foreground","after:-inset-2","after:absolute","after:inset-y-0","after:start-1/2","after:w-[2px]","animate-pulse","animate-spin","appearance-none","aria-disabled:opacity-50","aria-disabled:pointer-events-none","aria-expanded:bg-muted","aria-expanded:bg-secondary","aria-expanded:opacity-100","aria-expanded:text-foreground","aria-expanded:text-secondary-foreground","aria-invalid:border-destructive","aria-invalid:ring-1","aria-invalid:ring-destructive/20","aspect-square","aspect-video","auto-cols-fr","auto-rows-fr","auto-rows-min","before:-top-0.5","before:-top-px","before:absolute","before:bg-accent-brand","before:h-0.5","before:inset-x-0","before:inset-x-1","before:z-20","bg-accent-brand","bg-accent-brand/10","bg-accent-brand/15","bg-accent-brand/20","bg-accent-brand/25","bg-accent-brand/40","bg-accent-brand/5","bg-accent-brand/60","bg-accent-brand/70","bg-accent-brand/[0.07]","bg-accent/30","bg-accent/60","bg-background","bg-background/50","bg-background/60","bg-background/70","bg-background/85","bg-background/90","bg-black","bg-black/10","bg-blue-500/20","bg-border","bg-border/60","bg-canvas","bg-card","bg-clip-padding","bg-destructive","bg-destructive/10","bg-destructive/15","bg-destructive/5","bg-destructive/70","bg-emerald-500","bg-emerald-500/10","bg-foreground","bg-green-500","bg-green-500/10","bg-green-500/70","bg-input","bg-muted","bg-muted-foreground","bg-muted-foreground/15","bg-muted-foreground/20","bg-muted-foreground/25","bg-muted-foreground/30","bg-muted-foreground/40","bg-muted-foreground/50","bg-muted/10","bg-muted/15","bg-muted/20","bg-muted/30","bg-muted/40","bg-muted/50","bg-muted/60","bg-muted/70","bg-muted/[0.08]","bg-overlay","bg-popover","bg-primary","bg-red-400","bg-red-500","bg-red-500/10","bg-red-500/40","bg-secondary","bg-sidebar","bg-sidebar-border","bg-surface","bg-surface-dim/40","bg-surface-dim/60","bg-surface/40","bg-transparent","bg-warning","bg-warning/10","bg-warning/20","bg-warning/40","bg-warning/5","bg-warning/70","block","blur","border","border-0","border-2","border-accent-brand","border-accent-brand/20","border-accent-brand/30","border-accent-brand/40","border-accent-brand/50","border-accent-brand/60","border-accent-brand/70","border-b","border-b-2","border-b-accent-brand","border-border","border-border/30","border-border/40","border-border/50","border-border/60","border-current","border-dashed","border-destructive","border-destructive/20","border-destructive/30","border-destructive/40","border-destructive/50","border-emerald-500/40","border-foreground","border-foreground/10","border-green-500/30","border-green-500/40","border-input","border-l","border-l-2","border-l-transparent","border-muted-foreground/30","border-primary","border-r","border-red-500/40","border-sidebar-border","border-t","border-t-0","border-t-border/40","border-t-muted-foreground/70","border-t-transparent","border-transparent","border-warning/30","border-warning/40","border-x","border-y","bottom-0","bottom-4","break-all","break-words","capitalize","catppuccin","col-span-2","col-start-2","collapse","columns-2","columns-3","com","container","contents","cursor-col-resize","cursor-default","cursor-grab","cursor-not-allowed","cursor-ns-resize","cursor-pointer","cursor-row-resize","dark","dark:aria-invalid:border-destructive/50","dark:aria-invalid:ring-destructive/40","dark:bg-destructive/20","dark:bg-destructive/60","dark:bg-input/30","dark:bg-transparent","dark:border-accent-brand/40","dark:border-input","dark:data-[state=active]:bg-input/30","dark:data-[state=active]:border-input","dark:data-[state=active]:text-foreground","dark:data-[state=checked]:bg-primary","dark:data-[state=checked]:bg-primary-foreground","dark:data-[state=unchecked]:bg-foreground","dark:data-[state=unchecked]:bg-input/80","dark:data-[variant=destructive]:focus:bg-destructive/20","dark:disabled:bg-input/80","dark:focus-visible:ring-destructive/40","dark:hover:bg-accent-brand/10","dark:hover:bg-destructive/10","dark:hover:bg-destructive/30","dark:hover:bg-input/50","dark:hover:bg-muted/50","dark:text-muted-foreground","data-[disabled=true]:opacity-50","data-[disabled=true]:pointer-events-none","data-[disabled]:opacity-50","data-[disabled]:pointer-events-none","data-[orientation=horizontal]:h-1.5","data-[orientation=horizontal]:h-full","data-[orientation=horizontal]:h-px","data-[orientation=horizontal]:w-full","data-[orientation=vertical]:flex-col","data-[orientation=vertical]:h-full","data-[orientation=vertical]:min-h-44","data-[orientation=vertical]:self-center","data-[orientation=vertical]:self-stretch","data-[orientation=vertical]:w-1.5","data-[orientation=vertical]:w-auto","data-[orientation=vertical]:w-full","data-[orientation=vertical]:w-px","data-[placeholder]:text-muted-foreground","data-[selected=true]:bg-muted","data-[selected=true]:text-accent-foreground","data-[side=bottom]:border-t","data-[side=bottom]:bottom-0","data-[side=bottom]:data-closed:slide-out-to-bottom-10","data-[side=bottom]:data-open:slide-in-from-bottom-10","data-[side=bottom]:h-auto","data-[side=bottom]:inset-x-0","data-[side=bottom]:slide-in-from-top-2","data-[side=bottom]:translate-y-1","data-[side=left]:-translate-x-1","data-[side=left]:border-r","data-[side=left]:data-closed:slide-out-to-left-10","data-[side=left]:data-open:slide-in-from-left-10","data-[side=left]:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]","data-[side=left]:h-full","data-[side=left]:inset-y-0","data-[side=left]:left-0","data-[side=left]:slide-in-from-right-2","data-[side=left]:sm:max-w-sm","data-[side=left]:w-3/4","data-[side=right]:border-l","data-[side=right]:data-closed:slide-out-to-right-10","data-[side=right]:data-open:slide-in-from-right-10","data-[side=right]:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]","data-[side=right]:h-full","data-[side=right]:inset-y-0","data-[side=right]:right-0","data-[side=right]:slide-in-from-left-2","data-[side=right]:sm:max-w-sm","data-[side=right]:translate-x-1","data-[side=right]:w-3/4","data-[side=top]:-translate-y-1","data-[side=top]:border-b","data-[side=top]:data-closed:slide-out-to-top-10","data-[side=top]:data-open:slide-in-from-top-10","data-[side=top]:h-auto","data-[side=top]:inset-x-0","data-[side=top]:slide-in-from-bottom-2","data-[side=top]:top-0","data-[size=default]:h-9","data-[size=md]:text-xs","data-[size=sm]:gap-2","data-[size=sm]:h-8","data-[size=sm]:has-data-[slot=card-footer]:pb-0","data-[size=sm]:py-3","data-[size=sm]:text-xs","data-[state=active]:animate-in","data-[state=active]:bg-background","data-[state=active]:fade-in-0","data-[state=active]:shadow-sm","data-[state=checked]:bg-primary","data-[state=checked]:border-primary","data-[state=checked]:text-primary-foreground","data-[state=checked]:translate-x-[calc(100%-2px)]","data-[state=closed]:animate-out","data-[state=closed]:fade-out-0","data-[state=closed]:overflow-hidden","data-[state=closed]:zoom-out-95","data-[state=delayed-open]:animate-in","data-[state=delayed-open]:fade-in-0","data-[state=delayed-open]:zoom-in-95","data-[state=inactive]:animate-out","data-[state=inactive]:fade-out-0","data-[state=open]:animate-in","data-[state=open]:fade-in-0","data-[state=open]:zoom-in-95","data-[state=unchecked]:bg-input","data-[state=unchecked]:translate-x-0","data-[variant=destructive]:*:[svg]:text-destructive","data-[variant=destructive]:focus:bg-destructive/10","data-[variant=destructive]:focus:text-destructive","data-[variant=destructive]:text-destructive","data-active:bg-sidebar-accent","data-active:font-medium","data-active:text-sidebar-accent-foreground","data-closed:animate-out","data-closed:fade-out-0","data-closed:zoom-out-95","data-disabled:opacity-50","data-disabled:pointer-events-none","data-inset:pl-7","data-open:animate-in","data-open:bg-accent","data-open:fade-in-0","data-open:hover:bg-sidebar-accent","data-open:hover:text-sidebar-accent-foreground","data-open:text-accent-foreground","data-open:zoom-in-95","delay-75","disabled:bg-input/50","disabled:cursor-not-allowed","disabled:opacity-30","disabled:opacity-40","disabled:opacity-50","disabled:opacity-60","disabled:pointer-events-none","divide-border","divide-x","divide-y","dracula","drop-shadow","duration-100","duration-150","duration-200","duration-500","duration-[450ms]","ease-in-out","ease-linear","ease-out","enabled:hover:text-accent-brand","fade-in","file:bg-transparent","file:border-0","file:font-medium","file:h-6","file:inline-flex","file:text-foreground","file:text-xs","fill-foreground","filter","fixed","flex","flex-1","flex-col","flex-col-reverse","flex-row","flex-wrap","focus-visible:border-destructive/40","focus-visible:border-ring","focus-visible:outline-1","focus-visible:outline-hidden","focus-visible:outline-none","focus-visible:outline-ring","focus-visible:relative","focus-visible:ring-0","focus-visible:ring-1","focus-visible:ring-2","focus-visible:ring-4","focus-visible:ring-[#f59145]/40","focus-visible:ring-[3px]","focus-visible:ring-destructive/20","focus-visible:ring-inset","focus-visible:ring-ring","focus-visible:ring-ring/50","focus-visible:z-10","focus-within:border-ring","focus-within:opacity-100","focus-within:ring-1","focus-within:ring-ring","focus-within:ring-ring/50","focus:**:text-accent-foreground","focus:bg-accent","focus:ring-1","focus:ring-ring","focus:text-accent-brand","focus:text-accent-foreground","focus:text-destructive","font-bold","font-heading","font-medium","font-mono","font-normal","font-semibold","fs-lg","fs-md","fs-sm","fs-xl","fs-xs","gap-0","gap-0.5","gap-1","gap-1.5","gap-2","gap-2.5","gap-3","gap-3.5","gap-4","gap-5","gap-6","gap-[1.75px]","gap-[10.5px]","gap-[2px]","gap-[3.5px]","gap-[3px]","gap-px","gap-x-2","gap-x-3","gap-x-4","gap-x-6","gap-y-0","gap-y-0.5","gap-y-1","gap-y-1.5","gap-y-2","gap-y-3","grid","grid-cols-1","grid-cols-2","grid-cols-3","grid-cols-4","grid-cols-6","grid-cols-8","grid-cols-[0_1fr]","grid-cols-[1fr_5rem]","grid-flow-col","group","group-[.toast]:!border-accent-brand/30","group-[.toast]:!border-destructive/30","group-[.toast]:!text-accent-brand","group-[.toast]:!text-destructive","group-[.toast]:bg-accent-brand","group-[.toast]:bg-muted","group-[.toast]:font-semibold","group-[.toast]:text-muted-foreground","group-[.toast]:text-white","group-[.toaster]:bg-card","group-[.toaster]:border-border","group-[.toaster]:font-mono","group-[.toaster]:rounded-none!","group-[.toaster]:shadow-lg","group-[.toaster]:text-card-foreground","group-data-[collapsible=icon]:-mt-8","group-data-[collapsible=icon]:hidden","group-data-[collapsible=icon]:opacity-0","group-data-[collapsible=icon]:overflow-hidden","group-data-[collapsible=icon]:p-0!","group-data-[collapsible=icon]:p-2!","group-data-[collapsible=icon]:size-8!","group-data-[collapsible=icon]:w-(--sidebar-width-icon)","group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]","group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]","group-data-[collapsible=offcanvas]:after:left-full","group-data-[collapsible=offcanvas]:translate-x-0","group-data-[collapsible=offcanvas]:w-0","group-data-[disabled=true]:opacity-50","group-data-[disabled=true]:pointer-events-none","group-data-[selected=true]:opacity-100","group-data-[side=left]:-right-4","group-data-[side=left]:border-r","group-data-[side=right]:border-l","group-data-[side=right]:left-0","group-data-[side=right]:rotate-180","group-data-[size=sm]/card:[.border-b]:pb-3","group-data-[size=sm]/card:p-3","group-data-[size=sm]/card:px-3","group-data-[size=sm]/card:text-sm","group-data-[variant=floating]:ring-1","group-data-[variant=floating]:ring-sidebar-border","group-data-[variant=floating]:rounded-none","group-data-[variant=floating]:shadow-sm","group-focus-within/folder:flex","group-focus-within/menu-item:opacity-100","group-focus-within/row:flex","group-focus/dropdown-menu-item:text-accent-foreground","group-has-data-[sidebar=menu-action]/menu-item:pr-8","group-hover/btn:bg-accent-brand/20","group-hover/btn:border-accent-brand/40","group-hover/folder:flex","group-hover/folder:opacity-100","group-hover/folder:text-muted-foreground/70","group-hover/menu-item:opacity-100","group-hover/open:text-accent-brand","group-hover/resize:bg-accent-brand/60","group-hover/row:flex","group-hover/row:text-muted-foreground/60","group-hover/tab:opacity-100","group-hover:bg-accent-brand/60","group-hover:hidden","group-hover:max-h-[60px]","group-hover:mt-0","group-hover:opacity-100","group-hover:text-muted-foreground/70","group/btn","group/card","group/dropdown-menu-item","group/folder","group/menu-item","group/open","group/resize","group/row","group/tab","grow","gruvbox","h-0","h-0.5","h-1","h-1.5","h-10","h-11","h-12","h-12.5","h-14","h-16","h-2.5","h-20","h-24","h-28","h-3","h-3.5","h-4","h-5","h-6","h-7","h-8","h-9","h-[1.15rem]","h-[136px]","h-[14px]","h-[2px]","h-[3px]","h-[46%]","h-[calc(100%-1px)]","h-[var(--radix-select-trigger-height)]","h-full","h-px","h-screen","h-svh","has-[>img:first-child]:pt-0","has-[>svg]:gap-x-3","has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr]","has-[[data-state=open]]:opacity-100","has-data-[icon=inline-end]:pr-1.5","has-data-[icon=inline-end]:pr-2","has-data-[icon=inline-start]:pl-1.5","has-data-[icon=inline-start]:pl-2","has-data-[slot=card-action]:grid-cols-[1fr_auto]","has-data-[slot=card-description]:grid-rows-[auto_auto]","has-data-[slot=card-footer]:pb-0","has-data-[slot=kbd]:pr-1.5","has-data-[variant=inset]:bg-sidebar","hidden","host-status-stripe","hover:after:bg-sidebar-border","hover:bg-accent","hover:bg-accent-brand","hover:bg-accent-brand/10","hover:bg-accent-brand/15","hover:bg-accent-brand/20","hover:bg-accent-brand/40","hover:bg-accent-brand/5","hover:bg-accent-brand/90","hover:bg-background/80","hover:bg-destructive/10","hover:bg-destructive/20","hover:bg-destructive/90","hover:bg-foreground/[0.03]","hover:bg-muted","hover:bg-muted-foreground/10","hover:bg-muted/20","hover:bg-muted/30","hover:bg-muted/40","hover:bg-muted/50","hover:bg-muted/60","hover:bg-secondary/80","hover:bg-sidebar-accent","hover:bg-surface","hover:border-accent-brand","hover:border-accent-brand/30","hover:border-accent-brand/40","hover:border-accent-brand/60","hover:border-destructive/40","hover:border-foreground/40","hover:border-muted-foreground","hover:border-ring/60","hover:group-data-[collapsible=offcanvas]:bg-sidebar","hover:opacity-100","hover:ring-4","hover:ring-[#f59145]/40","hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]","hover:text-accent-brand","hover:text-accent-brand/70","hover:text-accent-brand/80","hover:text-accent-foreground","hover:text-destructive","hover:text-destructive/80","hover:text-foreground","hover:text-muted-foreground","hover:text-primary","hover:text-sidebar-accent-foreground","hover:underline","in-data-[side=left]:cursor-w-resize","in-data-[side=right]:cursor-e-resize","inline","inline-block","inline-flex","inset-0","inset-x-0","inset-x-3","inset-y-0","invisible","isolate","italic","items-baseline","items-center","items-end","items-start","items-stretch","justify-between","justify-center","justify-end","justify-items-start","justify-self-end","justify-start","last:border-0","last:border-b-0","leading-[1.4]","leading-[1.7]","leading-[14px]","leading-none","leading-relaxed","leading-snug","leading-tight","left-0","left-1/2","left-2","left-2.5","left-[50%]","lg:block","lg:col-span-2","lg:flex","lg:grid-cols-1","lg:grid-cols-2","lg:grid-cols-3","lg:grid-cols-4","lg:grid-cols-5","lg:hidden","lg:inline","line-clamp-2","line-clamp-3","line-clamp-4","line-through","list-disc","list-inside","lowercase","ltr:-translate-x-1/2","lucide","m-2.5","m-auto","max-h-(--radix-dropdown-menu-content-available-height)","max-h-(--radix-popover-content-available-height)","max-h-(--radix-select-content-available-height)","max-h-0","max-h-24","max-h-40","max-h-56","max-h-64","max-h-96","max-h-[130px]","max-h-[300px]","max-h-[60px]","max-h-[60vh]","max-h-[80dvh]","max-w-(--skeleton-width)","max-w-0","max-w-2xl","max-w-32","max-w-3xl","max-w-40","max-w-48","max-w-5xl","max-w-72","max-w-96","max-w-[120px]","max-w-[160px]","max-w-[180px]","max-w-[200px]","max-w-[260px]","max-w-[380px]","max-w-[50%]","max-w-[560px]","max-w-[5rem]","max-w-[60%]","max-w-[calc(100%-2rem)]","max-w-full","max-w-lg","max-w-md","max-w-sm","max-w-xl","max-w-xs","mb-0.5","mb-1","mb-1.5","mb-2","mb-4","md:-mx-4","md:after:hidden","md:block","md:col-span-1","md:flex","md:flex-row","md:grid-cols-1","md:grid-cols-2","md:grid-cols-3","md:grid-cols-4","md:hidden","md:max-h-none","md:opacity-0","md:overflow-visible","md:overflow-y-auto","md:peer-data-[variant=inset]:m-2","md:peer-data-[variant=inset]:ml-0","md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2","md:peer-data-[variant=inset]:rounded-none","md:peer-data-[variant=inset]:shadow-sm","md:px-3.5","md:px-4","md:size-4","md:static","md:text-2xl","md:text-xs","md:w-44","md:w-48","md:w-56","md:w-60","md:w-72","min-[280px]:inline","min-h-0","min-h-11","min-h-16","min-h-24","min-h-48","min-h-8","min-h-9","min-h-[19px]","min-h-[220px]","min-h-[70vh]","min-h-[80px]","min-h-full","min-h-svh","min-w-(--radix-popover-trigger-width)","min-w-0","min-w-16","min-w-3","min-w-32","min-w-4","min-w-40","min-w-44","min-w-5","min-w-52","min-w-[14px]","min-w-[150px]","min-w-[160px]","min-w-[170px]","min-w-[180px]","min-w-[200px]","min-w-[8rem]","min-w-[96px]","min-w-[var(--radix-select-trigger-width)]","min-w-max","ml-0.5","ml-1","ml-1.5","ml-2","ml-3","ml-3.5","ml-4","ml-8","ml-[27px]","ml-auto","motion-context-enter","motion-fade-enter","motion-interactive","motion-meter","motion-panel-enter","motion-transfer-phase","motion-workspace-enter","motion-workspace-layout","mr-1","mr-1.5","mr-2","mt-0.5","mt-1","mt-1.5","mt-2","mt-2.5","mt-3","mt-4","mt-[1.75px]","mt-auto","mt-px","mx-0.5","mx-2","mx-3.5","mx-4","mx-auto","my-0.5","my-1","my-3","my-auto","nord","normal-case","not-data-[variant=destructive]:focus:**:text-accent-foreground","object-contain","one-dark","opacity-0","opacity-100","opacity-40","opacity-50","opacity-60","opacity-70","opacity-80","origin-(--radix-dropdown-menu-content-transform-origin)","origin-(--radix-popover-content-transform-origin)","origin-(--radix-select-content-transform-origin)","origin-(--radix-tooltip-content-transform-origin)","origin-center","outline","outline-hidden","outline-none","overflow-auto","overflow-hidden","overflow-x-auto","overflow-x-hidden","overflow-y-auto","overflow-y-hidden","overline","p-0","p-0.5","p-1","p-1.5","p-10","p-2","p-2.5","p-3","p-4","p-5","p-6","p-8","p-[3px]","p-px","paused","pb-0","pb-1","pb-1.5","pb-2","pb-3","pb-6","peer","peer-data-[size=default]/menu-button:top-1.5","peer-data-[size=lg]/menu-button:top-2.5","peer-data-[size=sm]/menu-button:top-1","peer-data-active/menu-button:text-sidebar-accent-foreground","peer-disabled:cursor-not-allowed","peer-disabled:opacity-50","peer-hover/menu-button:text-sidebar-accent-foreground","peer/menu-button","pl-1","pl-2","pl-2.5","pl-4","pl-5","pl-5.5","pl-6","pl-7","pl-8","pl-[7px]","pl-[8.75px]","pl-px","placeholder:text-muted-foreground","placeholder:text-muted-foreground/50","pointer-coarse:flex","pointer-coarse:opacity-100","pointer-events-none","pr-0.5","pr-1","pr-1.5","pr-12","pr-2","pr-2.5","pr-7","pr-8","pr-9","pr-[5.25px]","pr-[7px]","pt-0.5","pt-1","pt-1.5","pt-2","pt-2.5","pt-3","pt-4","pt-[15vh]","pt-[3.5px]","pt-[3px]","pt-[5.25px]","px-0","px-0.5","px-1","px-1.5","px-2","px-2.5","px-3","px-3.5","px-4","px-5","px-6","px-[3px]","py-0","py-0.5","py-1","py-1.5","py-10","py-12","py-2","py-2.5","py-3","py-4","py-6","py-8","py-[1px]","py-[2px]","py-[5px]","py-[7px]","py-px","relative","resize","resize-none","right-0","right-1","right-1.5","right-2","right-2.5","right-3","ring","ring-0","ring-1","ring-2","ring-accent-brand","ring-accent-brand/50","ring-border","ring-foreground/10","ring-inset","ring-offset-1","ring-offset-background","ring-sidebar-ring","ring-white/60","rotate-180","rotate-45","rotate-90","rounded","rounded-[inherit]","rounded-full","rounded-none","rounded-sm","row-span-2","row-start-1","rtl:-translate-x-1/2","running","safe-bottom","scale-110","scroll-my-1","scrollbar-none","select-none","self-end","self-start","self-stretch","settings-search-hit","shadow","shadow-2xl","shadow-[0_0_0_1px_hsl(var(--sidebar-border))]","shadow-lg","shadow-md","shadow-none","shadow-sm","shadow-xl","shadow-xs","shrink-0","size-1.5","size-10","size-12","size-2","size-2.5","size-3","size-3.5","size-4","size-5","size-6","size-6.5","size-7","size-8","size-9","size-[10.5px]","size-[17.5px]","size-[19px]","size-[22.75px]","size-full","size-px","sm:border-b-0","sm:border-r","sm:col-span-2","sm:flex","sm:flex-row","sm:grid-cols-12","sm:grid-cols-2","sm:grid-cols-3","sm:grid-cols-4","sm:items-center","sm:justify-end","sm:max-w-lg","sm:max-w-sm","sm:mr-auto","sm:py-14","sm:text-left","solarized","space-y-0.5","space-y-1.5","space-y-2","sr-only","static","sticky","stroke-accent-brand","stroke-border","stroke-muted","supports-backdrop-filter:backdrop-blur-xs","tab-1","tab-2","table","tabular-nums","text-2xl","text-[10px]","text-[11px]","text-[12px]","text-[13px]","text-[9px]","text-accent-brand","text-accent-brand/50","text-accent-brand/80","text-accent-foreground","text-background","text-base","text-blue-400","text-blue-500","text-card-foreground","text-center","text-current","text-destructive","text-destructive/80","text-emerald-500","text-foreground","text-foreground-secondary","text-foreground/70","text-foreground/80","text-green-500","text-left","text-lg","text-muted-foreground","text-muted-foreground/0","text-muted-foreground/20","text-muted-foreground/30","text-muted-foreground/35","text-muted-foreground/40","text-muted-foreground/45","text-muted-foreground/50","text-muted-foreground/60","text-muted-foreground/70","text-muted-foreground/80","text-popover-foreground","text-primary","text-primary-foreground","text-red-500","text-right","text-secondary-foreground","text-sidebar-foreground","text-sidebar-foreground/70","text-sm","text-warning","text-white","text-xl","text-xs","text-xs/relaxed","thin-scrollbar","toast","toaster","tokyo-night","top-0","top-1","top-1.5","top-1/2","top-2","top-20","top-3","top-3.5","top-[50%]","touch-none","tracking-[0.35em]","tracking-[0.3em]","tracking-normal","tracking-tight","tracking-wide","tracking-wider","tracking-widest","transfer-progress-fill","transfer-progress-indeterminate","transfer-progress-stalled","transfer-progress-track","transform","transition","transition-[background-color,border-color,box-shadow,opacity]","transition-[color,background-color,border-color,opacity,box-shadow,transform]","transition-[color,box-shadow]","transition-[height,border-color]","transition-[height]","transition-[left,right,width]","transition-[left,top,width,height]","transition-[margin,opacity]","transition-[max-height,opacity,margin]","transition-[opacity,max-width]","transition-[opacity,width]","transition-[padding]","transition-[stroke-dasharray]","transition-[width,height,padding]","transition-[width]","transition-all","transition-colors","transition-none","transition-opacity","transition-shadow","transition-transform","translate-x-0.5","translate-x-4","translate-x-[-50%]","translate-x-px","translate-y-[-1px]","translate-y-[-50%]","translate-y-[calc(-50%_-_2px)]","truncate","underline","underline-offset-2","underline-offset-4","uppercase","visible","w-(--radix-dropdown-menu-trigger-width)","w-(--sidebar-width)","w-0","w-0.5","w-1","w-1.5","w-1/2","w-1/4","w-10","w-11","w-12","w-12.5","w-2","w-2.5","w-2/3","w-20","w-28","w-3","w-3/4","w-4","w-40","w-48","w-5","w-5/6","w-52","w-56","w-6","w-60","w-64","w-72","w-8","w-9","w-[240px]","w-[2px]","w-[3px]","w-[46%]","w-[min(85vw,360px)]","w-[var(--radix-popover-trigger-width)]","w-auto","w-fit","w-full","w-max","w-px","w-screen","whitespace-normal","whitespace-nowrap","whitespace-pre","whitespace-pre-line","whitespace-pre-wrap","xl:block","xl:flex","xl:grid-cols-1","xl:grid-cols-2","xl:grid-cols-3","xl:grid-cols-4","z-0","z-10","z-20","z-30","z-40","z-50","z-500","z-[10000]","z-[100]","z-[200]","z-[60]","z-[9998]","z-[99999]"]
@@ -3,7 +3,7 @@ import {
3
3
  SYNC_CHANGED_EVENT,
4
4
  getLinkedSession,
5
5
  notifySyncChanged
6
- } from "./chunk-LKYAVM4T.js";
6
+ } from "./chunk-OEOKIKXC.js";
7
7
  import "./chunk-YC2MAKSP.js";
8
8
  export {
9
9
  SYNC_CHANGED_EVENT,
@@ -23,7 +23,7 @@ import {
23
23
  shareHost,
24
24
  updateHostAccess,
25
25
  updateRole
26
- } from "./chunk-LKYAVM4T.js";
26
+ } from "./chunk-OEOKIKXC.js";
27
27
  import "./chunk-YC2MAKSP.js";
28
28
  export {
29
29
  assignRoleToUser,
@@ -40,16 +40,16 @@ import {
40
40
  setShellCallbacks,
41
41
  setShellHosts,
42
42
  setShellLayoutProvider
43
- } from "./chunk-SS43S6VI.js";
43
+ } from "./chunk-VETPF5WI.js";
44
44
  import {
45
45
  setPermissionsForTesting
46
- } from "./chunk-XKMRKQFN.js";
46
+ } from "./chunk-KOOUKYW3.js";
47
47
  import {
48
48
  getTabType,
49
49
  listTabTypes,
50
50
  setFrontendState,
51
51
  setPluginSummaries
52
- } from "./chunk-LKYAVM4T.js";
52
+ } from "./chunk-OEOKIKXC.js";
53
53
  import "./chunk-YC2MAKSP.js";
54
54
 
55
55
  // src/ui/plugin-host/testing-host.tsx
@@ -2,7 +2,7 @@
2
2
  * Credential sidebar preferences model. Shared by the frontend sidebar and
3
3
  * the backend preferences endpoint (no framework imports, mirrors
4
4
  * ./host-sidebar-preferences.ts's dependency-free convention). Independent
5
- * from HostSidebarPreferences by design — a separate parallel system, not a
5
+ * from HostSidebarPreferences by design, a separate parallel system, not a
6
6
  * shared blob, matching how credentialSortKey/credentialFilterState were
7
7
  * already independently namespaced from hostSortKey/etc. before this port.
8
8
  *
@@ -1,5 +1,6 @@
1
1
  import type { ChoiceAdjustment, PluginChoice } from "../../types/plugin-onboarding";
2
2
  import type { ChangelogRelease, ReleaseNotes } from "@termix-ssh/plugin-sdk/changelog";
3
+ import type { PluginEnvVar } from "@termix-ssh/plugin-sdk/docs";
3
4
  export type { PluginChoice };
4
5
  export interface PluginTabContribution {
5
6
  id: string;
@@ -138,6 +139,10 @@ export interface PluginSummary {
138
139
  videoId?: string;
139
140
  /** What the plugin does, from the manifest. Admins only. */
140
141
  features?: string[];
142
+ /** https docs link from the manifest. */
143
+ docs?: string;
144
+ /** Environment variables the plugin reads. Admins only. */
145
+ env?: PluginEnvVar[];
141
146
  signedBy?: string | null;
142
147
  }
143
148
  export declare function getPlugins(): Promise<PluginSummary[]>;
@@ -165,6 +170,7 @@ export interface RegistryPluginEntry {
165
170
  icon?: string;
166
171
  videoId?: string;
167
172
  features?: string[];
173
+ docs?: string;
168
174
  versions: RegistryPluginVersion[];
169
175
  /** Newest release on the plugin's channel. */
170
176
  latestVersion: string | null;
@@ -0,0 +1,25 @@
1
+ import type { ReactNode } from "react";
2
+ import { type DocsPage } from "../lib/docs";
3
+ /** A plugin's docs link from its manifest, or null when it has none. */
4
+ export declare function usePluginDocsUrl(pluginId: string | null | undefined, page?: string, anchor?: string): string | null;
5
+ export interface DocsLinkProps {
6
+ /** A core docs page. */
7
+ core?: DocsPage;
8
+ /**
9
+ * A page of the current plugin's docs, like "setup". Inside a plugin this
10
+ * resolves against its manifest docs link; "" is its main page.
11
+ */
12
+ page?: string;
13
+ anchor?: string;
14
+ /** A full URL, used as is. */
15
+ href?: string | null;
16
+ /** "text" is a small inline link, "icon" a square header button. */
17
+ variant?: "text" | "icon";
18
+ children?: ReactNode;
19
+ className?: string;
20
+ }
21
+ /**
22
+ * The one way the app links to docs. Renders nothing when there is nowhere
23
+ * to go, like a community plugin with no docs link.
24
+ */
25
+ export declare function DocsLink({ core, page, anchor, href, variant, children, className, }: DocsLinkProps): import("react").JSX.Element;
@@ -10,7 +10,7 @@ export declare const PANEL: {
10
10
  export type PanelViewMode = "grid" | "list";
11
11
  export type PanelDensity = "comfortable" | "compact";
12
12
  /** The header, toolbar band and body every tab and panel draws. */
13
- export declare function PanelShell({ chrome, icon, title, status, leading, actions, toolbar, tabs, footer, scroll, className, children, }: {
13
+ export declare function PanelShell({ chrome, icon, title, status, leading, actions, toolbar, tabs, footer, scroll, docs, className, children, }: {
14
14
  /** False inside a sidebar, which draws its own header. */
15
15
  chrome?: boolean;
16
16
  icon?: React.ReactNode;
@@ -27,6 +27,8 @@ export declare function PanelShell({ chrome, icon, title, status, leading, actio
27
27
  footer?: React.ReactNode;
28
28
  /** False when the body scrolls itself. */
29
29
  scroll?: boolean;
30
+ /** Docs URL for this screen, shown as a book icon in the header. */
31
+ docs?: string | null;
30
32
  className?: string;
31
33
  children: React.ReactNode;
32
34
  }): React.JSX.Element;
@@ -0,0 +1,10 @@
1
+ import DOCS_PAGES from "./docs-pages.json";
2
+ export declare const DOCS_URL = "https://docs.termix.site";
3
+ /**
4
+ * Every core docs page the app links to. The docs site checks each one
5
+ * exists, so add new links here rather than writing URLs inline.
6
+ */
7
+ export type DocsPage = keyof typeof DOCS_PAGES;
8
+ /** Full URL of a core docs page. */
9
+ export declare function docsUrl(page?: DocsPage): string;
10
+ export { DOCS_PAGES };
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Extract a stable, human-readable message from an unknown thrown value.
3
3
  *
4
- * A thrown value is not guaranteed to be an `Error` — libraries may throw
4
+ * A thrown value is not guaranteed to be an `Error`, libraries may throw
5
5
  * strings or plain objects, which carry no useful `.message`. When that
6
6
  * happens the caller gets `fallback` instead, so error paths never surface a
7
7
  * raw `[object Object]` in logs or UI.
@@ -66,7 +66,6 @@ export interface UserInfo {
66
66
  is_dual_auth?: boolean;
67
67
  password_hash?: string;
68
68
  data_unlocked?: boolean;
69
- show_donation_modal?: boolean;
70
69
  /** On a desktop linked to a server, the account it is signed in to there. */
71
70
  linked?: LinkedAccountInfo | null;
72
71
  }
@@ -83,6 +82,7 @@ interface UserCount {
83
82
  count: number;
84
83
  }
85
84
  export { isElectron };
85
+ export declare const TRUST_TOKEN_KEY = "termixTrustToken";
86
86
  export declare function setCookie(name: string, value: string, days?: number): void | Promise<void>;
87
87
  export declare function getCookie(name: string): string | undefined;
88
88
  export declare function markUserAuthenticated(): void;
@@ -120,7 +120,6 @@ export declare function logoutUser(): Promise<{
120
120
  message: string;
121
121
  }>;
122
122
  export declare function getUserInfo(): Promise<UserInfo>;
123
- export declare function dismissDonationModal(): Promise<void>;
124
123
  export declare function getCurrentToken(): Promise<string | null>;
125
124
  export declare function unlockUserData(password: string): Promise<{
126
125
  success: boolean;
@@ -87,6 +87,7 @@ export { getAdaptiveResourceBudget, markAdaptiveResourceUsed, runAdaptiveBackgro
87
87
  export { getDeviceId } from "../lib/device-id";
88
88
  export { createQuickConnectHost } from "../sidebar/quick-connect-host";
89
89
  export { BackButton, Facts, GroupHeading, PANEL, PanelSearch, PanelShell, Segmented, ViewToggle, type PanelDensity, type PanelViewMode, } from "../components/panel-layout";
90
+ export { DocsLink, type DocsLinkProps } from "../components/docs-link";
90
91
  export { AddButton, FormFooter, ListBadge, ListRow, ListRowAction, ListRowFolder, PanelList, type ListRowTone, } from "../components/list-kit";
91
92
  export { DataView, type DataColumn, type GridColumns, } from "../components/data-view";
92
93
  export { TabStrip, type TabStripItem } from "../components/tab-strip";
package/dist/host/ui.js CHANGED
@@ -23,6 +23,7 @@ import {
23
23
  Copy,
24
24
  Corner,
25
25
  Description2,
26
+ DocsLink,
26
27
  ExternalLink,
27
28
  Eye,
28
29
  EyeOff,
@@ -137,8 +138,8 @@ import {
137
138
  useSurfaceKind,
138
139
  useTheme,
139
140
  verifySecondFactor
140
- } from "./chunk-SS43S6VI.js";
141
- import "./chunk-XKMRKQFN.js";
141
+ } from "./chunk-VETPF5WI.js";
142
+ import "./chunk-KOOUKYW3.js";
142
143
  import {
143
144
  TabProvider,
144
145
  buildOriginWsUrl,
@@ -177,7 +178,7 @@ import {
177
178
  useTabs,
178
179
  useTabsSafe,
179
180
  verifyPasswordResetCode
180
- } from "./chunk-LKYAVM4T.js";
181
+ } from "./chunk-OEOKIKXC.js";
181
182
  import "./chunk-YC2MAKSP.js";
182
183
 
183
184
  // src/ui/components/alert.tsx
@@ -3838,6 +3839,7 @@ function HostPluginSection({
3838
3839
  {
3839
3840
  title: plugin.name,
3840
3841
  icon: /* @__PURE__ */ jsx36(PluginIcon, { name: plugin.icon, className: "size-3.5" }),
3842
+ action: /* @__PURE__ */ jsx36(DocsLink, { href: plugin.docs }),
3841
3843
  children: [
3842
3844
  !running && /* @__PURE__ */ jsx36("div", { className: "my-3 border border-warning/40 bg-warning/10 px-3 py-2 text-xs text-warning", children: t2("settings.featureUnavailable", { name: plugin.name }) }),
3843
3845
  enableKey && /* @__PURE__ */ jsx36(
@@ -4426,6 +4428,13 @@ function syncPlugins(summaries) {
4426
4428
  import.meta.hot?.on("termix:plugins-reloaded", () => {
4427
4429
  if (started) void syncPlugins();
4428
4430
  });
4431
+ function installLocaleResolver() {
4432
+ setPluginLocaleResolver((namespace, _language, file) => {
4433
+ const summary = getPluginRecord(namespace)?.summary;
4434
+ if (!summary) return Promise.resolve(null);
4435
+ return currentDeps().loadLocale(summary, file);
4436
+ });
4437
+ }
4429
4438
  async function fetchPreLoginPlugins() {
4430
4439
  const response = await fetch(getBackendUrl("/plugins/public-manifest"));
4431
4440
  if (!response.ok) return [];
@@ -4441,11 +4450,7 @@ async function startPreLoginPlugins(options = {}) {
4441
4450
  return;
4442
4451
  }
4443
4452
  if (list.length === 0 || started) return;
4444
- setPluginLocaleResolver((namespace, _language, file) => {
4445
- const summary = getPluginRecord(namespace)?.summary;
4446
- if (!summary) return Promise.resolve(null);
4447
- return currentDeps().loadLocale(summary, file);
4448
- });
4453
+ installLocaleResolver();
4449
4454
  await enqueue(async () => {
4450
4455
  if (started) return;
4451
4456
  if (options.retryFailed) {
@@ -5026,6 +5031,10 @@ function Auth({ onLogin }) {
5026
5031
  toast2.error(t2("auth.secondFactorUnavailable"));
5027
5032
  return;
5028
5033
  }
5034
+ if (error?.response?.data?.code === "external_login_required") {
5035
+ toast2.error(t2("auth.externalLoginRequired"));
5036
+ return;
5037
+ }
5029
5038
  toast2.error(
5030
5039
  error?.response?.data?.error || error?.message || t2(fallbackKey)
5031
5040
  );
@@ -31908,6 +31917,7 @@ export {
31908
31917
  DialogPortal,
31909
31918
  DialogTitle,
31910
31919
  DialogTrigger,
31920
+ DocsLink,
31911
31921
  DropdownMenu,
31912
31922
  DropdownMenuCheckboxItem,
31913
31923
  DropdownMenuContent,
@@ -7,7 +7,7 @@ import {
7
7
  resetPermissionsCache,
8
8
  setPermissionsForTesting,
9
9
  usePermissions
10
- } from "./chunk-XKMRKQFN.js";
10
+ } from "./chunk-KOOUKYW3.js";
11
11
  import "./chunk-YC2MAKSP.js";
12
12
  export {
13
13
  hasPermission,
@@ -17,7 +17,7 @@ export function execCommand(client, command, timeoutMs = 30000) {
17
17
  if (!settled) {
18
18
  settled = true;
19
19
  cleanup();
20
- reject(new Error(`Command timeout after ${timeoutMs}ms: ${command}`));
20
+ reject(new Error(`Command timeout after ${timeoutMs}ms`));
21
21
  }
22
22
  }, timeoutMs);
23
23
  const cleanup = () => {
@@ -9,6 +9,8 @@
9
9
  * "permissionGroups" used to validate clean and be silently dropped, which
10
10
  * made a manifest look like it declared something it did not.
11
11
  */
12
+ import { type PluginEnvVar } from "./docs.js";
13
+ export { ENV_NAME_PATTERN, MAX_ENV_DESCRIPTION_LENGTH, pluginDocsPage, pluginDocsUrl, pluginEnvVars, type PluginEnvVar, } from "./docs.js";
12
14
  /**
13
15
  * The plugin API this build implements. A minor bump adds to the API and
14
16
  * never removes; a major bump may break plugins.
@@ -17,7 +19,7 @@
17
19
  * "^1.2" needs 1.2 or later). engine.termix is checked against the core
18
20
  * version by the server.
19
21
  */
20
- export declare const PLUGIN_API_VERSION = "1.1.0";
22
+ export declare const PLUGIN_API_VERSION = "1.2.0";
21
23
  /** The API major, the default engine.api for a new plugin. */
22
24
  export declare const SUPPORTED_PLUGIN_API_VERSION: string;
23
25
  /** Whether this build's plugin API satisfies a manifest's engine.api. */
@@ -425,6 +427,10 @@ export interface PluginManifest {
425
427
  video?: string;
426
428
  /** Short lines listing what the plugin does, shown on its page. */
427
429
  features?: string[];
430
+ /** Where the plugin's docs live. https only. */
431
+ docs?: string;
432
+ /** Environment variables the plugin reads. */
433
+ env?: PluginEnvVar[];
428
434
  engine: PluginEngine;
429
435
  /** Catalog capability ids. */
430
436
  capabilities: string[];
package/dist/manifest.js CHANGED
@@ -11,6 +11,8 @@
11
11
  */
12
12
  import semver from "semver";
13
13
  import { isKnownCapability } from "./capabilities.js";
14
+ import { ENV_NAME_PATTERN, MAX_ENV_DESCRIPTION_LENGTH, pluginDocsUrl, } from "./docs.js";
15
+ export { ENV_NAME_PATTERN, MAX_ENV_DESCRIPTION_LENGTH, pluginDocsPage, pluginDocsUrl, pluginEnvVars, } from "./docs.js";
14
16
  /**
15
17
  * The plugin API this build implements. A minor bump adds to the API and
16
18
  * never removes; a major bump may break plugins.
@@ -19,7 +21,7 @@ import { isKnownCapability } from "./capabilities.js";
19
21
  * "^1.2" needs 1.2 or later). engine.termix is checked against the core
20
22
  * version by the server.
21
23
  */
22
- export const PLUGIN_API_VERSION = "1.1.0";
24
+ export const PLUGIN_API_VERSION = "1.2.0";
23
25
  /** The API major, the default engine.api for a new plugin. */
24
26
  export const SUPPORTED_PLUGIN_API_VERSION = String(semver.major(PLUGIN_API_VERSION));
25
27
  /** Whether this build's plugin API satisfies a manifest's engine.api. */
@@ -207,6 +209,8 @@ const ALLOWED_TOP_LEVEL = new Set([
207
209
  "icon",
208
210
  "video",
209
211
  "features",
212
+ "docs",
213
+ "env",
210
214
  "dependencies",
211
215
  "optionalDependencies",
212
216
  "provides",
@@ -337,6 +341,10 @@ export function validateManifest(manifest) {
337
341
  errors.push(`Field "video" must be a YouTube link such as https://www.youtube.com/watch?v=..., got: ${JSON.stringify(m.video)}`);
338
342
  }
339
343
  validateFeatures(m.features, errors);
344
+ if ("docs" in m && pluginDocsUrl(m.docs) === null) {
345
+ errors.push(`Field "docs" must be an https URL, got: ${JSON.stringify(m.docs)}`);
346
+ }
347
+ validateEnv(m.env, errors);
340
348
  for (const field of ["backend", "frontend", "locales"]) {
341
349
  if (field in m && requireString(m[field], field, errors)) {
342
350
  requireRelativePath(m[field], field, errors);
@@ -379,6 +387,45 @@ function validateFeatures(features, errors) {
379
387
  }
380
388
  });
381
389
  }
390
+ function validateEnv(env, errors) {
391
+ if (env === undefined)
392
+ return;
393
+ if (!Array.isArray(env)) {
394
+ errors.push('Field "env" must be an array');
395
+ return;
396
+ }
397
+ const seen = new Set();
398
+ env.forEach((entry, i) => {
399
+ const where = `env[${i}]`;
400
+ if (!isPlainObject(entry)) {
401
+ errors.push(`Field "${where}" must be an object`);
402
+ return;
403
+ }
404
+ rejectUnknown(entry, ["name", "description", "default", "required", "secret"], where, errors);
405
+ if (typeof entry.name !== "string" || !ENV_NAME_PATTERN.test(entry.name)) {
406
+ errors.push(`Field "${where}.name" must be an uppercase name like MY_PLUGIN_URL, got: ${JSON.stringify(entry.name)}`);
407
+ }
408
+ else if (seen.has(entry.name)) {
409
+ errors.push(`Field "${where}.name" repeats ${entry.name}`);
410
+ }
411
+ else {
412
+ seen.add(entry.name);
413
+ }
414
+ if (typeof entry.description !== "string" ||
415
+ entry.description.trim().length === 0 ||
416
+ entry.description.length > MAX_ENV_DESCRIPTION_LENGTH) {
417
+ errors.push(`Field "${where}.description" must be a non-empty string of at most ${MAX_ENV_DESCRIPTION_LENGTH} characters`);
418
+ }
419
+ if ("default" in entry && typeof entry.default !== "string") {
420
+ errors.push(`Field "${where}.default" must be a string`);
421
+ }
422
+ for (const flag of ["required", "secret"]) {
423
+ if (flag in entry && typeof entry[flag] !== "boolean") {
424
+ errors.push(`Field "${where}.${flag}" must be true or false`);
425
+ }
426
+ }
427
+ });
428
+ }
382
429
  function validateAuthor(author, errors) {
383
430
  if (author === undefined)
384
431
  return;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Builds an OpenAPI spec from the @openapi JSDoc blocks above routes. Core and
3
+ * every plugin use this, so the docs site renders them all the same way.
4
+ */
5
+ export interface OpenApiTag {
6
+ name: string;
7
+ description?: string;
8
+ }
9
+ export interface BuildOpenApiOptions {
10
+ title: string;
11
+ version: string;
12
+ description?: string;
13
+ /** Source files or globs holding @openapi blocks. */
14
+ files: string[];
15
+ tags?: OpenApiTag[];
16
+ }
17
+ export type OpenApiSpec = Record<string, unknown> & {
18
+ paths?: Record<string, unknown>;
19
+ };
20
+ /** How every Termix route is signed in to. */
21
+ export declare const TERMIX_SECURITY_SCHEMES: {
22
+ readonly bearerAuth: {
23
+ readonly type: "http";
24
+ readonly scheme: "bearer";
25
+ readonly bearerFormat: "JWT";
26
+ readonly description: "Session token sent in the Authorization header as Bearer and the token.";
27
+ };
28
+ readonly cookieAuth: {
29
+ readonly type: "apiKey";
30
+ readonly in: "cookie";
31
+ readonly name: "jwt";
32
+ readonly description: "Session JWT cookie set at sign-in. When present, it takes precedence over the Authorization header.";
33
+ };
34
+ readonly apiKeyAuth: {
35
+ readonly type: "http";
36
+ readonly scheme: "bearer";
37
+ readonly bearerFormat: "API key starting with tmx_";
38
+ readonly description: "API key sent in the Authorization header as Bearer and the key, which starts with tmx_. It acts as the key owner and cannot act as anyone else.";
39
+ };
40
+ };
41
+ export declare function buildOpenApi(options: BuildOpenApiOptions): Promise<OpenApiSpec>;
42
+ /** Whether a spec documents any route. */
43
+ export declare function hasOpenApiPaths(spec: OpenApiSpec): boolean;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Builds an OpenAPI spec from the @openapi JSDoc blocks above routes. Core and
3
+ * every plugin use this, so the docs site renders them all the same way.
4
+ */
5
+ import swaggerJSDoc from "@deadendjs/swagger-jsdoc";
6
+ /** How every Termix route is signed in to. */
7
+ export const TERMIX_SECURITY_SCHEMES = {
8
+ bearerAuth: {
9
+ type: "http",
10
+ scheme: "bearer",
11
+ bearerFormat: "JWT",
12
+ description: "Session token sent in the Authorization header as Bearer and the token.",
13
+ },
14
+ cookieAuth: {
15
+ type: "apiKey",
16
+ in: "cookie",
17
+ name: "jwt",
18
+ description: "Session JWT cookie set at sign-in. When present, it takes precedence over the Authorization header.",
19
+ },
20
+ apiKeyAuth: {
21
+ type: "http",
22
+ scheme: "bearer",
23
+ bearerFormat: "API key starting with tmx_",
24
+ description: "API key sent in the Authorization header as Bearer and the key, which starts with tmx_. It acts as the key owner and cannot act as anyone else.",
25
+ },
26
+ };
27
+ export async function buildOpenApi(options) {
28
+ const spec = (await swaggerJSDoc({
29
+ definition: {
30
+ openapi: "3.0.3",
31
+ info: {
32
+ title: options.title,
33
+ version: options.version,
34
+ ...(options.description ? { description: options.description } : {}),
35
+ },
36
+ servers: [{ url: "/", description: "Your Termix server" }],
37
+ components: {
38
+ securitySchemes: TERMIX_SECURITY_SCHEMES,
39
+ schemas: {
40
+ Error: {
41
+ type: "object",
42
+ properties: {
43
+ error: { type: "string" },
44
+ details: { type: "string" },
45
+ },
46
+ },
47
+ },
48
+ },
49
+ security: [{ bearerAuth: [] }, { cookieAuth: [] }, { apiKeyAuth: [] }],
50
+ ...(options.tags?.length ? { tags: options.tags } : {}),
51
+ },
52
+ apis: options.files.map((f) => f.replace(/\\/g, "/")),
53
+ failOnErrors: true,
54
+ }));
55
+ return spec;
56
+ }
57
+ /** Whether a spec documents any route. */
58
+ export function hasOpenApiPaths(spec) {
59
+ return Object.keys(spec.paths ?? {}).length > 0;
60
+ }
@@ -77,6 +77,7 @@ const noop = () => { };
77
77
  const offline = () => Promise.reject(new Error("No plugin API in a unit test; mock it"));
78
78
  globalThis.__termixTestPluginHost = {
79
79
  usePluginId: () => "test-plugin",
80
+ useDocsUrl: () => null,
80
81
  useTranslation: () => ({
81
82
  t: (key) => key,
82
83
  language: "en",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@termix-ssh/plugin-sdk",
3
- "version": "1.0.2",
3
+ "version": "1.0.5",
4
4
  "description": "The contract between Termix core and a plugin: types, the capability catalog and the manifest schema.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -53,6 +53,14 @@
53
53
  "types": "./dist/changelog.d.ts",
54
54
  "default": "./dist/changelog.js"
55
55
  },
56
+ "./docs": {
57
+ "types": "./dist/docs.d.ts",
58
+ "default": "./dist/docs.js"
59
+ },
60
+ "./openapi": {
61
+ "types": "./dist/openapi.d.ts",
62
+ "default": "./dist/openapi.js"
63
+ },
56
64
  "./settings": {
57
65
  "types": "./dist/settings.d.ts",
58
66
  "default": "./dist/settings.js"
@@ -102,6 +110,7 @@
102
110
  "prepublishOnly": "npm run build"
103
111
  },
104
112
  "dependencies": {
113
+ "@deadendjs/swagger-jsdoc": "^8.1.2",
105
114
  "@tailwindcss/node": "^4.2.4",
106
115
  "@tailwindcss/oxide": "^4.2.4",
107
116
  "esbuild": "^0.28.1",
@@ -139,6 +148,18 @@
139
148
  },
140
149
  "@testing-library/react": {
141
150
  "optional": true
151
+ },
152
+ "i18next": {
153
+ "optional": true
154
+ },
155
+ "react-dom": {
156
+ "optional": true
157
+ },
158
+ "react-i18next": {
159
+ "optional": true
160
+ },
161
+ "sonner": {
162
+ "optional": true
142
163
  }
143
164
  },
144
165
  "publishConfig": {