@hesed/webui 0.1.0 → 0.2.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 (106) hide show
  1. package/README.md +2 -2
  2. package/dist/commands/webui.d.ts +8 -0
  3. package/dist/commands/webui.js +12 -0
  4. package/dist/lib/introspect.js +23 -10
  5. package/dist/lib/server.d.ts +7 -1
  6. package/dist/lib/server.js +1 -1
  7. package/oclif.manifest.json +1 -1
  8. package/package.json +1 -1
  9. package/web/app/globals.css +42 -0
  10. package/web/app/page.tsx +1 -1
  11. package/web/components/command-detail.tsx +1 -2
  12. package/web/components/command-list.tsx +49 -11
  13. package/web/.next/BUILD_ID +0 -1
  14. package/web/.next/app-build-manifest.json +0 -26
  15. package/web/.next/app-path-routes-manifest.json +0 -4
  16. package/web/.next/build-manifest.json +0 -33
  17. package/web/.next/cache/.previewinfo +0 -1
  18. package/web/.next/cache/.rscinfo +0 -1
  19. package/web/.next/cache/.tsbuildinfo +0 -1
  20. package/web/.next/cache/eslint/.cache_1ki824o +0 -1
  21. package/web/.next/cache/next-devtools-config.json +0 -1
  22. package/web/.next/cache/webpack/client-development/0.pack.gz +0 -0
  23. package/web/.next/cache/webpack/client-development/1.pack.gz +0 -0
  24. package/web/.next/cache/webpack/client-development/index.pack.gz +0 -0
  25. package/web/.next/cache/webpack/client-development/index.pack.gz.old +0 -0
  26. package/web/.next/cache/webpack/client-production/0.pack +0 -0
  27. package/web/.next/cache/webpack/client-production/1.pack +0 -0
  28. package/web/.next/cache/webpack/client-production/2.pack +0 -0
  29. package/web/.next/cache/webpack/client-production/3.pack +0 -0
  30. package/web/.next/cache/webpack/client-production/index.pack +0 -0
  31. package/web/.next/cache/webpack/client-production/index.pack.old +0 -0
  32. package/web/.next/cache/webpack/edge-server-production/0.pack +0 -0
  33. package/web/.next/cache/webpack/edge-server-production/index.pack +0 -0
  34. package/web/.next/cache/webpack/server-development/0.pack.gz +0 -0
  35. package/web/.next/cache/webpack/server-development/index.pack.gz +0 -0
  36. package/web/.next/cache/webpack/server-production/0.pack +0 -0
  37. package/web/.next/cache/webpack/server-production/1.pack +0 -0
  38. package/web/.next/cache/webpack/server-production/index.pack +0 -0
  39. package/web/.next/cache/webpack/server-production/index.pack.old +0 -0
  40. package/web/.next/diagnostics/build-diagnostics.json +0 -6
  41. package/web/.next/diagnostics/framework.json +0 -1
  42. package/web/.next/export-marker.json +0 -6
  43. package/web/.next/images-manifest.json +0 -58
  44. package/web/.next/next-minimal-server.js.nft.json +0 -1
  45. package/web/.next/next-server.js.nft.json +0 -1
  46. package/web/.next/package.json +0 -1
  47. package/web/.next/prerender-manifest.json +0 -61
  48. package/web/.next/react-loadable-manifest.json +0 -1
  49. package/web/.next/required-server-files.json +0 -320
  50. package/web/.next/routes-manifest.json +0 -53
  51. package/web/.next/server/app/_not-found/page.js +0 -2
  52. package/web/.next/server/app/_not-found/page.js.nft.json +0 -1
  53. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +0 -1
  54. package/web/.next/server/app/_not-found.html +0 -1
  55. package/web/.next/server/app/_not-found.meta +0 -8
  56. package/web/.next/server/app/_not-found.rsc +0 -16
  57. package/web/.next/server/app/index.html +0 -1
  58. package/web/.next/server/app/index.meta +0 -7
  59. package/web/.next/server/app/index.rsc +0 -20
  60. package/web/.next/server/app/page.js +0 -2
  61. package/web/.next/server/app/page.js.nft.json +0 -1
  62. package/web/.next/server/app/page_client-reference-manifest.js +0 -1
  63. package/web/.next/server/app-paths-manifest.json +0 -4
  64. package/web/.next/server/chunks/157.js +0 -6
  65. package/web/.next/server/chunks/799.js +0 -30
  66. package/web/.next/server/functions-config-manifest.json +0 -4
  67. package/web/.next/server/interception-route-rewrite-manifest.js +0 -1
  68. package/web/.next/server/middleware-build-manifest.js +0 -1
  69. package/web/.next/server/middleware-manifest.json +0 -6
  70. package/web/.next/server/middleware-react-loadable-manifest.js +0 -1
  71. package/web/.next/server/next-font-manifest.js +0 -1
  72. package/web/.next/server/next-font-manifest.json +0 -1
  73. package/web/.next/server/pages/404.html +0 -1
  74. package/web/.next/server/pages/500.html +0 -1
  75. package/web/.next/server/pages/_app.js +0 -1
  76. package/web/.next/server/pages/_app.js.nft.json +0 -1
  77. package/web/.next/server/pages/_document.js +0 -1
  78. package/web/.next/server/pages/_document.js.nft.json +0 -1
  79. package/web/.next/server/pages/_error.js +0 -19
  80. package/web/.next/server/pages/_error.js.nft.json +0 -1
  81. package/web/.next/server/pages-manifest.json +0 -6
  82. package/web/.next/server/server-reference-manifest.js +0 -1
  83. package/web/.next/server/server-reference-manifest.json +0 -1
  84. package/web/.next/server/webpack-runtime.js +0 -1
  85. package/web/.next/static/LEwu5WvMMaoVEbVj3-KGq/_buildManifest.js +0 -1
  86. package/web/.next/static/LEwu5WvMMaoVEbVj3-KGq/_ssgManifest.js +0 -1
  87. package/web/.next/static/chunks/131-a68a87dd22cef82b.js +0 -1
  88. package/web/.next/static/chunks/app/_not-found/page-784ddbf5f1a9343a.js +0 -1
  89. package/web/.next/static/chunks/app/layout-4e0fe443358078fc.js +0 -1
  90. package/web/.next/static/chunks/app/page-d098bd39f1fcec43.js +0 -1
  91. package/web/.next/static/chunks/c7879cf7-b5ab1053c1d9a2e7.js +0 -1
  92. package/web/.next/static/chunks/framework-1934959d81242241.js +0 -1
  93. package/web/.next/static/chunks/main-a8814cf406d931cd.js +0 -1
  94. package/web/.next/static/chunks/main-app-a3b742ef05fa17a3.js +0 -1
  95. package/web/.next/static/chunks/pages/_app-930c2e1cfe9b86bc.js +0 -1
  96. package/web/.next/static/chunks/pages/_error-2a950c2742f550d2.js +0 -1
  97. package/web/.next/static/chunks/polyfills-42372ed130431b0a.js +0 -1
  98. package/web/.next/static/chunks/webpack-3046868c425865e4.js +0 -1
  99. package/web/.next/static/css/48007c73fe98a161.css +0 -1
  100. package/web/.next/trace +0 -2
  101. package/web/.next/types/app/layout.ts +0 -84
  102. package/web/.next/types/app/page.ts +0 -84
  103. package/web/.next/types/cache-life.d.ts +0 -141
  104. package/web/.next/types/package.json +0 -1
  105. package/web/.next/types/routes.d.ts +0 -57
  106. package/web/.next/types/validator.ts +0 -61
package/README.md CHANGED
@@ -26,7 +26,7 @@ $ npm install -g @hesed/webui
26
26
  $ webui COMMAND
27
27
  running command...
28
28
  $ webui (--version)
29
- @hesed/webui/0.1.0 darwin-arm64 node-v22.22.3
29
+ @hesed/webui/0.2.0 linux-x64 node-v24.16.0
30
30
  $ webui --help [COMMAND]
31
31
  USAGE
32
32
  $ webui COMMAND
@@ -63,5 +63,5 @@ EXAMPLES
63
63
  $ webui webui --host 0.0.0.0
64
64
  ```
65
65
 
66
- _See code: [src/commands/webui.ts](https://github.com/hesedcasa/webui/blob/v0.1.0/src/commands/webui.ts)_
66
+ _See code: [src/commands/webui.ts](https://github.com/hesedcasa/webui/blob/v0.2.0/src/commands/webui.ts)_
67
67
  <!-- commandsstop -->
@@ -1,3 +1,5 @@
1
+ import type { Config } from '@oclif/core';
2
+ import type { LoadOptions } from '@oclif/core/interfaces';
1
3
  import { Command } from '@oclif/core';
2
4
  export default class WebUI extends Command {
3
5
  static description: string;
@@ -7,6 +9,12 @@ export default class WebUI extends Command {
7
9
  open: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
10
  port: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
9
11
  };
12
+ /**
13
+ * oclif normally reloads a Config when a command comes from a different
14
+ * oclif core installation. Preserve the host CLI's initialized Config so
15
+ * commands registered dynamically by init hooks remain available to the UI.
16
+ */
17
+ static run<T extends Command>(this: new (argv: string[], config: Config) => T, argv?: string[], options?: LoadOptions): Promise<ReturnType<T['run']>>;
10
18
  run(): Promise<void>;
11
19
  private openBrowser;
12
20
  }
@@ -23,6 +23,18 @@ export default class WebUI extends Command {
23
23
  description: 'Port to listen on.',
24
24
  }),
25
25
  };
26
+ /**
27
+ * oclif normally reloads a Config when a command comes from a different
28
+ * oclif core installation. Preserve the host CLI's initialized Config so
29
+ * commands registered dynamically by init hooks remain available to the UI.
30
+ */
31
+ static async run(argv = [], options) {
32
+ if (options && typeof options !== 'string' && 'runCommand' in options) {
33
+ const command = new this(argv, options);
34
+ return command._run();
35
+ }
36
+ return super.run(argv, options);
37
+ }
26
38
  async run() {
27
39
  const { flags } = await this.parse(WebUI);
28
40
  this.log(`Starting web UI…`);
@@ -1,9 +1,20 @@
1
- function flagToMeta(name, flag) {
1
+ function renderMetadataText(value, config, commandId) {
2
+ if (!value)
3
+ return value;
4
+ const replacements = {
5
+ 'command.id': commandId,
6
+ 'config.bin': config.bin,
7
+ 'config.name': config.name,
8
+ 'config.version': config.version,
9
+ };
10
+ return value.replaceAll(/<%=\s*(command\.id|config\.(?:bin|name|version))\s*%>/g, (template, key) => replacements[key] ?? template);
11
+ }
12
+ function flagToMeta(name, flag, config, commandId) {
2
13
  const isBoolean = flag.type === 'boolean';
3
14
  return {
4
15
  char: flag.char,
5
16
  default: isBoolean ? undefined : flag.default,
6
- description: flag.summary ?? flag.description,
17
+ description: renderMetadataText(flag.summary ?? flag.description, config, commandId),
7
18
  multiple: !isBoolean && Boolean(flag.multiple),
8
19
  name,
9
20
  options: isBoolean ? undefined : flag.options,
@@ -11,10 +22,10 @@ function flagToMeta(name, flag) {
11
22
  type: isBoolean ? 'boolean' : 'option',
12
23
  };
13
24
  }
14
- function argToMeta(name, arg) {
25
+ function argToMeta(name, arg, config, commandId) {
15
26
  return {
16
27
  default: arg.default,
17
- description: arg.description,
28
+ description: renderMetadataText(arg.description, config, commandId),
18
29
  name,
19
30
  options: arg.options,
20
31
  required: Boolean(arg.required),
@@ -26,19 +37,21 @@ function argToMeta(name, arg) {
26
37
  */
27
38
  export function describeCommands(config) {
28
39
  return config.commands
29
- .filter((cmd) => !cmd.hidden)
40
+ .filter((cmd) => !cmd.hidden && !(cmd.id === 'webui' && cmd.pluginName === '@hesed/webui'))
30
41
  .map((cmd) => ({
31
42
  aliases: cmd.aliases ?? [],
32
- args: Object.entries(cmd.args ?? {}).map(([name, arg]) => argToMeta(name, arg)),
33
- description: cmd.description,
43
+ args: Object.entries(cmd.args ?? {}).map(([name, arg]) => argToMeta(name, arg, config, cmd.id)),
44
+ description: renderMetadataText(cmd.description, config, cmd.id),
34
45
  flags: Object.entries(cmd.flags ?? {})
35
46
  .filter(([, flag]) => !flag.hidden)
36
- .map(([name, flag]) => flagToMeta(name, flag)),
47
+ .map(([name, flag]) => flagToMeta(name, flag, config, cmd.id)),
37
48
  id: cmd.id,
38
49
  pluginName: cmd.pluginName,
39
50
  pluginType: cmd.pluginType,
40
- summary: cmd.summary,
41
- usage: cmd.usage,
51
+ summary: renderMetadataText(cmd.summary, config, cmd.id),
52
+ usage: Array.isArray(cmd.usage)
53
+ ? cmd.usage.map((usage) => renderMetadataText(usage, config, cmd.id) ?? usage)
54
+ : renderMetadataText(cmd.usage, config, cmd.id),
42
55
  }))
43
56
  .sort((a, b) => a.id.localeCompare(b.id));
44
57
  }
@@ -1,5 +1,5 @@
1
1
  import type { Config } from '@oclif/core';
2
- import type { Server } from 'node:http';
2
+ import type { IncomingMessage, Server, ServerResponse } from 'node:http';
3
3
  interface ServerOptions {
4
4
  config: Config;
5
5
  host: string;
@@ -9,6 +9,12 @@ interface RunningServer {
9
9
  server: Server;
10
10
  url: string;
11
11
  }
12
+ /**
13
+ * Handle the small JSON API the browser app talks to. Returns `true` if the
14
+ * request was an API request (and has been answered), `false` otherwise so the
15
+ * caller can hand it to Next.js.
16
+ */
17
+ export declare function handleApi(config: Config, req: IncomingMessage, res: ServerResponse): Promise<boolean>;
12
18
  /**
13
19
  * Start the web UI server: a Next.js app for the front-end plus a JSON API
14
20
  * (served from the same origin) that introspects and runs oclif commands.
@@ -25,7 +25,7 @@ async function readJsonBody(req) {
25
25
  * request was an API request (and has been answered), `false` otherwise so the
26
26
  * caller can hand it to Next.js.
27
27
  */
28
- async function handleApi(config, req, res) {
28
+ export async function handleApi(config, req, res) {
29
29
  const { pathname } = new URL(req.url ?? '/', 'http://localhost');
30
30
  if (!pathname?.startsWith('/api/'))
31
31
  return false;
@@ -50,5 +50,5 @@
50
50
  ]
51
51
  }
52
52
  },
53
- "version": "0.1.0"
53
+ "version": "0.2.0"
54
54
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hesed/webui",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Web UI to browse and execute commands",
5
5
  "author": "Hesed",
6
6
  "bin": {
@@ -128,12 +128,54 @@ body {
128
128
  border-color: var(--accent);
129
129
  }
130
130
 
131
+ .topic-filter {
132
+ display: grid;
133
+ grid-template-columns: auto 1fr;
134
+ align-items: center;
135
+ gap: 8px;
136
+ margin-top: 10px;
137
+ }
138
+
139
+ .topic-filter label,
140
+ .topic-heading {
141
+ color: var(--muted);
142
+ font-size: 11px;
143
+ font-weight: 600;
144
+ text-transform: uppercase;
145
+ letter-spacing: 0.06em;
146
+ }
147
+
148
+ .topic-filter select {
149
+ min-width: 0;
150
+ padding: 7px 9px;
151
+ background: var(--bg);
152
+ border: 1px solid var(--border);
153
+ border-radius: 6px;
154
+ color: var(--text);
155
+ font-size: 12px;
156
+ }
157
+
158
+ .topic-filter select:focus {
159
+ outline: none;
160
+ border-color: var(--accent);
161
+ }
162
+
131
163
  .command-list {
132
164
  overflow-y: auto;
133
165
  flex: 1;
134
166
  padding: 8px;
135
167
  }
136
168
 
169
+ .command-group + .command-group {
170
+ margin-top: 10px;
171
+ }
172
+
173
+ .topic-heading {
174
+ display: flex;
175
+ justify-content: space-between;
176
+ padding: 6px 10px 4px;
177
+ }
178
+
137
179
  .command-item {
138
180
  width: 100%;
139
181
  text-align: left;
package/web/app/page.tsx CHANGED
@@ -43,7 +43,7 @@ export default function Page() {
43
43
  />
44
44
  <main className="main">
45
45
  {selected ? (
46
- <CommandDetail bin={data.bin} command={selected} />
46
+ <CommandDetail bin={data.bin} command={selected} key={selected.id} />
47
47
  ) : (
48
48
  <div className="empty">
49
49
  Select a command from the left to view its options and run it.
@@ -10,7 +10,6 @@ export function CommandDetail({bin, command}: {bin: string; command: CommandMeta
10
10
  const [result, setResult] = useState<null | RunResult>(null)
11
11
  const [running, setRunning] = useState(false)
12
12
 
13
- // Reset form state whenever the selected command changes.
14
13
  const argv = useMemo(() => buildArgv(command, argValues, flagValues), [command, argValues, flagValues])
15
14
  const preview = `${bin || 'sdkck'} ${command.id}${argv.length > 0 ? ' ' + argv.join(' ') : ''}`
16
15
 
@@ -32,7 +31,7 @@ export function CommandDetail({bin, command}: {bin: string; command: CommandMeta
32
31
  }
33
32
 
34
33
  return (
35
- <div className="detail" key={command.id}>
34
+ <div className="detail">
36
35
  <h1>
37
36
  {command.id}
38
37
  {command.pluginName && <span className="badge">{command.pluginName}</span>}
@@ -1,5 +1,7 @@
1
1
  'use client'
2
2
 
3
+ import {useMemo, useState} from 'react'
4
+
3
5
  import type {CommandMeta} from '../lib/types'
4
6
 
5
7
  import {ThemeToggle} from './theme-toggle'
@@ -21,7 +23,19 @@ export function CommandList({
21
23
  setQuery: (q: string) => void
22
24
  version: string
23
25
  }) {
26
+ const [topic, setTopic] = useState('all')
27
+ const topics = useMemo(() => {
28
+ const counts = new Map<string, number>()
29
+ for (const command of commands) {
30
+ const commandTopic = command.id.split(':', 1)[0]
31
+ counts.set(commandTopic, (counts.get(commandTopic) ?? 0) + 1)
32
+ }
33
+
34
+ return [...counts].sort(([a], [b]) => a.localeCompare(b))
35
+ }, [commands])
36
+
24
37
  const filtered = commands.filter((cmd) => {
38
+ if (topic !== 'all' && cmd.id.split(':', 1)[0] !== topic) return false
25
39
  if (!query) return true
26
40
  const haystack = `${cmd.id} ${cmd.summary ?? ''} ${cmd.description ?? ''}`.toLowerCase()
27
41
  return query
@@ -47,20 +61,44 @@ export function CommandList({
47
61
  type="text"
48
62
  value={query}
49
63
  />
64
+ <div className="topic-filter">
65
+ <label htmlFor="topic">Topic</label>
66
+ <select id="topic" onChange={(event) => setTopic(event.target.value)} value={topic}>
67
+ <option value="all">All topics ({commands.length})</option>
68
+ {topics.map(([name, count]) => (
69
+ <option key={name} value={name}>
70
+ {name} ({count})
71
+ </option>
72
+ ))}
73
+ </select>
74
+ </div>
50
75
  </div>
51
76
  <nav className="command-list">
52
77
  {filtered.length === 0 && <div className="empty">No commands match.</div>}
53
- {filtered.map((cmd) => (
54
- <button
55
- className={`command-item${cmd.id === selectedId ? ' active' : ''}`}
56
- key={cmd.id}
57
- onClick={() => onSelect(cmd.id)}
58
- type="button"
59
- >
60
- <span className="cmd-id">{cmd.id}</span>
61
- <span className="cmd-summary">{cmd.summary ?? cmd.description ?? ''}</span>
62
- </button>
63
- ))}
78
+ {topics.map(([name]) => {
79
+ const topicCommands = filtered.filter((command) => command.id.split(':', 1)[0] === name)
80
+ if (topicCommands.length === 0) return null
81
+
82
+ return (
83
+ <section className="command-group" key={name}>
84
+ <div className="topic-heading">
85
+ <span>{name}</span>
86
+ <span>{topicCommands.length}</span>
87
+ </div>
88
+ {topicCommands.map((cmd) => (
89
+ <button
90
+ className={`command-item${cmd.id === selectedId ? ' active' : ''}`}
91
+ key={cmd.id}
92
+ onClick={() => onSelect(cmd.id)}
93
+ type="button"
94
+ >
95
+ <span className="cmd-id">{cmd.id}</span>
96
+ <span className="cmd-summary">{cmd.summary ?? cmd.description ?? ''}</span>
97
+ </button>
98
+ ))}
99
+ </section>
100
+ )
101
+ })}
64
102
  </nav>
65
103
  </aside>
66
104
  )
@@ -1 +0,0 @@
1
- LEwu5WvMMaoVEbVj3-KGq
@@ -1,26 +0,0 @@
1
- {
2
- "pages": {
3
- "/_not-found/page": [
4
- "static/chunks/webpack-3046868c425865e4.js",
5
- "static/chunks/c7879cf7-b5ab1053c1d9a2e7.js",
6
- "static/chunks/131-a68a87dd22cef82b.js",
7
- "static/chunks/main-app-a3b742ef05fa17a3.js",
8
- "static/chunks/app/_not-found/page-784ddbf5f1a9343a.js"
9
- ],
10
- "/layout": [
11
- "static/chunks/webpack-3046868c425865e4.js",
12
- "static/chunks/c7879cf7-b5ab1053c1d9a2e7.js",
13
- "static/chunks/131-a68a87dd22cef82b.js",
14
- "static/chunks/main-app-a3b742ef05fa17a3.js",
15
- "static/css/48007c73fe98a161.css",
16
- "static/chunks/app/layout-4e0fe443358078fc.js"
17
- ],
18
- "/page": [
19
- "static/chunks/webpack-3046868c425865e4.js",
20
- "static/chunks/c7879cf7-b5ab1053c1d9a2e7.js",
21
- "static/chunks/131-a68a87dd22cef82b.js",
22
- "static/chunks/main-app-a3b742ef05fa17a3.js",
23
- "static/chunks/app/page-d098bd39f1fcec43.js"
24
- ]
25
- }
26
- }
@@ -1,4 +0,0 @@
1
- {
2
- "/_not-found/page": "/_not-found",
3
- "/page": "/"
4
- }
@@ -1,33 +0,0 @@
1
- {
2
- "polyfillFiles": [
3
- "static/chunks/polyfills-42372ed130431b0a.js"
4
- ],
5
- "devFiles": [],
6
- "ampDevFiles": [],
7
- "lowPriorityFiles": [
8
- "static/LEwu5WvMMaoVEbVj3-KGq/_buildManifest.js",
9
- "static/LEwu5WvMMaoVEbVj3-KGq/_ssgManifest.js"
10
- ],
11
- "rootMainFiles": [
12
- "static/chunks/webpack-3046868c425865e4.js",
13
- "static/chunks/c7879cf7-b5ab1053c1d9a2e7.js",
14
- "static/chunks/131-a68a87dd22cef82b.js",
15
- "static/chunks/main-app-a3b742ef05fa17a3.js"
16
- ],
17
- "rootMainFilesTree": {},
18
- "pages": {
19
- "/_app": [
20
- "static/chunks/webpack-3046868c425865e4.js",
21
- "static/chunks/framework-1934959d81242241.js",
22
- "static/chunks/main-a8814cf406d931cd.js",
23
- "static/chunks/pages/_app-930c2e1cfe9b86bc.js"
24
- ],
25
- "/_error": [
26
- "static/chunks/webpack-3046868c425865e4.js",
27
- "static/chunks/framework-1934959d81242241.js",
28
- "static/chunks/main-a8814cf406d931cd.js",
29
- "static/chunks/pages/_error-2a950c2742f550d2.js"
30
- ]
31
- },
32
- "ampFirstPages": []
33
- }
@@ -1 +0,0 @@
1
- {"previewModeId":"63f2f8c7b13438f3b92669c1ecce7f00","previewModeSigningKey":"64015a693092958936de470ce931f1bd410de8a2f8c6aef1ce8af87de8375dcd","previewModeEncryptionKey":"72cf980c4c8d18be499583dc84861c68dbaeb46f8b8df13c3aef3591b5cc4151","expireAt":1783148711578}
@@ -1 +0,0 @@
1
- {"encryption.key":"SUJ1L2Gp2SxF0qg4pIsnWs4EdpXdO3e+Eff5rWKj3xA=","encryption.expire_at":1783146077960}