@p10i/create-nestbox 0.1.2 → 0.3.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.
@@ -37,6 +37,9 @@ services:
37
37
  # Executes super-document HTTP and CLI directions. Trusted pages may update
38
38
  # page state and intentionally operate on the mounted workspace.
39
39
  php-fpm:
40
+ depends_on:
41
+ control:
42
+ condition: service_healthy
40
43
  build:
41
44
  context: .
42
45
  dockerfile: ./docker/php-fpm/Dockerfile
@@ -56,6 +59,12 @@ services:
56
59
  target: /workspace
57
60
  bind:
58
61
  create_host_path: false
62
+ - type: bind
63
+ source: ${WORKSPACE_PATH:-.}/package.json
64
+ target: /workspace/package.json
65
+ read_only: true
66
+ bind:
67
+ create_host_path: false
59
68
  environment:
60
69
  # Event publication and OpenCode automation stay inside Compose.
61
70
  - NESTBOX_BUS_PUBLISH_URL=http://nginx:8080/_nestbox/publish
@@ -64,6 +73,11 @@ services:
64
73
  - NESTBOX_OPENCODE_PUBLIC_URL=${NESTBOX_OPENCODE_PUBLIC_URL:-}
65
74
  - OPENCODE_SERVER_USERNAME=${OPENCODE_SERVER_USERNAME:-opencode}
66
75
  - OPENCODE_SERVER_PASSWORD=${OPENCODE_SERVER_PASSWORD:-}
76
+ - NESTBOX_CONTROL_URL=http://control:4088
77
+ labels:
78
+ - nestbox.exec.allow-target=true
79
+ - nestbox.exec.allow-source=true
80
+ - nestbox.exec.targets=php-fpm
67
81
  restart: unless-stopped
68
82
 
69
83
  # Watches shared browser source and refreshes tracked bundle baselines.
@@ -88,11 +102,56 @@ services:
88
102
  create_host_path: false
89
103
  # Keep dependencies out of the host page tree.
90
104
  - rollup-node-modules:/sources/rollup/node_modules
105
+ labels:
106
+ - nestbox.exec.allow-target=true
107
+ restart: unless-stopped
108
+
109
+ # Private, label-gated bridge for container exec and native host npm jobs.
110
+ # This is the only Nestbox service with access to the host Docker socket.
111
+ control:
112
+ build:
113
+ context: .
114
+ dockerfile: ./docker/control/Dockerfile
115
+ expose:
116
+ - "4088"
117
+ ports:
118
+ # Authenticated native runner transport; never bind beyond loopback.
119
+ - "127.0.0.1:${NESTBOX_CONTROL_HOST_PORT:-4089}:4089"
120
+ volumes:
121
+ - ${DOCKER_SOCKET_PATH:-/var/run/docker.sock}:/var/run/docker.sock
122
+ - type: bind
123
+ source: ${WORKSPACE_PATH:-.}
124
+ target: /workspace
125
+ read_only: true
126
+ bind:
127
+ create_host_path: false
128
+ - type: bind
129
+ source: ${NESTBOX_HOST_TOKEN_FILE:?NESTBOX_HOST_TOKEN_FILE must be configured}
130
+ target: /run/secrets/nestbox-host-token
131
+ read_only: true
132
+ bind:
133
+ create_host_path: false
134
+ environment:
135
+ - COMPOSE_PROJECT_NAME=${COMPOSE_PROJECT_NAME}
136
+ - NESTBOX_CONTROL_SERVICE=control
137
+ - NESTBOX_CONTROL_HOST_PORT=4089
138
+ - NESTBOX_HOST_TOKEN_FILE=/run/secrets/nestbox-host-token
139
+ - NESTBOX_CONTROL_LOG_OUTPUT=${NESTBOX_CONTROL_LOG_OUTPUT:-text}
140
+ - NESTBOX_CONTROL_LOG_MAX_BYTES=${NESTBOX_CONTROL_LOG_MAX_BYTES:-262144}
141
+ healthcheck:
142
+ test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:4088/health"]
143
+ interval: 10s
144
+ timeout: 3s
145
+ retries: 5
146
+ start_period: 5s
91
147
  restart: unless-stopped
92
148
 
93
149
  # Internal implementation agent. It can edit Nestbox and the workspace but
94
150
  # deliberately receives no Docker socket or direct host control.
95
151
  opencode:
152
+ depends_on:
153
+ control:
154
+ condition: service_healthy
96
155
  build:
97
156
  context: .
98
157
  dockerfile: ./docker/opencode/Dockerfile
@@ -125,11 +184,22 @@ services:
125
184
  target: /workspace
126
185
  bind:
127
186
  create_host_path: false
187
+ - type: bind
188
+ source: ${WORKSPACE_PATH:-.}/package.json
189
+ target: /workspace/package.json
190
+ read_only: true
191
+ bind:
192
+ create_host_path: false
128
193
  # The entrypoint fixes this volume's ownership before dropping privileges.
129
194
  - opencode-data:/home/nestbox/.local/share/opencode
130
195
  working_dir: /workspace
131
196
  environment:
132
197
  - NESTBOX_BUS_PUBLISH_URL=http://nginx:8080/_nestbox/publish
198
+ - NESTBOX_CONTROL_URL=http://control:4088
199
+ labels:
200
+ - nestbox.exec.allow-source=true
201
+ - nestbox.exec.targets=php-fpm,rollup
202
+ - nestbox.npm.allow=true
133
203
  restart: unless-stopped
134
204
 
135
205
  volumes:
@@ -192,6 +192,74 @@ function nestbox_opencode_request(string $method, string $path, array $body, str
192
192
  return json_decode($response, true, flags: JSON_THROW_ON_ERROR);
193
193
  }
194
194
 
195
+ /** Send a bounded JSON request to the private, label-gated control service. */
196
+ function nestbox_control_request(string $method, string $path, array $body = []): mixed
197
+ {
198
+ $baseUrl = rtrim((string) getenv('NESTBOX_CONTROL_URL'), '/');
199
+ if ($baseUrl === '') {
200
+ throw new RuntimeException('NESTBOX_CONTROL_URL is not configured.');
201
+ }
202
+ if (!str_starts_with($path, '/') || preg_match('/[\0\r\n]/', $path)) {
203
+ throw new InvalidArgumentException('Invalid control request path.');
204
+ }
205
+
206
+ $payload = json_encode($body, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES);
207
+ $context = stream_context_create([
208
+ 'http' => [
209
+ 'method' => strtoupper($method),
210
+ 'header' => implode("\r\n", [
211
+ 'Accept: application/json',
212
+ 'Content-Type: application/json',
213
+ 'Content-Length: ' . strlen($payload),
214
+ ]),
215
+ 'content' => $payload,
216
+ 'ignore_errors' => true,
217
+ 'timeout' => 30,
218
+ ],
219
+ ]);
220
+ $response = @file_get_contents($baseUrl . $path, false, $context, 0, 1024 * 1024);
221
+ $statusLine = $http_response_header[0] ?? '';
222
+ preg_match('#^HTTP/\S+\s+(\d{3})#', $statusLine, $matches);
223
+ $status = isset($matches[1]) ? (int) $matches[1] : 0;
224
+ if ($response === false || $status < 200 || $status >= 300) {
225
+ throw new RuntimeException("Control request failed with HTTP status $status.");
226
+ }
227
+ return $response === '' ? null : json_decode($response, true, flags: JSON_THROW_ON_ERROR);
228
+ }
229
+
230
+ /** Execute one explicit argument array in an allowed Compose service. */
231
+ function nestbox_control_exec(
232
+ string $service,
233
+ string $workdir,
234
+ array $command,
235
+ bool $detach = false,
236
+ string $user = '',
237
+ ): array {
238
+ if (!preg_match('/^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/', $service)) {
239
+ throw new InvalidArgumentException('Invalid control target service.');
240
+ }
241
+ if (!str_starts_with($workdir, '/') || str_contains($workdir, "\0")) {
242
+ throw new InvalidArgumentException('Control workdir must be an absolute container path.');
243
+ }
244
+ if ($command === [] || count($command) > 256) {
245
+ throw new InvalidArgumentException('Control command must be a non-empty bounded argument array.');
246
+ }
247
+ foreach ($command as $part) {
248
+ if (!is_string($part) || str_contains($part, "\0")) {
249
+ throw new InvalidArgumentException('Every control command argument must be a string.');
250
+ }
251
+ }
252
+ $body = ['command' => array_values($command), 'workdir' => $workdir, 'detach' => $detach];
253
+ if ($user !== '') {
254
+ $body['user'] = $user;
255
+ }
256
+ $result = nestbox_control_request('POST', '/containers/' . rawurlencode($service) . '/exec', $body);
257
+ if (!is_array($result) || !isset($result['runId']) || !is_string($result['runId'])) {
258
+ throw new RuntimeException('Control service returned an invalid exec response.');
259
+ }
260
+ return $result;
261
+ }
262
+
195
263
  /** Create an OpenCode session synchronously so its ID is available immediately. */
196
264
  function nestbox_opencode_create_session(string $title, string $directory = '/workspace'): array
197
265
  {
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const baseUrl = (process.env.NESTBOX_CONTROL_URL || 'http://control:4088').replace(/\/$/, '');
5
+ const timeoutMs = Number(process.env.NESTBOX_CONTROL_TIMEOUT_MS || '30000');
6
+ const tools = [
7
+ { name: 'docker_exec', description: 'Run a command in an allowed Compose service.', inputSchema: { type: 'object', properties: { machine: { type: 'string' }, workdir: { type: 'string' }, command: { type: 'array', items: { type: 'string' } }, user: { type: 'string' }, detach: { type: 'boolean' } }, required: ['machine', 'workdir', 'command'], additionalProperties: false } },
8
+ { name: 'npm_scripts', description: 'List host npm scripts from /workspace/package.json.', inputSchema: { type: 'object', properties: {}, additionalProperties: false } },
9
+ { name: 'npm_run', description: 'Run an allowed npm script through the active host bridge.', inputSchema: { type: 'object', properties: { script: { type: 'string' }, args: { type: 'array', items: { type: 'string' } }, detach: { type: 'boolean' } }, required: ['script'], additionalProperties: false } },
10
+ { name: 'npm_script_change_request', description: 'Request an add, edit, or delete of an npm script. The confirmation code is displayed only by the host runner.', inputSchema: { type: 'object', properties: { operation: { type: 'string', enum: ['add', 'edit', 'delete'] }, name: { type: 'string' }, command: { type: 'string' } }, required: ['operation', 'name'], additionalProperties: false } },
11
+ { name: 'npm_script_change_confirm', description: 'Confirm a pending npm script change with the host-visible code.', inputSchema: { type: 'object', properties: { requestId: { type: 'string' }, code: { type: 'string' } }, required: ['requestId', 'code'], additionalProperties: false } },
12
+ ];
13
+
14
+ function request(method, path, body) {
15
+ const url = new URL(baseUrl + path);
16
+ const transport = require(url.protocol === 'https:' ? 'https' : 'http');
17
+ const payload = body === undefined ? undefined : JSON.stringify(body);
18
+ return new Promise((resolve, reject) => {
19
+ const headers = { accept: 'application/json' };
20
+ if (payload !== undefined) Object.assign(headers, { 'content-type': 'application/json', 'content-length': Buffer.byteLength(payload) });
21
+ const client = transport.request({ method, hostname: url.hostname, port: url.port || undefined, path: url.pathname + url.search, headers }, response => {
22
+ const chunks = [];
23
+ response.setEncoding('utf8');
24
+ response.on('data', chunk => chunks.push(chunk));
25
+ response.on('end', () => {
26
+ const text = chunks.join('');
27
+ let value = null;
28
+ try { value = text ? JSON.parse(text) : null; } catch { value = text; }
29
+ if (response.statusCode >= 400) reject(new Error(`Control API ${method} ${path} returned ${response.statusCode}: ${JSON.stringify(value)}`));
30
+ else resolve(value);
31
+ });
32
+ });
33
+ client.setTimeout(timeoutMs, () => client.destroy(new Error('Control API request timed out')));
34
+ client.on('error', reject);
35
+ if (payload !== undefined) client.write(payload);
36
+ client.end();
37
+ });
38
+ }
39
+
40
+ function object(value) {
41
+ if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Tool arguments must be an object.');
42
+ return value;
43
+ }
44
+
45
+ async function call(name, raw) {
46
+ const args = object(raw || {});
47
+ if (name === 'docker_exec') {
48
+ if (typeof args.machine !== 'string' || !args.machine) throw new Error('machine must be a non-empty string.');
49
+ if (typeof args.workdir !== 'string' || !args.workdir.startsWith('/')) throw new Error('workdir must be an absolute path.');
50
+ if (!Array.isArray(args.command) || !args.command.length || !args.command.every(value => typeof value === 'string')) throw new Error('command must be a non-empty string array.');
51
+ const body = { command: args.command, workdir: args.workdir };
52
+ if (args.user) body.user = String(args.user);
53
+ if (args.detach !== undefined) body.detach = Boolean(args.detach);
54
+ return { machine: args.machine, workdir: args.workdir, command: args.command, result: await request('POST', `/containers/${encodeURIComponent(args.machine)}/exec`, body) };
55
+ }
56
+ if (name === 'npm_scripts') return request('GET', '/npm/scripts');
57
+ if (name === 'npm_run') {
58
+ if (typeof args.script !== 'string' || !args.script) throw new Error('script must be a non-empty string.');
59
+ if (args.args !== undefined && (!Array.isArray(args.args) || !args.args.every(value => typeof value === 'string'))) throw new Error('args must be a string array.');
60
+ return request('POST', '/npm/run', { script: args.script, ...(args.args === undefined ? {} : { args: args.args }), ...(args.detach === undefined ? {} : { detach: Boolean(args.detach) }) });
61
+ }
62
+ if (name === 'npm_script_change_request') {
63
+ if (!['add', 'edit', 'delete'].includes(args.operation) || typeof args.name !== 'string' || !args.name) throw new Error('operation and name are required.');
64
+ if (args.operation !== 'delete' && (typeof args.command !== 'string' || !args.command)) throw new Error('command is required for add and edit.');
65
+ if (args.operation === 'delete' && args.command !== undefined) throw new Error('command must be omitted for delete.');
66
+ return request('POST', '/npm/scripts/request', { operation: args.operation, name: args.name, ...(args.command === undefined ? {} : { command: args.command }) });
67
+ }
68
+ if (name === 'npm_script_change_confirm') {
69
+ if (typeof args.requestId !== 'string' || typeof args.code !== 'string') throw new Error('requestId and code are required.');
70
+ return request('POST', '/npm/scripts/confirm', { requestId: args.requestId, code: args.code });
71
+ }
72
+ throw new Error(`Unknown tool: ${name}`);
73
+ }
74
+
75
+ function emit(value) { process.stdout.write(`${JSON.stringify(value)}\n`); }
76
+ async function handle(message) {
77
+ if (message.method === 'initialize') return emit({ jsonrpc: '2.0', id: message.id, result: { protocolVersion: '2024-11-05', capabilities: { tools: { listChanged: false } }, serverInfo: { name: 'nestbox-control-mcp', version: '0.3.0' } } });
78
+ if (message.method === 'initialized') return;
79
+ if (message.method === 'tools/list') return emit({ jsonrpc: '2.0', id: message.id, result: { tools } });
80
+ if (message.method === 'tools/call') {
81
+ try {
82
+ const output = await call(message.params?.name, message.params?.arguments);
83
+ emit({ jsonrpc: '2.0', id: message.id, result: { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }] } });
84
+ } catch (error) {
85
+ emit({ jsonrpc: '2.0', id: message.id, error: { code: -32603, message: error.message || 'Tool execution failed.' } });
86
+ }
87
+ return;
88
+ }
89
+ if (message.id !== undefined) emit({ jsonrpc: '2.0', id: message.id, error: { code: -32601, message: `Method not supported: ${message.method}` } });
90
+ }
91
+
92
+ let input = '';
93
+ process.stdin.on('data', chunk => {
94
+ input += chunk.toString('utf8');
95
+ let boundary;
96
+ while ((boundary = input.indexOf('\n')) >= 0) {
97
+ const line = input.slice(0, boundary).replace(/\r$/, ''); input = input.slice(boundary + 1);
98
+ if (!line) continue;
99
+ try { Promise.resolve(handle(JSON.parse(line))).catch(error => emit({ jsonrpc: '2.0', id: null, error: { code: -32603, message: error.message } })); }
100
+ catch (error) { emit({ jsonrpc: '2.0', id: null, error: { code: -32700, message: error.message } }); }
101
+ }
102
+ });
@@ -168,11 +168,25 @@ The interactive 404 page demonstrates the intended flow: create a session scoped
168
168
 
169
169
  Use API-created sessions only for explicit page workflows. The shared header's Open Chat link remains the normal entry point for conversation.
170
170
 
171
+ ## Container Control API
172
+
173
+ OpenCode has no Docker socket. Use the control MCP as the only path for commands in other containers or on the native host:
174
+
175
+ - `docker_exec` accepts an allowed Compose `machine`, absolute `workdir`, positional `command` array, and optional `user` or `detach` value.
176
+ - `npm_scripts` lists scripts from `/workspace/package.json`.
177
+ - `npm_run` executes one declared script through the active native host runner. Prefer `detach: true` for work expected to exceed the MCP request timeout.
178
+ - `npm_script_change_request` requests an add, edit, or delete. The confirmation code appears only in the host runner terminal.
179
+ - `npm_script_change_confirm` applies the pending change after the user explicitly supplies that code.
180
+
181
+ Do not call the control HTTP API directly from OpenCode, impersonate the native runner, invoke Docker or SSH as a substitute, or work around a missing MCP capability. PHP cannot invoke MCP, so trusted super-documents may use `nestbox_control_exec()`; default labels restrict PHP-FPM to self-exec. Only OpenCode has `nestbox.npm.allow=true` by default. Root exec requires an explicit target label.
182
+
183
+ The control log is the audit source of truth and requires no observability service. The native runner uses an authenticated, loopback-only long-poll endpoint; no host listener or filesystem queue exists. Commands still operate in trusted containers and a trusted writable workspace; Docker socket access and editable host-script source mean this is a controlled capability, not a security sandbox.
184
+
171
185
  ## Git And Commit Policy
172
186
 
173
187
  - Resolve the owning Git root before staging any path.
174
188
  - Read the separately loaded `instance.md` before committing Nestbox work. It records the installation layout, repository roots, and commit policy without exposing `.env`.
175
- - Treat the repository as installation history. Do not assume an upstream remote or upstream commit ancestry exists; use the recorded source revision when reasoning about upgrades.
189
+ - Treat the repository as installation history. Do not assume an upstream remote or upstream commit ancestry exists.
176
190
  - The policy may allow logical commits after verification, require confirmation before each commit, or require leaving changes uncommitted.
177
191
  - Reasonable Nestbox commits include super-documents, Compose, Dockerfiles, configurations, integrations, and dependency files.
178
192
  - Keep commits within one Git root. When one feature spans repositories, create separate commits and report their relationship.
@@ -182,12 +196,13 @@ Use API-created sessions only for explicit page workflows. The shared header's O
182
196
 
183
197
  ## Container Architecture
184
198
 
185
- The default Compose application has four services:
199
+ The default Compose application has five services:
186
200
 
187
201
  - `nginx`: the only host-published gateway. It serves static page files, routes super-documents to PHP-FPM, proxies the OpenCode virtual host, and hosts Nchan.
188
202
  - `php-fpm`: executes HTTP and CLI directions. It mounts the page tree and workspace writable because trusted pages may intentionally modify them.
189
203
  - `rollup`: watches `scripts.js` and browser dependencies and writes local JS/CSS bundles into the page tree.
190
204
  - `opencode`: runs this agent, mounts the engine and workspace, and edits the page tree through `/nestbox/home/code`.
205
+ - `control`: owns the Docker socket and privately brokers label-gated container exec and host-runner jobs.
191
206
 
192
207
  The default host port is `4180` after installer confirmation. Nginx container port `80` serves both Nestbox and OpenCode through host-based routing. Nginx container port `8080` is only the internal Nchan publisher.
193
208
 
@@ -214,6 +229,7 @@ The engine's untracked `/nestbox/.env` controls runtime paths, exposure, authent
214
229
  - `APP_UID` and `APP_GID`: PHP-FPM ownership for writable bind mounts.
215
230
  - `NESTBOX_URL_PREFIX`: public path prefix when an outer proxy strips that prefix before forwarding.
216
231
  - `NESTBOX_OPENCODE_PUBLIC_URL`: explicit public OpenCode origin when `agent.<nestbox-host>` cannot be derived.
232
+ - `NESTBOX_CONTROL_URL`: internal control API URL, normally `http://control:4088`.
217
233
  - `OPENCODE_SERVER_USERNAME` and `OPENCODE_SERVER_PASSWORD`: OpenCode HTTP authentication shared with PHP's internal API client.
218
234
 
219
235
  Nginx publishes one host port and distinguishes two browser origins:
@@ -271,13 +287,13 @@ Mount every directory needed by both PHP and OpenCode at the same container path
271
287
 
272
288
  ## Container Boundary And Host Handoff
273
289
 
274
- This OpenCode container deliberately has no Docker socket and may not be able to run Docker CLI commands. Never claim to build, pull, start, stop, restart, recreate, inspect, or remove host containers from this session.
290
+ This OpenCode container deliberately has no Docker socket. Use control MCP only for its declared, label-gated operations. Never claim a broader Docker or operating-system capability.
275
291
 
276
292
  You may edit Compose files, Dockerfiles, Nginx configuration, PHP configuration, dependency files, mount definitions, and OpenCode configuration. After an infrastructure edit:
277
293
 
278
294
  1. Validate every part that can be checked inside this container.
279
295
  2. Identify the exact affected services.
280
- 3. Give the user the narrowest host-side command to run from the engine directory.
296
+ 3. Use a declared host npm script through MCP when one exists; otherwise give the user the narrowest host-side command.
281
297
  4. State that activation and host health checks remain pending.
282
298
  5. Tell the user to reconnect if OpenCode itself must restart.
283
299
 
@@ -5,6 +5,17 @@
5
5
  "port": 4096
6
6
  },
7
7
  "instructions": ["instructions.md", "instance.md"],
8
+ "mcp": {
9
+ "control": {
10
+ "type": "local",
11
+ "command": ["node", "/etc/opencode/control-mcp.js"],
12
+ "enabled": true,
13
+ "environment": {
14
+ "NESTBOX_CONTROL_URL": "{env:NESTBOX_CONTROL_URL}",
15
+ "NESTBOX_CONTROL_TIMEOUT_MS": "30000"
16
+ }
17
+ }
18
+ },
8
19
  "permission": {
9
20
  "external_directory": {
10
21
  "*": "ask",
@@ -1088,16 +1088,15 @@
1088
1088
  "license": "CC0-1.0"
1089
1089
  },
1090
1090
  "node_modules/nanoid": {
1091
- "version": "3.3.12",
1092
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
1093
- "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
1091
+ "version": "3.3.18",
1092
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
1093
+ "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
1094
1094
  "funding": [
1095
1095
  {
1096
1096
  "type": "github",
1097
1097
  "url": "https://github.com/sponsors/ai"
1098
1098
  }
1099
1099
  ],
1100
- "license": "MIT",
1101
1100
  "peer": true,
1102
1101
  "bin": {
1103
1102
  "nanoid": "bin/nanoid.cjs"
@@ -1210,9 +1209,9 @@
1210
1209
  }
1211
1210
  },
1212
1211
  "node_modules/postcss": {
1213
- "version": "8.5.14",
1214
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
1215
- "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
1212
+ "version": "8.5.26",
1213
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz",
1214
+ "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==",
1216
1215
  "funding": [
1217
1216
  {
1218
1217
  "type": "opencollective",
@@ -1227,10 +1226,9 @@
1227
1226
  "url": "https://github.com/sponsors/ai"
1228
1227
  }
1229
1228
  ],
1230
- "license": "MIT",
1231
1229
  "peer": true,
1232
1230
  "dependencies": {
1233
- "nanoid": "^3.3.11",
1231
+ "nanoid": "^3.3.17",
1234
1232
  "picocolors": "^1.1.1",
1235
1233
  "source-map-js": "^1.2.1"
1236
1234
  },
@@ -2020,10 +2018,9 @@
2020
2018
  }
2021
2019
  },
2022
2020
  "node_modules/svgo": {
2023
- "version": "2.8.2",
2024
- "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.2.tgz",
2025
- "integrity": "sha512-TyzE4NVGLUFy+H/Uy4N6c3G0HEeprsVfge6Lmq+0FdQQ/zqoVYB62IsBZORsiL+o96s6ff/V6/3UQo/C0cgCAA==",
2026
- "license": "MIT",
2021
+ "version": "2.8.3",
2022
+ "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.3.tgz",
2023
+ "integrity": "sha512-5EZD0pafXX6PphdwOGCiVLDSaV1xyuQao2blHajHLsPxr07q4mmEjdtXEWgG07ae2mIz8Ex2CDXNCTiXhy3Khw==",
2027
2024
  "dependencies": {
2028
2025
  "commander": "^7.2.0",
2029
2026
  "css-select": "^4.1.3",