@ampless/admin 1.0.0-alpha.41 → 1.0.0-alpha.43

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 (35) hide show
  1. package/dist/api/index.d.ts +1 -1
  2. package/dist/{chunk-YNQR3AFO.js → chunk-3EOEDH5Q.js} +2 -2
  3. package/dist/{chunk-BF6XV6TP.js → chunk-462KM4PP.js} +1 -1
  4. package/dist/{chunk-MDBBDBE5.js → chunk-4J2WT5DT.js} +2 -2
  5. package/dist/{chunk-3QXCPMPD.js → chunk-4VX6Z3OI.js} +1 -1
  6. package/dist/{chunk-GJSMMQVO.js → chunk-AZR2PYIA.js} +2 -2
  7. package/dist/{chunk-TGEPARLA.js → chunk-EX3XWPBC.js} +1 -1
  8. package/dist/{chunk-MHTKDXSP.js → chunk-HLORQWBX.js} +4 -2
  9. package/dist/{chunk-Q3FMKPAQ.js → chunk-IYUEDZTX.js} +2 -2
  10. package/dist/{chunk-OMWVBK2C.js → chunk-QUFDISLW.js} +1 -1
  11. package/dist/chunk-T6IB6W3N.js +380 -0
  12. package/dist/{chunk-5DWBQ73J.js → chunk-U2VSCLWY.js} +26 -0
  13. package/dist/{chunk-ZR35TWHV.js → chunk-VJ3FOXAK.js} +1 -1
  14. package/dist/{chunk-VZJU74K3.js → chunk-X47FJZR2.js} +1 -1
  15. package/dist/{chunk-UTQRFC6V.js → chunk-X4SCR6A4.js} +2 -2
  16. package/dist/{chunk-DNMENH6C.js → chunk-YI727DVE.js} +1 -1
  17. package/dist/components/admin-dashboard.js +3 -3
  18. package/dist/components/edit-post-view.js +5 -5
  19. package/dist/components/index.d.ts +1 -1
  20. package/dist/components/index.js +6 -6
  21. package/dist/components/login-view.js +3 -3
  22. package/dist/components/mcp-tokens-view.js +3 -3
  23. package/dist/components/media-view.js +5 -5
  24. package/dist/components/new-post-view.js +5 -5
  25. package/dist/components/plugin-settings-form.d.ts +17 -0
  26. package/dist/components/plugin-settings-form.js +10 -0
  27. package/dist/components/posts-list-view.js +3 -3
  28. package/dist/components/users-list-view.js +3 -3
  29. package/dist/{i18n-sYi0kbnC.d.ts → i18n-C2ev0Y0f.d.ts} +40 -0
  30. package/dist/index.d.ts +13 -2
  31. package/dist/index.js +9 -1
  32. package/dist/metafile-esm.json +1 -1
  33. package/dist/pages/index.d.ts +17 -2
  34. package/dist/pages/index.js +70 -14
  35. package/package.json +4 -4
@@ -2,7 +2,7 @@ import { NextRequest } from 'next/server';
2
2
  import { Admin } from '../index.js';
3
3
  import 'ampless';
4
4
  import '@ampless/runtime';
5
- import '../i18n-sYi0kbnC.js';
5
+ import '../i18n-C2ev0Y0f.js';
6
6
  import '@aws-amplify/adapter-nextjs';
7
7
 
8
8
  /**
@@ -3,13 +3,13 @@ import {
3
3
  ImageUploadDialog,
4
4
  createMediaRow,
5
5
  getMediaProcessingDefaults
6
- } from "./chunk-OMWVBK2C.js";
6
+ } from "./chunk-QUFDISLW.js";
7
7
  import {
8
8
  publicMediaUrl
9
9
  } from "./chunk-2ITWLRYF.js";
10
10
  import {
11
11
  useT
12
- } from "./chunk-TGEPARLA.js";
12
+ } from "./chunk-EX3XWPBC.js";
13
13
 
14
14
  // src/components/media-uploader.tsx
15
15
  import { useState, useEffect, useCallback, useRef } from "react";
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import {
3
3
  useT
4
- } from "./chunk-TGEPARLA.js";
4
+ } from "./chunk-EX3XWPBC.js";
5
5
 
6
6
  // src/components/posts-list-view.tsx
7
7
  import { useEffect, useState } from "react";
@@ -1,10 +1,10 @@
1
1
  'use client';
2
2
  import {
3
3
  PostForm
4
- } from "./chunk-UTQRFC6V.js";
4
+ } from "./chunk-X4SCR6A4.js";
5
5
  import {
6
6
  useT
7
- } from "./chunk-TGEPARLA.js";
7
+ } from "./chunk-EX3XWPBC.js";
8
8
 
9
9
  // src/components/new-post-view.tsx
10
10
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import {
3
3
  useT
4
- } from "./chunk-TGEPARLA.js";
4
+ } from "./chunk-EX3XWPBC.js";
5
5
 
6
6
  // src/components/admin-dashboard.tsx
7
7
  import { useEffect, useState } from "react";
@@ -1,10 +1,10 @@
1
1
  'use client';
2
2
  import {
3
3
  PostForm
4
- } from "./chunk-UTQRFC6V.js";
4
+ } from "./chunk-X4SCR6A4.js";
5
5
  import {
6
6
  useT
7
- } from "./chunk-TGEPARLA.js";
7
+ } from "./chunk-EX3XWPBC.js";
8
8
 
9
9
  // src/components/edit-post-view.tsx
10
10
  import { useEffect, useState, use } from "react";
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import {
3
3
  translate
4
- } from "./chunk-5DWBQ73J.js";
4
+ } from "./chunk-U2VSCLWY.js";
5
5
 
6
6
  // src/components/i18n-provider.tsx
7
7
  import { createContext, useContext, useMemo } from "react";
@@ -7,14 +7,14 @@ import {
7
7
  } from "./chunk-C7G5AQ3L.js";
8
8
  import {
9
9
  setAdminCmsConfigClient
10
- } from "./chunk-OMWVBK2C.js";
10
+ } from "./chunk-QUFDISLW.js";
11
11
  import {
12
12
  setAdminMediaContext
13
13
  } from "./chunk-2ITWLRYF.js";
14
14
  import {
15
15
  useLocale,
16
16
  useT
17
- } from "./chunk-TGEPARLA.js";
17
+ } from "./chunk-EX3XWPBC.js";
18
18
 
19
19
  // src/lib/amplify-client.ts
20
20
  import { Amplify } from "aws-amplify";
@@ -258,6 +258,7 @@ import {
258
258
  Globe,
259
259
  Users,
260
260
  Key,
261
+ Puzzle,
261
262
  LogOut,
262
263
  ExternalLink,
263
264
  Menu,
@@ -272,6 +273,7 @@ var navItems = [
272
273
  // ampless runs one site per deployment, so this links directly to
273
274
  // the single site's settings page instead of a list landing.
274
275
  { href: "/admin/sites/default", key: "sidebar.sites", icon: Globe },
276
+ { href: "/admin/plugins", key: "sidebar.plugins", icon: Puzzle },
275
277
  { href: "/admin/users", key: "sidebar.users", icon: Users, adminOnly: true },
276
278
  { href: "/admin/mcp-tokens", key: "sidebar.mcpTokens", icon: Key, adminOnly: true }
277
279
  ];
@@ -1,10 +1,10 @@
1
1
  'use client';
2
2
  import {
3
3
  MediaUploader
4
- } from "./chunk-YNQR3AFO.js";
4
+ } from "./chunk-3EOEDH5Q.js";
5
5
  import {
6
6
  useT
7
- } from "./chunk-TGEPARLA.js";
7
+ } from "./chunk-EX3XWPBC.js";
8
8
 
9
9
  // src/components/media-view.tsx
10
10
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-2ITWLRYF.js";
5
5
  import {
6
6
  useT
7
- } from "./chunk-TGEPARLA.js";
7
+ } from "./chunk-EX3XWPBC.js";
8
8
 
9
9
  // src/lib/upload.ts
10
10
  import { uploadData } from "aws-amplify/storage";
@@ -0,0 +1,380 @@
1
+ 'use client';
2
+ import {
3
+ invalidateSiteSettingsCache
4
+ } from "./chunk-D72XF3Q3.js";
5
+ import {
6
+ useLocale,
7
+ useT
8
+ } from "./chunk-EX3XWPBC.js";
9
+
10
+ // src/components/plugin-settings-form.tsx
11
+ import { useState } from "react";
12
+ import {
13
+ resolveLocalized
14
+ } from "ampless";
15
+ import { Button, Input, Label, Textarea } from "@ampless/runtime/ui";
16
+
17
+ // src/lib/plugin-settings.ts
18
+ import {
19
+ getKvStore,
20
+ SITE_CONFIG_PK,
21
+ isValidPluginKey,
22
+ validatePluginSettingValue
23
+ } from "ampless";
24
+ function pluginSettingKey(instanceId, fieldKey) {
25
+ return `plugins.${instanceId}.${fieldKey}`;
26
+ }
27
+ async function setPluginPublicSetting(instanceId, field, rawValue) {
28
+ if (!isValidPluginKey(instanceId)) {
29
+ throw new Error(`Invalid plugin instanceId: "${instanceId}"`);
30
+ }
31
+ if (!isValidPluginKey(field.key)) {
32
+ throw new Error(`Invalid plugin field key: "${field.key}"`);
33
+ }
34
+ const validated = validatePluginSettingValue(field, rawValue);
35
+ if (validated === null) {
36
+ throw new Error(`Invalid value for plugin field "${field.key}"`);
37
+ }
38
+ const store = getKvStore();
39
+ await store.put(SITE_CONFIG_PK, pluginSettingKey(instanceId, field.key), validated);
40
+ }
41
+ async function deletePluginPublicSetting(instanceId, field) {
42
+ if (!isValidPluginKey(instanceId)) {
43
+ throw new Error(`Invalid plugin instanceId: "${instanceId}"`);
44
+ }
45
+ if (!isValidPluginKey(field.key)) {
46
+ throw new Error(`Invalid plugin field key: "${field.key}"`);
47
+ }
48
+ const store = getKvStore();
49
+ await store.remove(SITE_CONFIG_PK, pluginSettingKey(instanceId, field.key));
50
+ }
51
+
52
+ // src/components/plugin-settings-form.tsx
53
+ import { jsx, jsxs } from "react/jsx-runtime";
54
+ var CACHE_REBUILD_DELAY_MS = 8e3;
55
+ function stringify(field, raw) {
56
+ if (raw === void 0 || raw === null) return "";
57
+ switch (field.type) {
58
+ case "boolean":
59
+ return raw === true ? "true" : raw === false ? "false" : String(raw);
60
+ case "json":
61
+ if (typeof raw === "string") return raw;
62
+ try {
63
+ return JSON.stringify(raw, null, 2);
64
+ } catch {
65
+ return "";
66
+ }
67
+ default:
68
+ return typeof raw === "string" ? raw : String(raw);
69
+ }
70
+ }
71
+ function parse(field, raw) {
72
+ switch (field.type) {
73
+ case "boolean":
74
+ if (raw === "true") return true;
75
+ if (raw === "false") return false;
76
+ return null;
77
+ case "number": {
78
+ const trimmed = raw.trim();
79
+ if (trimmed === "") return null;
80
+ const n = Number(trimmed);
81
+ return Number.isNaN(n) ? null : n;
82
+ }
83
+ case "json": {
84
+ const trimmed = raw.trim();
85
+ if (trimmed === "") return null;
86
+ try {
87
+ return JSON.parse(trimmed);
88
+ } catch {
89
+ return null;
90
+ }
91
+ }
92
+ default:
93
+ return raw;
94
+ }
95
+ }
96
+ function PluginSettingsForm({
97
+ instanceId,
98
+ displayName,
99
+ fields,
100
+ initialValues
101
+ }) {
102
+ const t = useT();
103
+ const locale = useLocale();
104
+ function defaultDisplay(field) {
105
+ return field.default !== void 0 ? stringify(field, field.default) : "";
106
+ }
107
+ const [state, setState] = useState(() => {
108
+ const values = {};
109
+ for (const field of fields) {
110
+ const has = Object.prototype.hasOwnProperty.call(initialValues, field.key);
111
+ values[field.key] = has ? stringify(field, initialValues[field.key]) : defaultDisplay(field);
112
+ }
113
+ return { values, touched: {}, invalid: {} };
114
+ });
115
+ const [storedKeys, setStoredKeys] = useState(
116
+ () => new Set(Object.keys(initialValues))
117
+ );
118
+ const [saving, setSaving] = useState(false);
119
+ const [error, setError] = useState(null);
120
+ const [info, setInfo] = useState(null);
121
+ function update(key, value) {
122
+ setState((prev) => ({
123
+ values: { ...prev.values, [key]: value },
124
+ touched: { ...prev.touched, [key]: true },
125
+ invalid: { ...prev.invalid, [key]: false }
126
+ }));
127
+ setInfo(null);
128
+ }
129
+ function scheduleCacheInvalidation() {
130
+ setTimeout(async () => {
131
+ try {
132
+ await invalidateSiteSettingsCache();
133
+ } catch (err) {
134
+ console.warn("[plugin] cache invalidation failed", err);
135
+ }
136
+ }, CACHE_REBUILD_DELAY_MS);
137
+ }
138
+ async function reset(field) {
139
+ setError(null);
140
+ setInfo(null);
141
+ try {
142
+ await deletePluginPublicSetting(instanceId, field);
143
+ setState((prev) => ({
144
+ values: { ...prev.values, [field.key]: defaultDisplay(field) },
145
+ touched: { ...prev.touched, [field.key]: false },
146
+ invalid: { ...prev.invalid, [field.key]: false }
147
+ }));
148
+ setStoredKeys((prev) => {
149
+ if (!prev.has(field.key)) return prev;
150
+ const next = new Set(prev);
151
+ next.delete(field.key);
152
+ return next;
153
+ });
154
+ setInfo(t("plugins.resetDone"));
155
+ scheduleCacheInvalidation();
156
+ } catch (err) {
157
+ console.error("[plugin] reset failed", err);
158
+ setError(err instanceof Error ? err.message : String(err));
159
+ }
160
+ }
161
+ async function save(e) {
162
+ e.preventDefault();
163
+ setSaving(true);
164
+ setError(null);
165
+ setInfo(null);
166
+ const newInvalid = {};
167
+ const writes = [];
168
+ const writtenKeys = [];
169
+ for (const field of fields) {
170
+ if (!state.touched[field.key]) continue;
171
+ const raw = state.values[field.key] ?? "";
172
+ const parsed = parse(field, raw);
173
+ if (parsed === null && raw !== "") {
174
+ newInvalid[field.key] = true;
175
+ continue;
176
+ }
177
+ if (parsed === null) continue;
178
+ writes.push(setPluginPublicSetting(instanceId, field, parsed));
179
+ writtenKeys.push(field.key);
180
+ }
181
+ if (Object.keys(newInvalid).length > 0) {
182
+ setState((prev) => ({ ...prev, invalid: newInvalid }));
183
+ setSaving(false);
184
+ setError(t("plugins.invalidValue"));
185
+ return;
186
+ }
187
+ try {
188
+ await Promise.all(writes);
189
+ setInfo(t("plugins.saved"));
190
+ setState((prev) => ({ ...prev, touched: {}, invalid: {} }));
191
+ if (writtenKeys.length > 0) {
192
+ setStoredKeys((prev) => {
193
+ const next = new Set(prev);
194
+ for (const k of writtenKeys) next.add(k);
195
+ return next;
196
+ });
197
+ }
198
+ scheduleCacheInvalidation();
199
+ } catch (err) {
200
+ console.error("[plugin] save failed", err);
201
+ setError(err instanceof Error ? err.message : String(err));
202
+ } finally {
203
+ setSaving(false);
204
+ }
205
+ }
206
+ return /* @__PURE__ */ jsxs("form", { onSubmit: save, className: "max-w-2xl space-y-5 rounded-md border p-4", children: [
207
+ /* @__PURE__ */ jsxs("div", { children: [
208
+ /* @__PURE__ */ jsx("h2", { className: "text-base font-semibold", children: displayName ? resolveLocalized(displayName, locale) : instanceId }),
209
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: instanceId })
210
+ ] }),
211
+ fields.map((field) => /* @__PURE__ */ jsx(
212
+ FieldRow,
213
+ {
214
+ field,
215
+ value: state.values[field.key] ?? "",
216
+ invalid: !!state.invalid[field.key],
217
+ onChange: (v) => update(field.key, v),
218
+ onReset: () => void reset(field),
219
+ hasStoredValue: storedKeys.has(field.key)
220
+ },
221
+ field.key
222
+ )),
223
+ info && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: info }),
224
+ error && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: error }),
225
+ /* @__PURE__ */ jsx(Button, { type: "submit", disabled: saving, children: saving ? t("plugins.saving") : t("plugins.save") })
226
+ ] });
227
+ }
228
+ function FieldRow({ field, value, invalid, onChange, onReset, hasStoredValue }) {
229
+ const t = useT();
230
+ const locale = useLocale();
231
+ const id = `plugin-${field.key}`;
232
+ const labelEl = /* @__PURE__ */ jsxs(Label, { htmlFor: id, className: invalid ? "text-destructive" : void 0, children: [
233
+ resolveLocalized(field.label, locale),
234
+ field.required && /* @__PURE__ */ jsx("span", { className: "ml-1 text-destructive", children: "*" })
235
+ ] });
236
+ const description = field.description ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: resolveLocalized(field.description, locale) }) : null;
237
+ const input = renderInput(field, id, value, invalid, onChange);
238
+ const placeholder = renderDefaultHint(field, locale);
239
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
240
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
241
+ labelEl,
242
+ hasStoredValue && /* @__PURE__ */ jsx(
243
+ "button",
244
+ {
245
+ type: "button",
246
+ className: "text-xs text-muted-foreground underline-offset-2 hover:underline",
247
+ onClick: onReset,
248
+ children: t("plugins.resetToDefault")
249
+ }
250
+ )
251
+ ] }),
252
+ description,
253
+ input,
254
+ placeholder
255
+ ] });
256
+ }
257
+ function renderDefaultHint(field, _locale) {
258
+ if (field.default === void 0) return null;
259
+ let preview;
260
+ if (typeof field.default === "string") preview = field.default;
261
+ else if (typeof field.default === "boolean" || typeof field.default === "number") {
262
+ preview = String(field.default);
263
+ } else {
264
+ try {
265
+ preview = JSON.stringify(field.default);
266
+ } catch {
267
+ return null;
268
+ }
269
+ }
270
+ if (!preview) return null;
271
+ return /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: /* @__PURE__ */ jsx("code", { children: preview }) });
272
+ }
273
+ function renderInput(field, id, value, invalid, onChange) {
274
+ switch (field.type) {
275
+ case "text":
276
+ case "url":
277
+ return /* @__PURE__ */ jsx(
278
+ Input,
279
+ {
280
+ id,
281
+ value,
282
+ maxLength: field.type === "text" ? field.maxLength : void 0,
283
+ placeholder: "placeholder" in field ? field.placeholder : void 0,
284
+ onChange: (e) => onChange(e.target.value),
285
+ "aria-invalid": invalid,
286
+ type: field.type === "url" ? "url" : "text"
287
+ }
288
+ );
289
+ case "textarea":
290
+ return /* @__PURE__ */ jsx(
291
+ Textarea,
292
+ {
293
+ id,
294
+ value,
295
+ rows: field.rows ?? 4,
296
+ maxLength: field.maxLength,
297
+ placeholder: field.placeholder,
298
+ onChange: (e) => onChange(e.target.value),
299
+ "aria-invalid": invalid
300
+ }
301
+ );
302
+ case "code":
303
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
304
+ field.language && /* @__PURE__ */ jsx("p", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: field.language }),
305
+ /* @__PURE__ */ jsx(
306
+ Textarea,
307
+ {
308
+ id,
309
+ value,
310
+ rows: field.rows ?? 8,
311
+ maxLength: field.maxLength,
312
+ placeholder: field.placeholder,
313
+ onChange: (e) => onChange(e.target.value),
314
+ "aria-invalid": invalid,
315
+ className: "font-mono text-xs"
316
+ }
317
+ )
318
+ ] });
319
+ case "boolean":
320
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
321
+ /* @__PURE__ */ jsx(
322
+ "input",
323
+ {
324
+ id,
325
+ type: "checkbox",
326
+ checked: value === "true",
327
+ onChange: (e) => onChange(e.target.checked ? "true" : "false"),
328
+ className: "h-4 w-4"
329
+ }
330
+ ),
331
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: value === "true" ? "on" : "off" })
332
+ ] });
333
+ case "number":
334
+ return /* @__PURE__ */ jsx(
335
+ Input,
336
+ {
337
+ id,
338
+ type: "number",
339
+ value,
340
+ min: field.min,
341
+ max: field.max,
342
+ step: field.step,
343
+ onChange: (e) => onChange(e.target.value),
344
+ "aria-invalid": invalid
345
+ }
346
+ );
347
+ case "select":
348
+ return /* @__PURE__ */ jsxs(
349
+ "select",
350
+ {
351
+ id,
352
+ className: "w-full rounded-md border bg-background px-2 py-1.5 text-sm",
353
+ value,
354
+ onChange: (e) => onChange(e.target.value),
355
+ "aria-invalid": invalid,
356
+ children: [
357
+ /* @__PURE__ */ jsx("option", { value: "", children: "\u2014" }),
358
+ field.options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: typeof opt.label === "string" ? opt.label : opt.value }, opt.value))
359
+ ]
360
+ }
361
+ );
362
+ case "json":
363
+ return /* @__PURE__ */ jsx(
364
+ Textarea,
365
+ {
366
+ id,
367
+ value,
368
+ rows: field.rows ?? 8,
369
+ placeholder: field.placeholder ?? "{}",
370
+ onChange: (e) => onChange(e.target.value),
371
+ "aria-invalid": invalid,
372
+ className: "font-mono text-xs"
373
+ }
374
+ );
375
+ }
376
+ }
377
+
378
+ export {
379
+ PluginSettingsForm
380
+ };
@@ -34,6 +34,7 @@ var en_default = {
34
34
  posts: "Posts",
35
35
  media: "Media",
36
36
  sites: "Sites",
37
+ plugins: "Plugins",
37
38
  users: "Users",
38
39
  mcpTokens: "MCP tokens",
39
40
  viewSite: "View site",
@@ -281,6 +282,18 @@ var en_default = {
281
282
  userExists: "An account with this email already exists."
282
283
  }
283
284
  },
285
+ plugins: {
286
+ title: "Plugin Settings",
287
+ description: "Edit admin-managed values for installed plugins. Changes propagate to the public site within ~10 seconds (S3 cache rebuild).",
288
+ noConfigurablePlugins: "No installed plugins expose configurable settings.",
289
+ save: "Save",
290
+ saving: "Saving\u2026",
291
+ saved: "Saved. Public site refreshes within ~10 seconds.",
292
+ saveError: "Save failed",
293
+ invalidValue: "One or more values are invalid \u2014 fix highlighted fields and try again.",
294
+ resetToDefault: "Reset to default",
295
+ resetDone: "Stored value cleared. Falling back to the manifest default."
296
+ },
284
297
  mcpTokens: {
285
298
  title: "MCP tokens",
286
299
  description: "Access tokens for the HTTP MCP endpoint. Use them in Claude Desktop / Cursor / any MCP-aware client to read and write this CMS over the wire.",
@@ -363,6 +376,7 @@ var ja_default = {
363
376
  posts: "\u8A18\u4E8B",
364
377
  media: "\u30E1\u30C7\u30A3\u30A2",
365
378
  sites: "\u30B5\u30A4\u30C8",
379
+ plugins: "\u30D7\u30E9\u30B0\u30A4\u30F3",
366
380
  users: "\u30E6\u30FC\u30B6\u30FC",
367
381
  mcpTokens: "MCP \u30C8\u30FC\u30AF\u30F3",
368
382
  viewSite: "\u30B5\u30A4\u30C8\u3092\u8868\u793A",
@@ -610,6 +624,18 @@ var ja_default = {
610
624
  userExists: "\u3053\u306E\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u306E\u30A2\u30AB\u30A6\u30F3\u30C8\u306F\u65E2\u306B\u5B58\u5728\u3057\u307E\u3059\u3002"
611
625
  }
612
626
  },
627
+ plugins: {
628
+ title: "\u30D7\u30E9\u30B0\u30A4\u30F3\u8A2D\u5B9A",
629
+ description: "\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u6E08\u307F\u30D7\u30E9\u30B0\u30A4\u30F3\u306E\u7BA1\u7406\u5024\u3092\u7DE8\u96C6\u3057\u307E\u3059\u3002\u7D0410\u79D2\u4EE5\u5185\u306B\u516C\u958B\u30B5\u30A4\u30C8\u306B\u53CD\u6620\u3055\u308C\u307E\u3059(S3\u30AD\u30E3\u30C3\u30B7\u30E5\u518D\u751F\u6210)\u3002",
630
+ noConfigurablePlugins: "\u8A2D\u5B9A\u53EF\u80FD\u306A\u30D7\u30E9\u30B0\u30A4\u30F3\u306F\u3042\u308A\u307E\u305B\u3093\u3002",
631
+ save: "\u4FDD\u5B58",
632
+ saving: "\u4FDD\u5B58\u4E2D\u2026",
633
+ saved: "\u4FDD\u5B58\u3057\u307E\u3057\u305F\u3002\u7D0410\u79D2\u4EE5\u5185\u306B\u516C\u958B\u30B5\u30A4\u30C8\u306B\u53CD\u6620\u3055\u308C\u307E\u3059\u3002",
634
+ saveError: "\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
635
+ invalidValue: "\u4E0D\u6B63\u306A\u5024\u304C\u542B\u307E\u308C\u3066\u3044\u307E\u3059 \u2014 \u30CF\u30A4\u30E9\u30A4\u30C8\u3055\u308C\u305F\u9805\u76EE\u3092\u4FEE\u6B63\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
636
+ resetToDefault: "\u30C7\u30D5\u30A9\u30EB\u30C8\u306B\u623B\u3059",
637
+ resetDone: "\u4FDD\u5B58\u5024\u3092\u30AF\u30EA\u30A2\u3057\u307E\u3057\u305F\u3002\u30DE\u30CB\u30D5\u30A7\u30B9\u30C8\u306E\u30C7\u30D5\u30A9\u30EB\u30C8\u5024\u304C\u4F7F\u7528\u3055\u308C\u307E\u3059\u3002"
638
+ },
613
639
  mcpTokens: {
614
640
  title: "MCP \u30C8\u30FC\u30AF\u30F3",
615
641
  description: "HTTP MCP \u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u7528\u306E\u30A2\u30AF\u30BB\u30B9\u30C8\u30FC\u30AF\u30F3\u3002Claude Desktop / Cursor \u306A\u3069\u306E MCP \u5BFE\u5FDC\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8\u304B\u3089\u3001\u3053\u306E CMS \u306E\u8AAD\u307F\u66F8\u304D\u306B\u4F7F\u3048\u307E\u3059\u3002",
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import {
3
3
  useT
4
- } from "./chunk-TGEPARLA.js";
4
+ } from "./chunk-EX3XWPBC.js";
5
5
 
6
6
  // src/components/login-view.tsx
7
7
  import { useState } from "react";
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-C7G5AQ3L.js";
5
5
  import {
6
6
  useT
7
- } from "./chunk-TGEPARLA.js";
7
+ } from "./chunk-EX3XWPBC.js";
8
8
 
9
9
  // src/components/mcp-tokens-view.tsx
10
10
  import { useEffect, useState } from "react";
@@ -3,13 +3,13 @@ import {
3
3
  ImageUploadDialog,
4
4
  getMediaProcessingDefaults,
5
5
  uploadProcessedImage
6
- } from "./chunk-OMWVBK2C.js";
6
+ } from "./chunk-QUFDISLW.js";
7
7
  import {
8
8
  publicMediaUrl
9
9
  } from "./chunk-2ITWLRYF.js";
10
10
  import {
11
11
  useT
12
- } from "./chunk-TGEPARLA.js";
12
+ } from "./chunk-EX3XWPBC.js";
13
13
 
14
14
  // src/components/media-picker.tsx
15
15
  import { useEffect, useRef, useState } from "react";
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import {
3
3
  useT
4
- } from "./chunk-TGEPARLA.js";
4
+ } from "./chunk-EX3XWPBC.js";
5
5
 
6
6
  // src/components/users-list-view.tsx
7
7
  import { useEffect, useState } from "react";
@@ -1,9 +1,9 @@
1
1
  "use client";
2
2
  import {
3
3
  AdminDashboard
4
- } from "../chunk-3QXCPMPD.js";
5
- import "../chunk-TGEPARLA.js";
6
- import "../chunk-5DWBQ73J.js";
4
+ } from "../chunk-4VX6Z3OI.js";
5
+ import "../chunk-EX3XWPBC.js";
6
+ import "../chunk-U2VSCLWY.js";
7
7
  export {
8
8
  AdminDashboard
9
9
  };
@@ -1,12 +1,12 @@
1
1
  "use client";
2
2
  import {
3
3
  EditPostPage
4
- } from "../chunk-GJSMMQVO.js";
5
- import "../chunk-UTQRFC6V.js";
6
- import "../chunk-OMWVBK2C.js";
4
+ } from "../chunk-AZR2PYIA.js";
5
+ import "../chunk-X4SCR6A4.js";
6
+ import "../chunk-QUFDISLW.js";
7
7
  import "../chunk-2ITWLRYF.js";
8
- import "../chunk-TGEPARLA.js";
9
- import "../chunk-5DWBQ73J.js";
8
+ import "../chunk-EX3XWPBC.js";
9
+ import "../chunk-U2VSCLWY.js";
10
10
  export {
11
11
  EditPostPage
12
12
  };
@@ -1,5 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { L as Locale, D as Dictionary } from '../i18n-sYi0kbnC.js';
2
+ import { L as Locale, D as Dictionary } from '../i18n-C2ev0Y0f.js';
3
3
  import { Config, Post, ThemeManifest, LocalizedString, MediaProcessingDefaults } from 'ampless';
4
4
  import { AmplessOutputs, ColorScheme } from '@ampless/runtime';
5
5
  import { ProcessOptions } from 'ampless/media';