@dxos/plugin-registry 0.8.3 → 0.8.4-main.1c7ec43d41

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 (201) hide show
  1. package/dist/lib/browser/cli/index.mjs +32 -0
  2. package/dist/lib/browser/cli/index.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +27 -63
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/translations.mjs +61 -0
  7. package/dist/lib/browser/translations.mjs.map +7 -0
  8. package/dist/lib/node-esm/cli/index.mjs +34 -0
  9. package/dist/lib/node-esm/cli/index.mjs.map +7 -0
  10. package/dist/lib/node-esm/index.mjs +37 -0
  11. package/dist/lib/node-esm/index.mjs.map +7 -0
  12. package/dist/lib/node-esm/meta.json +1 -0
  13. package/dist/lib/node-esm/translations.mjs +63 -0
  14. package/dist/lib/node-esm/translations.mjs.map +7 -0
  15. package/dist/types/src/RegistryPlugin.d.ts +3 -1
  16. package/dist/types/src/RegistryPlugin.d.ts.map +1 -1
  17. package/dist/types/src/RegistryPlugin.node.d.ts +4 -0
  18. package/dist/types/src/RegistryPlugin.node.d.ts.map +1 -0
  19. package/dist/types/src/RegistryPlugin.test.d.ts +2 -0
  20. package/dist/types/src/RegistryPlugin.test.d.ts.map +1 -0
  21. package/dist/types/src/capabilities/app-graph-builder.d.ts +4 -2
  22. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  23. package/dist/types/src/capabilities/dev-plugin-loader.d.ts +11 -0
  24. package/dist/types/src/capabilities/dev-plugin-loader.d.ts.map +1 -0
  25. package/dist/types/src/capabilities/index.d.ts +15 -3
  26. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  27. package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
  28. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
  29. package/dist/types/src/capabilities/react-surface.d.ts +3 -2
  30. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  31. package/dist/types/src/capabilities/settings.d.ts +14 -0
  32. package/dist/types/src/capabilities/settings.d.ts.map +1 -0
  33. package/dist/types/src/cli/index.d.ts +2 -0
  34. package/dist/types/src/cli/index.d.ts.map +1 -0
  35. package/dist/types/src/commands/disable.d.ts +11 -0
  36. package/dist/types/src/commands/disable.d.ts.map +1 -0
  37. package/dist/types/src/commands/enable.d.ts +11 -0
  38. package/dist/types/src/commands/enable.d.ts.map +1 -0
  39. package/dist/types/src/commands/index.d.ts +3 -0
  40. package/dist/types/src/commands/index.d.ts.map +1 -0
  41. package/dist/types/src/commands/list.d.ts +7 -0
  42. package/dist/types/src/commands/list.d.ts.map +1 -0
  43. package/dist/types/src/commands/util.d.ts +11 -0
  44. package/dist/types/src/commands/util.d.ts.map +1 -0
  45. package/dist/types/src/components/PluginDetail/PluginDetail.d.ts +46 -0
  46. package/dist/types/src/components/PluginDetail/PluginDetail.d.ts.map +1 -0
  47. package/dist/types/src/components/PluginDetail/PluginDetail.stories.d.ts +68 -0
  48. package/dist/types/src/components/PluginDetail/PluginDetail.stories.d.ts.map +1 -0
  49. package/dist/types/src/components/PluginDetail/index.d.ts +2 -0
  50. package/dist/types/src/components/PluginDetail/index.d.ts.map +1 -0
  51. package/dist/types/src/components/PluginFailureBadge/PluginFailureBadge.d.ts +20 -0
  52. package/dist/types/src/components/PluginFailureBadge/PluginFailureBadge.d.ts.map +1 -0
  53. package/dist/types/src/components/PluginFailureBadge/index.d.ts +2 -0
  54. package/dist/types/src/components/PluginFailureBadge/index.d.ts.map +1 -0
  55. package/dist/types/src/components/PluginList/PluginItem.d.ts +40 -0
  56. package/dist/types/src/components/PluginList/PluginItem.d.ts.map +1 -0
  57. package/dist/types/src/components/PluginList/PluginList.d.ts +23 -0
  58. package/dist/types/src/components/PluginList/PluginList.d.ts.map +1 -0
  59. package/dist/types/src/components/PluginList/PluginList.stories.d.ts +67 -0
  60. package/dist/types/src/components/PluginList/PluginList.stories.d.ts.map +1 -0
  61. package/dist/types/src/components/PluginList/index.d.ts +2 -0
  62. package/dist/types/src/components/PluginList/index.d.ts.map +1 -0
  63. package/dist/types/src/components/RegistrySettings/RegistrySettings.d.ts +18 -0
  64. package/dist/types/src/components/RegistrySettings/RegistrySettings.d.ts.map +1 -0
  65. package/dist/types/src/components/RegistrySettings/index.d.ts +2 -0
  66. package/dist/types/src/components/RegistrySettings/index.d.ts.map +1 -0
  67. package/dist/types/src/components/index.d.ts +2 -1
  68. package/dist/types/src/components/index.d.ts.map +1 -1
  69. package/dist/types/src/containers/BaseRegistryArticle/BaseRegistryArticle.d.ts +20 -0
  70. package/dist/types/src/containers/BaseRegistryArticle/BaseRegistryArticle.d.ts.map +1 -0
  71. package/dist/types/src/containers/BaseRegistryArticle/index.d.ts +2 -0
  72. package/dist/types/src/containers/BaseRegistryArticle/index.d.ts.map +1 -0
  73. package/dist/types/src/containers/LoadPluginDialog/LoadPluginDialog.d.ts +4 -0
  74. package/dist/types/src/containers/LoadPluginDialog/LoadPluginDialog.d.ts.map +1 -0
  75. package/dist/types/src/containers/LoadPluginDialog/index.d.ts +2 -0
  76. package/dist/types/src/containers/LoadPluginDialog/index.d.ts.map +1 -0
  77. package/dist/types/src/containers/PluginArticle/PluginArticle.d.ts +7 -0
  78. package/dist/types/src/containers/PluginArticle/PluginArticle.d.ts.map +1 -0
  79. package/dist/types/src/containers/PluginArticle/index.d.ts +2 -0
  80. package/dist/types/src/containers/PluginArticle/index.d.ts.map +1 -0
  81. package/dist/types/src/containers/PublicRegistryArticle/PublicRegistryArticle.d.ts +8 -0
  82. package/dist/types/src/containers/PublicRegistryArticle/PublicRegistryArticle.d.ts.map +1 -0
  83. package/dist/types/src/containers/PublicRegistryArticle/index.d.ts +2 -0
  84. package/dist/types/src/containers/PublicRegistryArticle/index.d.ts.map +1 -0
  85. package/dist/types/src/containers/RegistryArticle/RegistryArticle.d.ts +14 -0
  86. package/dist/types/src/containers/RegistryArticle/RegistryArticle.d.ts.map +1 -0
  87. package/dist/types/src/containers/RegistryArticle/index.d.ts +2 -0
  88. package/dist/types/src/containers/RegistryArticle/index.d.ts.map +1 -0
  89. package/dist/types/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.d.ts +13 -0
  90. package/dist/types/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.d.ts.map +1 -0
  91. package/dist/types/src/containers/RegistrySettingsContainer/index.d.ts +2 -0
  92. package/dist/types/src/containers/RegistrySettingsContainer/index.d.ts.map +1 -0
  93. package/dist/types/src/containers/index.d.ts +8 -0
  94. package/dist/types/src/containers/index.d.ts.map +1 -0
  95. package/dist/types/src/hooks/index.d.ts +4 -0
  96. package/dist/types/src/hooks/index.d.ts.map +1 -0
  97. package/dist/types/src/hooks/use-auto-tags.d.ts +20 -0
  98. package/dist/types/src/hooks/use-auto-tags.d.ts.map +1 -0
  99. package/dist/types/src/hooks/use-registry-plugins.d.ts +15 -0
  100. package/dist/types/src/hooks/use-registry-plugins.d.ts.map +1 -0
  101. package/dist/types/src/hooks/use-update-available.d.ts +18 -0
  102. package/dist/types/src/hooks/use-update-available.d.ts.map +1 -0
  103. package/dist/types/src/index.d.ts +2 -1
  104. package/dist/types/src/index.d.ts.map +1 -1
  105. package/dist/types/src/meta.d.ts +7 -4
  106. package/dist/types/src/meta.d.ts.map +1 -1
  107. package/dist/types/src/operations/index.d.ts +3 -0
  108. package/dist/types/src/operations/index.d.ts.map +1 -0
  109. package/dist/types/src/operations/open-plugin-registry.d.ts +5 -0
  110. package/dist/types/src/operations/open-plugin-registry.d.ts.map +1 -0
  111. package/dist/types/src/storage.d.ts +18 -0
  112. package/dist/types/src/storage.d.ts.map +1 -0
  113. package/dist/types/src/translations.d.ts +51 -19
  114. package/dist/types/src/translations.d.ts.map +1 -1
  115. package/dist/types/src/types.d.ts +32 -1
  116. package/dist/types/src/types.d.ts.map +1 -1
  117. package/dist/types/tsconfig.tsbuildinfo +1 -1
  118. package/package.json +74 -38
  119. package/src/RegistryPlugin.node.ts +19 -0
  120. package/src/RegistryPlugin.test.ts +24 -0
  121. package/src/RegistryPlugin.tsx +20 -27
  122. package/src/capabilities/app-graph-builder.ts +180 -135
  123. package/src/capabilities/dev-plugin-loader.ts +39 -0
  124. package/src/capabilities/index.ts +10 -4
  125. package/src/capabilities/operation-handler.ts +16 -0
  126. package/src/capabilities/react-surface.tsx +124 -78
  127. package/src/capabilities/settings.ts +34 -0
  128. package/src/cli/index.ts +5 -0
  129. package/src/commands/disable.ts +47 -0
  130. package/src/commands/enable.ts +44 -0
  131. package/src/commands/index.ts +14 -0
  132. package/src/commands/list.ts +41 -0
  133. package/src/commands/util.ts +24 -0
  134. package/src/components/PluginDetail/PluginDetail.stories.tsx +44 -0
  135. package/src/components/PluginDetail/PluginDetail.tsx +196 -0
  136. package/src/components/PluginDetail/index.ts +5 -0
  137. package/src/components/PluginFailureBadge/PluginFailureBadge.tsx +68 -0
  138. package/src/components/PluginFailureBadge/index.ts +5 -0
  139. package/src/components/PluginList/PluginItem.tsx +226 -0
  140. package/src/components/PluginList/PluginList.stories.tsx +87 -0
  141. package/src/components/PluginList/PluginList.tsx +52 -0
  142. package/src/components/PluginList/index.ts +5 -0
  143. package/src/components/RegistrySettings/RegistrySettings.tsx +120 -0
  144. package/src/components/RegistrySettings/index.ts +5 -0
  145. package/src/components/index.ts +2 -1
  146. package/src/containers/BaseRegistryArticle/BaseRegistryArticle.tsx +163 -0
  147. package/src/containers/BaseRegistryArticle/index.ts +5 -0
  148. package/src/containers/LoadPluginDialog/LoadPluginDialog.tsx +87 -0
  149. package/src/containers/LoadPluginDialog/index.ts +5 -0
  150. package/src/containers/PluginArticle/PluginArticle.tsx +336 -0
  151. package/src/containers/PluginArticle/index.ts +5 -0
  152. package/src/containers/PublicRegistryArticle/PublicRegistryArticle.tsx +192 -0
  153. package/src/containers/PublicRegistryArticle/index.ts +5 -0
  154. package/src/containers/RegistryArticle/RegistryArticle.tsx +53 -0
  155. package/src/containers/RegistryArticle/index.ts +5 -0
  156. package/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.tsx +59 -0
  157. package/src/containers/RegistrySettingsContainer/index.ts +5 -0
  158. package/src/containers/index.ts +12 -0
  159. package/src/hooks/index.ts +7 -0
  160. package/src/hooks/use-auto-tags.ts +51 -0
  161. package/src/hooks/use-registry-plugins.ts +29 -0
  162. package/src/hooks/use-update-available.ts +31 -0
  163. package/src/index.ts +5 -1
  164. package/src/meta.ts +21 -9
  165. package/src/operations/index.ts +7 -0
  166. package/src/operations/open-plugin-registry.ts +21 -0
  167. package/src/storage.ts +50 -0
  168. package/src/translations.ts +57 -18
  169. package/src/types.ts +37 -1
  170. package/dist/lib/browser/app-graph-builder-J5YHAGNI.mjs +0 -161
  171. package/dist/lib/browser/app-graph-builder-J5YHAGNI.mjs.map +0 -7
  172. package/dist/lib/browser/chunk-ISC56E5F.mjs +0 -16
  173. package/dist/lib/browser/chunk-ISC56E5F.mjs.map +0 -7
  174. package/dist/lib/browser/intent-resolver-AQJNBUT3.mjs +0 -23
  175. package/dist/lib/browser/intent-resolver-AQJNBUT3.mjs.map +0 -7
  176. package/dist/lib/browser/react-surface-W5RNDLXV.mjs +0 -335
  177. package/dist/lib/browser/react-surface-W5RNDLXV.mjs.map +0 -7
  178. package/dist/types/src/capabilities/intent-resolver.d.ts +0 -4
  179. package/dist/types/src/capabilities/intent-resolver.d.ts.map +0 -1
  180. package/dist/types/src/components/Icon.stories.d.ts +0 -6
  181. package/dist/types/src/components/Icon.stories.d.ts.map +0 -1
  182. package/dist/types/src/components/PluginDetail.d.ts +0 -9
  183. package/dist/types/src/components/PluginDetail.d.ts.map +0 -1
  184. package/dist/types/src/components/PluginDetail.stories.d.ts +0 -8
  185. package/dist/types/src/components/PluginDetail.stories.d.ts.map +0 -1
  186. package/dist/types/src/components/PluginItem.d.ts +0 -13
  187. package/dist/types/src/components/PluginItem.d.ts.map +0 -1
  188. package/dist/types/src/components/PluginList.d.ts +0 -8
  189. package/dist/types/src/components/PluginList.d.ts.map +0 -1
  190. package/dist/types/src/components/PluginList.stories.d.ts +0 -11
  191. package/dist/types/src/components/PluginList.stories.d.ts.map +0 -1
  192. package/dist/types/src/components/RegistryContainer.d.ts +0 -7
  193. package/dist/types/src/components/RegistryContainer.d.ts.map +0 -1
  194. package/src/capabilities/intent-resolver.ts +0 -27
  195. package/src/components/Icon.stories.tsx +0 -107
  196. package/src/components/PluginDetail.stories.tsx +0 -45
  197. package/src/components/PluginDetail.tsx +0 -63
  198. package/src/components/PluginItem.tsx +0 -119
  199. package/src/components/PluginList.stories.tsx +0 -82
  200. package/src/components/PluginList.tsx +0 -26
  201. package/src/components/RegistryContainer.tsx +0 -84
@@ -0,0 +1,16 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { Capabilities, Capability } from '@dxos/app-framework';
8
+ import type { OperationHandlerSet } from '@dxos/compute';
9
+
10
+ import { RegistryOperationHandlerSet } from '#operations';
11
+
12
+ export default Capability.makeModule<OperationHandlerSet.OperationHandlerSet>(
13
+ Effect.fnUntraced(function* () {
14
+ return Capability.contributes(Capabilities.OperationHandler, RegistryOperationHandlerSet);
15
+ }),
16
+ );
@@ -2,89 +2,135 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import React, { useCallback, useMemo } from 'react';
5
+ import * as Effect from 'effect/Effect';
6
+ import React, { useMemo } from 'react';
6
7
 
7
- import { Capabilities, contributes, createSurface, Plugin, usePluginManager } from '@dxos/app-framework';
8
- import { StackItem } from '@dxos/react-ui-stack';
8
+ import { Capabilities, Capability, Plugin } from '@dxos/app-framework';
9
+ import { Surface, usePluginManager } from '@dxos/app-framework/ui';
10
+ import { AppCapabilities } from '@dxos/app-toolkit';
11
+ import { AppSurface } from '@dxos/app-toolkit/ui';
9
12
 
10
- import { PluginDetail, RegistryContainer } from '../components';
11
- import { REGISTRY_KEY, REGISTRY_PLUGIN } from '../meta';
13
+ import {
14
+ LOAD_PLUGIN_DIALOG,
15
+ LoadPluginDialog,
16
+ PluginArticle,
17
+ PublicRegistryArticle,
18
+ RegistryArticle,
19
+ RegistrySettingsContainer,
20
+ } from '#containers';
21
+ import { meta, registryCategoryId } from '#meta';
12
22
 
13
- export default () =>
14
- contributes(Capabilities.ReactSurface, [
15
- createSurface({
16
- id: `${REGISTRY_PLUGIN}/all`,
17
- role: 'article',
18
- filter: (data): data is any => data.subject === `${REGISTRY_KEY}+all`,
19
- component: () => {
20
- const manager = usePluginManager();
21
- const filtered = useMemo(() => manager.plugins.filter(({ meta }) => !manager.core.includes(meta.id)), []);
23
+ import { useAutoTags, useRegistryPlugins, useRemotePluginIds } from '../hooks';
22
24
 
23
- return <RegistryContainer id={`${REGISTRY_KEY}+all`} plugins={filtered} />;
24
- },
25
- }),
26
- createSurface({
27
- id: `${REGISTRY_PLUGIN}/installed`,
28
- role: 'article',
29
- filter: (data): data is any => data.subject === `${REGISTRY_KEY}+installed`,
30
- component: () => {
31
- const manager = usePluginManager();
32
- const filtered = useMemo(
33
- () =>
34
- manager.plugins
35
- .filter(({ meta }) => !manager.core.includes(meta.id))
36
- .filter(({ meta }) => manager.enabled.includes(meta.id)),
37
- [],
38
- );
25
+ export default Capability.makeModule(() =>
26
+ Effect.succeed(
27
+ Capability.contributes(Capabilities.ReactSurface, [
28
+ Surface.create({
29
+ id: 'official',
30
+ filter: AppSurface.literal(AppSurface.Article, registryCategoryId('official')),
31
+ component: () => {
32
+ const manager = usePluginManager();
33
+ const remoteIds = useRemotePluginIds();
34
+ const core = useMemo(() => manager.getCore(), [manager]);
35
+ const predicate = useMemo<PluginPredicate>(
36
+ () =>
37
+ ({ meta }) =>
38
+ !core.includes(meta.id) && !remoteIds.has(meta.id),
39
+ [core, remoteIds],
40
+ );
39
41
 
40
- return <RegistryContainer id={`${REGISTRY_KEY}+installed`} plugins={filtered} />;
41
- },
42
- }),
43
- createSurface({
44
- id: `${REGISTRY_PLUGIN}/recommended`,
45
- role: 'article',
46
- filter: (data): data is any => data.subject === `${REGISTRY_KEY}+recommended`,
47
- component: () => {
48
- const manager = usePluginManager();
49
- const filtered = useMemo(
50
- () =>
51
- manager.plugins
52
- .filter(({ meta }) => !manager.core.includes(meta.id))
53
- .filter(({ meta }) => !meta.tags?.includes('labs')),
54
- [],
55
- );
42
+ return <FilteredRegistryArticle id={registryCategoryId('official')} filter={predicate} />;
43
+ },
44
+ }),
45
+ Surface.create({
46
+ id: 'installed',
47
+ filter: AppSurface.literal(AppSurface.Article, registryCategoryId('installed')),
48
+ component: () => {
49
+ const manager = usePluginManager();
50
+ const core = useMemo(() => manager.getCore(), [manager]);
51
+ const enabled = useMemo(() => manager.getEnabled(), [manager]);
52
+ const predicate = useMemo<PluginPredicate>(
53
+ () =>
54
+ ({ meta }) =>
55
+ !core.includes(meta.id) && enabled.includes(meta.id),
56
+ [core, enabled],
57
+ );
56
58
 
57
- return <RegistryContainer id={`${REGISTRY_KEY}+recommended`} plugins={filtered} />;
58
- },
59
- }),
60
- createSurface({
61
- id: `${REGISTRY_PLUGIN}/labs`,
62
- role: 'article',
63
- filter: (data): data is any => data.subject === `${REGISTRY_KEY}+labs`,
64
- component: () => {
65
- const manager = usePluginManager();
66
- const filtered = useMemo(() => manager.plugins.filter(({ meta }) => meta.tags?.includes('labs')), []);
59
+ return <FilteredRegistryArticle id={registryCategoryId('installed')} filter={predicate} />;
60
+ },
61
+ }),
62
+ Surface.create({
63
+ id: 'recommended',
64
+ filter: AppSurface.literal(AppSurface.Article, registryCategoryId('recommended')),
65
+ component: () => {
66
+ const manager = usePluginManager();
67
+ const remoteIds = useRemotePluginIds();
68
+ const core = useMemo(() => manager.getCore(), [manager]);
69
+ const predicate = useMemo<PluginPredicate>(
70
+ () =>
71
+ ({ meta }) =>
72
+ !core.includes(meta.id) && !remoteIds.has(meta.id) && !meta.tags?.includes('labs'),
73
+ [core, remoteIds],
74
+ );
67
75
 
68
- return <RegistryContainer id={`${REGISTRY_KEY}+labs`} plugins={filtered} />;
69
- },
70
- }),
71
- createSurface({
72
- id: `${REGISTRY_PLUGIN}/plugin-details`,
73
- role: 'article',
74
- filter: (data): data is { subject: Plugin } => data.subject instanceof Plugin,
75
- component: ({ data: { subject } }) => {
76
- const manager = usePluginManager();
77
- const enabled = manager.enabled.includes(subject.meta.id);
78
- const handleEnable = useCallback(
79
- () => (enabled ? manager.disable(subject.meta.id) : manager.enable(subject.meta.id)),
80
- [manager, subject.meta.id, enabled],
81
- );
76
+ return <FilteredRegistryArticle id={registryCategoryId('recommended')} filter={predicate} />;
77
+ },
78
+ }),
79
+ Surface.create({
80
+ id: 'labs',
81
+ filter: AppSurface.literal(AppSurface.Article, registryCategoryId('labs')),
82
+ component: () => {
83
+ const predicate = useMemo<PluginPredicate>(
84
+ () =>
85
+ ({ meta }) =>
86
+ meta.tags?.includes('labs') ?? false,
87
+ [],
88
+ );
82
89
 
83
- return (
84
- <StackItem.Content role='article'>
85
- <PluginDetail plugin={subject} enabled={enabled} onEnable={handleEnable} />
86
- </StackItem.Content>
87
- );
88
- },
89
- }),
90
- ]);
90
+ return <FilteredRegistryArticle id={registryCategoryId('labs')} filter={predicate} />;
91
+ },
92
+ }),
93
+ Surface.create({
94
+ id: 'registry',
95
+ filter: AppSurface.literal(AppSurface.Article, registryCategoryId('registry')),
96
+ component: () => <PublicRegistryArticle id={registryCategoryId('registry')} />,
97
+ }),
98
+ Surface.create({
99
+ id: 'plugin-details',
100
+ filter: AppSurface.subject(AppSurface.Article, Plugin.isPlugin),
101
+ component: ({ data: { subject } }) => {
102
+ return <PluginArticle subject={subject} />;
103
+ },
104
+ }),
105
+ Surface.create({
106
+ id: LOAD_PLUGIN_DIALOG,
107
+ filter: AppSurface.component(AppSurface.Dialog, LOAD_PLUGIN_DIALOG),
108
+ component: () => <LoadPluginDialog />,
109
+ }),
110
+ Surface.create({
111
+ id: 'plugin-settings',
112
+ role: 'article',
113
+ filter: (data): data is { subject: AppCapabilities.Settings } =>
114
+ AppCapabilities.isSettings(data.subject) && data.subject.prefix === meta.id,
115
+ component: ({ data: { subject } }) => <RegistrySettingsContainer subject={subject} />,
116
+ }),
117
+ ]),
118
+ ),
119
+ );
120
+
121
+ type PluginPredicate = (plugin: Plugin.Plugin) => boolean;
122
+
123
+ /**
124
+ * Renders the {@link RegistryArticle} surface filtered by an arbitrary
125
+ * predicate computed against the live plugin list. Centralises the
126
+ * `usePluginManager` + `useRegistryPlugins` + `useAutoTags` wiring shared
127
+ * by every category surface.
128
+ */
129
+ const FilteredRegistryArticle = ({ id, filter }: { id: string; filter: PluginPredicate }) => {
130
+ const manager = usePluginManager();
131
+ const { entries } = useRegistryPlugins();
132
+ const extraTagsById = useAutoTags(entries);
133
+ const filtered = useMemo(() => manager.getPlugins().filter(filter), [manager, filter]);
134
+
135
+ return <RegistryArticle id={id} plugins={filtered} extraTagsById={extraTagsById} />;
136
+ };
@@ -0,0 +1,34 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { Capability, PLUGIN_DEV_SERVER_PORT } from '@dxos/app-framework';
8
+ import { AppCapabilities } from '@dxos/app-toolkit';
9
+ import { createKvsStore } from '@dxos/effect';
10
+
11
+ import { meta } from '#meta';
12
+
13
+ import { RegistryCapabilities, RegistrySettingsSchema } from '../types';
14
+
15
+ const DEFAULT_DEV_PLUGIN_URL = `http://localhost:${PLUGIN_DEV_SERVER_PORT}/manifest.json`;
16
+
17
+ export default Capability.makeModule(() =>
18
+ Effect.sync(() => {
19
+ const settingsAtom = createKvsStore({
20
+ key: meta.id,
21
+ schema: RegistrySettingsSchema,
22
+ defaultValue: () => ({ devPluginUrl: DEFAULT_DEV_PLUGIN_URL }),
23
+ });
24
+
25
+ return [
26
+ Capability.contributes(RegistryCapabilities.Settings, settingsAtom),
27
+ Capability.contributes(AppCapabilities.Settings, {
28
+ prefix: meta.id,
29
+ schema: RegistrySettingsSchema,
30
+ atom: settingsAtom,
31
+ }),
32
+ ];
33
+ }),
34
+ );
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from '../storage';
@@ -0,0 +1,47 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Args from '@effect/cli/Args';
6
+ import * as Command from '@effect/cli/Command';
7
+ import * as Console from 'effect/Console';
8
+ import * as Effect from 'effect/Effect';
9
+
10
+ import { Plugin } from '@dxos/app-framework';
11
+ import { CommandConfig } from '@dxos/cli-util';
12
+ import { invariant } from '@dxos/invariant';
13
+
14
+ import { saveEnabledPlugins } from '../storage';
15
+
16
+ export const handler = Effect.fn(function* ({ id }: { id: string }) {
17
+ const { json, profile } = yield* CommandConfig;
18
+ const manager = yield* Plugin.Service;
19
+
20
+ const plugins = manager.getPlugins();
21
+ const plugin = plugins.find((p: Plugin.Plugin) => p.meta.id === id);
22
+ invariant(plugin, `Plugin not found: ${id}`);
23
+
24
+ const core = manager.getCore();
25
+ invariant(!core.includes(id), `Cannot disable core plugin: ${id}`);
26
+
27
+ const disabled = yield* manager.disable(id);
28
+ invariant(disabled, `Failed to disable plugin: ${id}`);
29
+
30
+ // Save enabled plugins to storage
31
+ const enabledPlugins = manager.getEnabled();
32
+ yield* saveEnabledPlugins({ profile, enabled: [...enabledPlugins] });
33
+
34
+ if (json) {
35
+ yield* Console.log(JSON.stringify({ id, enabled: false }, null, 2));
36
+ } else {
37
+ yield* Console.log(`Plugin "${plugin.meta.name ?? id}" disabled.`);
38
+ }
39
+ });
40
+
41
+ export const disable = Command.make(
42
+ 'disable',
43
+ {
44
+ id: Args.text({ name: 'id' }).pipe(Args.withDescription('The ID of the plugin to disable.')),
45
+ },
46
+ handler,
47
+ ).pipe(Command.withDescription('Disable a plugin.'));
@@ -0,0 +1,44 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Args from '@effect/cli/Args';
6
+ import * as Command from '@effect/cli/Command';
7
+ import * as Console from 'effect/Console';
8
+ import * as Effect from 'effect/Effect';
9
+
10
+ import { Plugin } from '@dxos/app-framework';
11
+ import { CommandConfig } from '@dxos/cli-util';
12
+ import { invariant } from '@dxos/invariant';
13
+
14
+ import { saveEnabledPlugins } from '../storage';
15
+
16
+ export const handler = Effect.fn(function* ({ id }: { id: string }) {
17
+ const { json, profile } = yield* CommandConfig;
18
+ const manager = yield* Plugin.Service;
19
+
20
+ const plugins = manager.getPlugins();
21
+ const plugin = plugins.find((p: Plugin.Plugin) => p.meta.id === id);
22
+ invariant(plugin, `Plugin not found: ${id}`);
23
+
24
+ const enabled = yield* manager.enable(id);
25
+ invariant(enabled, `Failed to enable plugin: ${id}`);
26
+
27
+ // Save enabled plugins to storage
28
+ const enabledPlugins = manager.getEnabled();
29
+ yield* saveEnabledPlugins({ profile, enabled: [...enabledPlugins] });
30
+
31
+ if (json) {
32
+ yield* Console.log(JSON.stringify({ id, enabled: true }, null, 2));
33
+ } else {
34
+ yield* Console.log(`Plugin "${plugin.meta.name ?? id}" enabled.`);
35
+ }
36
+ });
37
+
38
+ export const enable = Command.make(
39
+ 'enable',
40
+ {
41
+ id: Args.text({ name: 'id' }).pipe(Args.withDescription('The ID of the plugin to enable.')),
42
+ },
43
+ handler,
44
+ ).pipe(Command.withDescription('Enable a plugin.'));
@@ -0,0 +1,14 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Command from '@effect/cli/Command';
6
+
7
+ import { disable } from './disable';
8
+ import { enable } from './enable';
9
+ import { list } from './list';
10
+
11
+ export const plugin: Command.Command<any, any, any, any> = Command.make('plugin').pipe(
12
+ Command.withDescription('Manage plugins.'),
13
+ Command.withSubcommands([enable, disable, list]),
14
+ );
@@ -0,0 +1,41 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Command from '@effect/cli/Command';
6
+ import * as Console from 'effect/Console';
7
+ import * as Effect from 'effect/Effect';
8
+
9
+ import { Plugin } from '@dxos/app-framework';
10
+ import { CommandConfig, printList } from '@dxos/cli-util';
11
+
12
+ import { type FormattedPlugin, printPlugin } from './util';
13
+
14
+ export const handler = Effect.fn(function* () {
15
+ const { json } = yield* CommandConfig;
16
+ const manager = yield* Plugin.Service;
17
+
18
+ const plugins = manager.getPlugins();
19
+ const enabled = manager.getEnabled();
20
+ const core = manager.getCore();
21
+
22
+ const formattedPlugins: FormattedPlugin[] = plugins.map((plugin: Plugin.Plugin) => {
23
+ const isEnabled = enabled.includes(plugin.meta.id);
24
+ const isCore = core.includes(plugin.meta.id);
25
+ return {
26
+ id: plugin.meta.id,
27
+ name: plugin.meta.name ?? plugin.meta.id,
28
+ enabled: isEnabled,
29
+ core: isCore,
30
+ };
31
+ });
32
+
33
+ if (json) {
34
+ yield* Console.log(JSON.stringify(formattedPlugins, null, 2));
35
+ } else {
36
+ const formatted = formattedPlugins.map(printPlugin);
37
+ yield* Console.log(printList(formatted));
38
+ }
39
+ });
40
+
41
+ export const list = Command.make('list', {}, handler).pipe(Command.withDescription('List all available plugins.'));
@@ -0,0 +1,24 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { FormBuilder } from '@dxos/cli-util';
6
+
7
+ export type FormattedPlugin = {
8
+ id: string;
9
+ name: string;
10
+ enabled: boolean;
11
+ core: boolean;
12
+ };
13
+
14
+ /**
15
+ * Pretty prints a plugin with ANSI colors.
16
+ */
17
+ export const printPlugin = (plugin: FormattedPlugin) => {
18
+ const status = plugin.core ? 'core' : plugin.enabled ? 'enabled' : 'disabled';
19
+ return FormBuilder.make({ title: plugin.name }).pipe(
20
+ FormBuilder.set('id', plugin.id),
21
+ FormBuilder.set('status', status),
22
+ FormBuilder.build,
23
+ );
24
+ };
@@ -0,0 +1,44 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+
7
+ import { Plugin } from '@dxos/app-framework';
8
+ import { random } from '@dxos/random';
9
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
10
+
11
+ import { translations } from '#translations';
12
+
13
+ import { PluginDetail } from './PluginDetail';
14
+
15
+ const meta = {
16
+ title: 'plugins/plugin-registry/components/PluginDetail',
17
+ component: PluginDetail,
18
+ decorators: [withTheme(), withLayout({ layout: 'column' })],
19
+ parameters: {
20
+ layout: 'fullscreen',
21
+ translations,
22
+ },
23
+ } satisfies Meta<typeof PluginDetail>;
24
+
25
+ export default meta;
26
+
27
+ type Story = StoryObj<typeof meta>;
28
+
29
+ export const Default: Story = {
30
+ args: {
31
+ plugin: Plugin.define({
32
+ id: 'com.example.plugin.test',
33
+ name: 'Test Plugin',
34
+ description: random.lorem.paragraphs(2),
35
+ icon: 'ph--bug--regular',
36
+ iconHue: 'sky',
37
+ homePage: 'https://example.com',
38
+ source: 'https://github.com/example/test-plugin',
39
+ screenshots: [
40
+ 'https://media.gcflearnfree.org/content/55e073de7dd48174331f51b3_01_17_2014/getting_started_interactive2.png',
41
+ ],
42
+ }).pipe(Plugin.make)(),
43
+ },
44
+ };