@abraca/nuxt 2.15.0 → 2.16.0

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 (26) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/aware/AMedia.d.vue.ts +1 -1
  3. package/dist/runtime/components/aware/AMedia.vue.d.ts +1 -1
  4. package/dist/runtime/components/registry/APluginBrowser.vue +18 -2
  5. package/dist/runtime/components/settings/APluginInstallDialog.d.vue.ts +39 -0
  6. package/dist/runtime/components/settings/APluginInstallDialog.vue +254 -0
  7. package/dist/runtime/components/settings/APluginInstallDialog.vue.d.ts +39 -0
  8. package/dist/runtime/components/settings/APluginsTabInstalled.d.vue.ts +7 -0
  9. package/dist/runtime/components/settings/APluginsTabInstalled.vue +413 -0
  10. package/dist/runtime/components/settings/APluginsTabInstalled.vue.d.ts +7 -0
  11. package/dist/runtime/components/settings/APluginsTabPending.d.vue.ts +24 -0
  12. package/dist/runtime/components/settings/APluginsTabPending.vue +248 -0
  13. package/dist/runtime/components/settings/APluginsTabPending.vue.d.ts +24 -0
  14. package/dist/runtime/components/settings/ASettingsPluginsPanel.d.vue.ts +14 -1
  15. package/dist/runtime/components/settings/ASettingsPluginsPanel.vue +34 -80
  16. package/dist/runtime/components/settings/ASettingsPluginsPanel.vue.d.ts +14 -1
  17. package/dist/runtime/composables/useDeclinedSpacePlugins.d.ts +7 -0
  18. package/dist/runtime/composables/useDeclinedSpacePlugins.js +24 -0
  19. package/dist/runtime/composables/useLoadTimePending.d.ts +29 -0
  20. package/dist/runtime/composables/useLoadTimePending.js +37 -0
  21. package/dist/runtime/composables/usePluginCatalog.d.ts +5 -1
  22. package/dist/runtime/composables/usePluginCatalog.js +34 -0
  23. package/dist/runtime/composables/useUploadedPluginStore.d.ts +43 -0
  24. package/dist/runtime/composables/useUploadedPluginStore.js +66 -0
  25. package/dist/runtime/plugin-abracadabra.client.js +48 -1
  26. package/package.json +1 -1
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=4.0.0"
6
6
  },
7
- "version": "2.15.0",
7
+ "version": "2.16.0",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
@@ -12,8 +12,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
12
12
  awareness: boolean;
13
13
  tag: "video" | "audio";
14
14
  live: boolean;
15
- total: boolean;
16
15
  controls: boolean;
16
+ total: boolean;
17
17
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
18
18
  declare const _default: typeof __VLS_export;
19
19
  export default _default;
@@ -12,8 +12,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
12
12
  awareness: boolean;
13
13
  tag: "video" | "audio";
14
14
  live: boolean;
15
- total: boolean;
16
15
  controls: boolean;
16
+ total: boolean;
17
17
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
18
18
  declare const _default: typeof __VLS_export;
19
19
  export default _default;
@@ -124,11 +124,27 @@ watch(resolvedServerUrl, refreshPolicy);
124
124
  <UButton size="sm" variant="soft" label="Retry" @click="refresh" />
125
125
  </div>
126
126
 
127
+ <div
128
+ v-else-if="registry.plugins.value.length === 0 && (search || category)"
129
+ class="flex flex-1 flex-col items-center justify-center gap-2 text-center"
130
+ >
131
+ <UIcon name="i-lucide-search-x" class="size-8 text-(--ui-text-dimmed)" />
132
+ <p class="text-sm text-(--ui-text-muted)">
133
+ No plugins match your search.
134
+ </p>
135
+ </div>
136
+
127
137
  <div
128
138
  v-else-if="registry.plugins.value.length === 0"
129
- class="flex flex-1 items-center justify-center text-sm text-muted"
139
+ class="flex flex-1 flex-col items-center justify-center gap-2 text-center"
130
140
  >
131
- No plugins match.
141
+ <UIcon name="i-lucide-package-open" class="size-8 text-(--ui-text-dimmed)" />
142
+ <p class="text-sm text-(--ui-text-muted)">
143
+ The plugin registry is empty.
144
+ </p>
145
+ <p class="text-xs text-(--ui-text-dimmed) max-w-sm">
146
+ No plugins have been published yet. Check back later, or install a plugin by URL from the Installed tab.
147
+ </p>
132
148
  </div>
133
149
 
134
150
  <div
@@ -0,0 +1,39 @@
1
+ import type { PluginManifest, PluginCapability } from '@abraca/plugin';
2
+ export interface TrustAcknowledgement {
3
+ trustAcknowledgedAt: number;
4
+ acknowledgedVersion: string;
5
+ acknowledgedCapabilities: PluginCapability[];
6
+ }
7
+ type __VLS_Props = {
8
+ open?: boolean;
9
+ /** Drives copy + icon. */
10
+ mode: 'url' | 'upload' | 'space';
11
+ /** Resolved plugin manifest the user is about to authorise. */
12
+ manifest: PluginManifest;
13
+ /** URL (url/space modes) or filename (upload mode) — shown verbatim. */
14
+ source: string;
15
+ /**
16
+ * Capabilities the user had previously acknowledged for this entry.
17
+ * New ones (in `manifest.capabilities.required` but not in this list)
18
+ * are highlighted as "new since last install". Empty/undefined on a
19
+ * fresh install.
20
+ */
21
+ previouslyAcknowledged?: readonly PluginCapability[];
22
+ /** For mode='space', the space id; surfaces in copy + decline payload. */
23
+ spaceId?: string;
24
+ /** Show "Don't install for this space" button. Only meaningful for mode='space'. */
25
+ declinable?: boolean;
26
+ };
27
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
28
+ cancel: () => any;
29
+ "update:open": (args_0: boolean) => any;
30
+ confirm: (payload: TrustAcknowledgement) => any;
31
+ decline: () => any;
32
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
33
+ onCancel?: (() => any) | undefined;
34
+ "onUpdate:open"?: ((args_0: boolean) => any) | undefined;
35
+ onConfirm?: ((payload: TrustAcknowledgement) => any) | undefined;
36
+ onDecline?: (() => any) | undefined;
37
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
38
+ declare const _default: typeof __VLS_export;
39
+ export default _default;
@@ -0,0 +1,254 @@
1
+ <script setup>
2
+ import { ref, computed, watch } from "vue";
3
+ const props = defineProps({
4
+ open: { type: Boolean, required: false },
5
+ mode: { type: String, required: true },
6
+ manifest: { type: Object, required: true },
7
+ source: { type: String, required: true },
8
+ previouslyAcknowledged: { type: Array, required: false },
9
+ spaceId: { type: String, required: false },
10
+ declinable: { type: Boolean, required: false }
11
+ });
12
+ const emit = defineEmits(["update:open", "confirm", "cancel", "decline"]);
13
+ const open = computed({
14
+ get: () => !!props.open,
15
+ set: (v) => emit("update:open", v)
16
+ });
17
+ const acknowledged = ref(false);
18
+ watch(() => props.open, (v) => {
19
+ if (v) acknowledged.value = false;
20
+ });
21
+ const requiredCaps = computed(
22
+ () => props.manifest.capabilities.required ?? []
23
+ );
24
+ const optionalCaps = computed(
25
+ () => props.manifest.capabilities.optional ?? []
26
+ );
27
+ const newCaps = computed(() => {
28
+ const prev = new Set(props.previouslyAcknowledged ?? []);
29
+ return new Set(requiredCaps.value.filter((c) => !prev.has(c)));
30
+ });
31
+ const hasGrowth = computed(() => newCaps.value.size > 0);
32
+ const isReprompt = computed(() => (props.previouslyAcknowledged?.length ?? 0) > 0);
33
+ const headerIcon = computed(() => {
34
+ if (props.mode === "upload") return "i-lucide-upload";
35
+ if (props.mode === "space") return "i-lucide-globe";
36
+ return "i-lucide-link-2";
37
+ });
38
+ const headerTitle = computed(() => {
39
+ if (isReprompt.value && hasGrowth.value) return "Plugin requests new permissions";
40
+ if (isReprompt.value) return "Plugin updated \u2014 confirm again";
41
+ if (props.mode === "upload") return "Activate uploaded plugin";
42
+ if (props.mode === "space") return "Activate space plugin";
43
+ return "Activate plugin from URL";
44
+ });
45
+ const sourceLabel = computed(() => {
46
+ if (props.mode === "upload") return "Uploaded file";
47
+ if (props.mode === "space") return "Declared by space";
48
+ return "Source URL";
49
+ });
50
+ const author = computed(() => {
51
+ const a = props.manifest.author;
52
+ if (!a) return null;
53
+ return a.name || a.github || a.email || null;
54
+ });
55
+ function describeCap(cap) {
56
+ if (cap.startsWith("network:")) return `Network: ${cap.slice("network:".length)}`;
57
+ if (cap === "network") return "Network (any host)";
58
+ if (cap.startsWith("page-type:")) return `Register page type "${cap.slice("page-type:".length)}"`;
59
+ const map = {
60
+ "fs:read": "Read local files",
61
+ "fs:write": "Write local files",
62
+ "clipboard:read": "Read clipboard",
63
+ "clipboard:write": "Write clipboard",
64
+ "doc-read": "Read documents",
65
+ "doc-write": "Write documents",
66
+ "awareness": "Share presence",
67
+ "server-runner": "Run server-side code",
68
+ "command-palette": "Add command-palette items",
69
+ "toolbar": "Add editor toolbar items",
70
+ "bubble-menu": "Add bubble-menu items",
71
+ "slash-command": "Add slash-command items",
72
+ "drag-handle": "Add drag-handle items",
73
+ "chat:send": "Send chat messages",
74
+ "chat:read": "Read chat history",
75
+ "notifications:show": "Show notifications"
76
+ };
77
+ return map[cap] ?? cap;
78
+ }
79
+ function onConfirm() {
80
+ if (!acknowledged.value) return;
81
+ emit("confirm", {
82
+ trustAcknowledgedAt: Date.now(),
83
+ acknowledgedVersion: props.manifest.version,
84
+ acknowledgedCapabilities: [...requiredCaps.value]
85
+ });
86
+ open.value = false;
87
+ }
88
+ function onCancel() {
89
+ emit("cancel");
90
+ open.value = false;
91
+ }
92
+ function onDecline() {
93
+ emit("decline");
94
+ open.value = false;
95
+ }
96
+ </script>
97
+
98
+ <template>
99
+ <UModal
100
+ v-model:open="open"
101
+ :title="headerTitle"
102
+ :ui="{ content: 'sm:max-w-lg' }"
103
+ >
104
+ <template #body>
105
+ <div class="flex flex-col gap-4">
106
+ <!-- Header glyph + mode label -->
107
+ <div class="flex items-center gap-3">
108
+ <div
109
+ class="size-10 rounded-lg flex items-center justify-center bg-(--ui-warning)/10 text-(--ui-warning)"
110
+ >
111
+ <UIcon
112
+ :name="headerIcon"
113
+ class="size-5"
114
+ />
115
+ </div>
116
+ <div class="flex-1 min-w-0">
117
+ <div class="text-sm font-semibold text-(--ui-text-highlighted) truncate">
118
+ {{ manifest.name || manifest.id }}
119
+ <span class="text-(--ui-text-muted) font-normal ml-1">v{{ manifest.version }}</span>
120
+ </div>
121
+ <div class="text-xs text-(--ui-text-muted) truncate">
122
+ {{ manifest.id }}<template v-if="author"> · {{ author }}</template>
123
+ </div>
124
+ </div>
125
+ </div>
126
+
127
+ <!-- BIG untrusted-code warning -->
128
+ <UAlert
129
+ color="warning"
130
+ variant="subtle"
131
+ :icon="hasGrowth ? 'i-lucide-shield-alert' : 'i-lucide-alert-triangle'"
132
+ :title="hasGrowth ? 'New permissions requested' : 'This plugin has not been reviewed'"
133
+ :description="hasGrowth ? 'This update requests permissions you did not previously grant. Review the new permissions below before activating.' : 'This plugin is not from the official registry. Activating it will run third-party code in your app that can read and write your documents, share presence, and access the network.'"
134
+ />
135
+
136
+ <!-- Source -->
137
+ <div class="flex flex-col gap-1">
138
+ <label class="text-xs font-medium text-(--ui-text-muted) px-1">{{ sourceLabel }}</label>
139
+ <div class="rounded-md border border-(--ui-border) bg-(--ui-bg-elevated) px-3 py-2 text-xs font-mono text-(--ui-text) break-all">
140
+ {{ source }}
141
+ </div>
142
+ <div
143
+ v-if="mode === 'space' && spaceId"
144
+ class="text-xs text-(--ui-text-muted) px-1"
145
+ >
146
+ Declared in space <span class="font-mono">{{ spaceId }}</span>.
147
+ </div>
148
+ </div>
149
+
150
+ <!-- Description -->
151
+ <div
152
+ v-if="manifest.description"
153
+ class="text-sm text-(--ui-text)"
154
+ >
155
+ {{ manifest.description }}
156
+ </div>
157
+
158
+ <!-- Required capabilities -->
159
+ <div
160
+ v-if="requiredCaps.length"
161
+ class="flex flex-col gap-1"
162
+ >
163
+ <label class="text-xs font-medium text-(--ui-text-muted) px-1">
164
+ Required permissions ({{ requiredCaps.length }})
165
+ </label>
166
+ <div class="rounded-md border border-(--ui-border) overflow-hidden divide-y divide-(--ui-border)">
167
+ <div
168
+ v-for="cap in requiredCaps"
169
+ :key="cap"
170
+ class="flex items-center gap-2.5 px-3 py-2 text-sm bg-(--ui-bg)"
171
+ :class="newCaps.has(cap) ? 'bg-(--ui-warning)/5' : ''"
172
+ >
173
+ <UIcon
174
+ name="i-lucide-key-round"
175
+ class="size-4 text-(--ui-text-muted) shrink-0"
176
+ />
177
+ <span class="flex-1 text-(--ui-text)">{{ describeCap(cap) }}</span>
178
+ <UBadge
179
+ v-if="newCaps.has(cap)"
180
+ color="warning"
181
+ variant="soft"
182
+ size="xs"
183
+ label="New"
184
+ />
185
+ </div>
186
+ </div>
187
+ </div>
188
+
189
+ <!-- Optional capabilities (compact) -->
190
+ <div
191
+ v-if="optionalCaps.length"
192
+ class="flex flex-wrap gap-1.5 px-1"
193
+ >
194
+ <span class="text-xs text-(--ui-text-muted) mr-1">Optional:</span>
195
+ <UBadge
196
+ v-for="cap in optionalCaps"
197
+ :key="cap"
198
+ color="neutral"
199
+ variant="subtle"
200
+ size="xs"
201
+ :label="describeCap(cap)"
202
+ />
203
+ </div>
204
+
205
+ <!-- Acknowledgement -->
206
+ <label class="flex items-start gap-2.5 rounded-md border border-(--ui-border) bg-(--ui-bg-elevated) p-3 cursor-pointer">
207
+ <UCheckbox
208
+ v-model="acknowledged"
209
+ :ui="{ wrapper: 'mt-0.5' }"
210
+ />
211
+ <span class="text-sm text-(--ui-text) leading-snug">
212
+ I understand this plugin runs unreviewed third-party code in my app and I want to activate it.
213
+ </span>
214
+ </label>
215
+ </div>
216
+ </template>
217
+
218
+ <template #footer>
219
+ <div class="flex items-center justify-between gap-2 w-full">
220
+ <UButton
221
+ v-if="mode === 'space' && declinable"
222
+ variant="ghost"
223
+ color="neutral"
224
+ size="sm"
225
+ icon="i-lucide-x-circle"
226
+ label="Don't install for this space"
227
+ @click="onDecline"
228
+ />
229
+ <div
230
+ v-else
231
+ aria-hidden="true"
232
+ />
233
+ <div class="flex items-center gap-2">
234
+ <UButton
235
+ variant="ghost"
236
+ color="neutral"
237
+ size="sm"
238
+ label="Cancel"
239
+ @click="onCancel"
240
+ />
241
+ <UButton
242
+ variant="solid"
243
+ color="warning"
244
+ size="sm"
245
+ icon="i-lucide-shield-check"
246
+ label="Activate"
247
+ :disabled="!acknowledged"
248
+ @click="onConfirm"
249
+ />
250
+ </div>
251
+ </div>
252
+ </template>
253
+ </UModal>
254
+ </template>
@@ -0,0 +1,39 @@
1
+ import type { PluginManifest, PluginCapability } from '@abraca/plugin';
2
+ export interface TrustAcknowledgement {
3
+ trustAcknowledgedAt: number;
4
+ acknowledgedVersion: string;
5
+ acknowledgedCapabilities: PluginCapability[];
6
+ }
7
+ type __VLS_Props = {
8
+ open?: boolean;
9
+ /** Drives copy + icon. */
10
+ mode: 'url' | 'upload' | 'space';
11
+ /** Resolved plugin manifest the user is about to authorise. */
12
+ manifest: PluginManifest;
13
+ /** URL (url/space modes) or filename (upload mode) — shown verbatim. */
14
+ source: string;
15
+ /**
16
+ * Capabilities the user had previously acknowledged for this entry.
17
+ * New ones (in `manifest.capabilities.required` but not in this list)
18
+ * are highlighted as "new since last install". Empty/undefined on a
19
+ * fresh install.
20
+ */
21
+ previouslyAcknowledged?: readonly PluginCapability[];
22
+ /** For mode='space', the space id; surfaces in copy + decline payload. */
23
+ spaceId?: string;
24
+ /** Show "Don't install for this space" button. Only meaningful for mode='space'. */
25
+ declinable?: boolean;
26
+ };
27
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
28
+ cancel: () => any;
29
+ "update:open": (args_0: boolean) => any;
30
+ confirm: (payload: TrustAcknowledgement) => any;
31
+ decline: () => any;
32
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
33
+ onCancel?: (() => any) | undefined;
34
+ "onUpdate:open"?: ((args_0: boolean) => any) | undefined;
35
+ onConfirm?: ((payload: TrustAcknowledgement) => any) | undefined;
36
+ onDecline?: (() => any) | undefined;
37
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
38
+ declare const _default: typeof __VLS_export;
39
+ export default _default;
@@ -0,0 +1,7 @@
1
+ import type { BuiltinPluginEntry } from './ASettingsPluginsPanel.vue.js';
2
+ type __VLS_Props = {
3
+ builtins?: readonly BuiltinPluginEntry[];
4
+ };
5
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
+ declare const _default: typeof __VLS_export;
7
+ export default _default;